Loading core/res/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -7068,6 +7068,10 @@ android:permission="android.permission.BIND_JOB_SERVICE"> </service> <service android:name="com.android.server.notification.NotificationHistoryJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader" android:exported="false"> <intent-filter> Loading services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +12 −85 Original line number Diff line number Diff line Loading @@ -16,15 +16,8 @@ package com.android.server.notification; import android.app.AlarmManager; import android.app.NotificationHistory; import android.app.NotificationHistory.HistoricalNotification; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Handler; import android.util.AtomicFile; import android.util.Slog; Loading Loading @@ -60,18 +53,9 @@ public class NotificationHistoryDatabase { private static final String TAG = "NotiHistoryDatabase"; private static final boolean DEBUG = NotificationManagerService.DBG; private static final int HISTORY_RETENTION_DAYS = 1; private static final int HISTORY_RETENTION_MS = 24 * 60 * 60 * 1000; private static final long WRITE_BUFFER_INTERVAL_MS = 1000 * 60 * 20; private static final long INVALID_FILE_TIME_MS = -1; private static final String ACTION_HISTORY_DELETION = NotificationHistoryDatabase.class.getSimpleName() + ".CLEANUP"; private static final int REQUEST_CODE_DELETION = 1; private static final String SCHEME_DELETION = "delete"; private static final String EXTRA_KEY = "key"; private final Context mContext; private final AlarmManager mAlarmManager; private final Object mLock = new Object(); private final Handler mFileWriteHandler; @VisibleForTesting Loading @@ -87,9 +71,7 @@ public class NotificationHistoryDatabase { @VisibleForTesting NotificationHistory mBuffer; public NotificationHistoryDatabase(Context context, Handler fileWriteHandler, File dir) { mContext = context; mAlarmManager = context.getSystemService(AlarmManager.class); public NotificationHistoryDatabase(Handler fileWriteHandler, File dir) { mCurrentVersion = DEFAULT_CURRENT_VERSION; mFileWriteHandler = fileWriteHandler; mVersionFile = new File(dir, "version"); Loading @@ -97,11 +79,6 @@ public class NotificationHistoryDatabase { mHistoryFiles = new LinkedList<>(); mBuffer = new NotificationHistory(); mWriteBufferRunnable = new WriteBufferRunnable(); IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION); deletionFilter.addDataScheme(SCHEME_DELETION); mContext.registerReceiver(mFileCleanupReceiver, deletionFilter, Context.RECEIVER_EXPORTED_UNAUDITED); } public void init() { Loading @@ -117,7 +94,7 @@ public class NotificationHistoryDatabase { checkVersionAndBuildLocked(); indexFilesLocked(); prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis()); prune(); } } Loading Loading @@ -246,7 +223,14 @@ public class NotificationHistoryDatabase { } /** * Remove any files that are too old and schedule jobs to clean up the rest * Remove any files that are too old. */ void prune() { prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis()); } /** * Remove any files that are too old. */ void prune(final int retentionDays, final long currentTimeMillis) { synchronized (mLock) { Loading @@ -265,10 +249,6 @@ public class NotificationHistoryDatabase { if (creationTime <= retentionBoundary.getTimeInMillis()) { deleteFile(currentOldestFile); } else { // all remaining files are newer than the cut off; schedule jobs to delete scheduleDeletion( currentOldestFile.getBaseFile(), creationTime, retentionDays); } } } Loading Loading @@ -306,26 +286,6 @@ public class NotificationHistoryDatabase { removeFilePathFromHistory(file.getBaseFile().getAbsolutePath()); } private void scheduleDeletion(File file, long creationTime, int retentionDays) { final long deletionTime = creationTime + (retentionDays * HISTORY_RETENTION_MS); scheduleDeletion(file, deletionTime); } private void scheduleDeletion(File file, long deletionTime) { if (DEBUG) { Slog.d(TAG, "Scheduling deletion for " + file.getName() + " at " + deletionTime); } final PendingIntent pi = PendingIntent.getBroadcast(mContext, REQUEST_CODE_DELETION, new Intent(ACTION_HISTORY_DELETION) .setData(new Uri.Builder().scheme(SCHEME_DELETION) .appendPath(file.getAbsolutePath()).build()) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(EXTRA_KEY, file.getAbsolutePath()), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, deletionTime, pi); } private void writeLocked(AtomicFile file, NotificationHistory notifications) throws IOException { FileOutputStream fos = file.startWrite(); Loading Loading @@ -355,12 +315,6 @@ public class NotificationHistoryDatabase { } } public void unregisterFileCleanupReceiver() { if(mContext != null) { mContext.unregisterReceiver(mFileCleanupReceiver); } } private static long safeParseLong(String fileName) { // AtomicFile will create copies of the numeric files with ".new" and ".bak" // over the course of its processing. If these files still exist on boot we need to clean Loading @@ -372,40 +326,15 @@ public class NotificationHistoryDatabase { } } private final BroadcastReceiver mFileCleanupReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) { return; } if (ACTION_HISTORY_DELETION.equals(action)) { try { synchronized (mLock) { final String filePath = intent.getStringExtra(EXTRA_KEY); AtomicFile fileToDelete = new AtomicFile(new File(filePath)); if (DEBUG) { Slog.d(TAG, "Removed " + fileToDelete.getBaseFile().getName()); } fileToDelete.delete(); removeFilePathFromHistory(filePath); } } catch (Exception e) { Slog.e(TAG, "Failed to delete notification history file", e); } } } }; final class WriteBufferRunnable implements Runnable { @Override public void run() { long time = System.currentTimeMillis(); run(time, new AtomicFile(new File(mHistoryDir, String.valueOf(time)))); run(new AtomicFile(new File(mHistoryDir, String.valueOf(time)))); } void run(long time, AtomicFile file) { void run(AtomicFile file) { synchronized (mLock) { if (DEBUG) Slog.d(TAG, "WriteBufferRunnable " + file.getBaseFile().getAbsolutePath()); Loading @@ -413,8 +342,6 @@ public class NotificationHistoryDatabase { writeLocked(file, mBuffer); mHistoryFiles.addFirst(file); mBuffer = new NotificationHistory(); scheduleDeletion(file.getBaseFile(), time, HISTORY_RETENTION_DAYS); } catch (IOException e) { Slog.e(TAG, "Failed to write buffer to disk. not flushing buffer", e); } Loading services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java +1 −1 Original line number Diff line number Diff line Loading @@ -35,6 +35,6 @@ public class NotificationHistoryDatabaseFactory { if(sTestingNotificationHistoryDb != null) { return sTestingNotificationHistoryDb; } return new NotificationHistoryDatabase(context, handler, rootDir); return new NotificationHistoryDatabase(handler, rootDir); } } services/core/java/com/android/server/notification/NotificationHistoryJobService.java 0 → 100644 +81 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import static android.app.job.JobScheduler.RESULT_SUCCESS; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.os.CancellationSignal; import android.util.Slog; import com.android.server.LocalServices; import java.util.concurrent.TimeUnit; /** * This service runs every twenty minutes to ensure the retention policy for notification history * data. */ public class NotificationHistoryJobService extends JobService { private final static String TAG = "NotificationHistoryJob"; private static final long JOB_RUN_INTERVAL = TimeUnit.MINUTES.toMillis(20); static final int BASE_JOB_ID = 237039804; static void scheduleJob(Context context) { JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); if (jobScheduler.getPendingJob(BASE_JOB_ID) == null) { ComponentName component = new ComponentName(context, NotificationHistoryJobService.class); JobInfo newJob = new JobInfo.Builder(BASE_JOB_ID, component) .setRequiresDeviceIdle(false) .setPeriodic(JOB_RUN_INTERVAL) .build(); if (jobScheduler.schedule(newJob) != RESULT_SUCCESS) { Slog.w(TAG, "Failed to schedule history cleanup job"); } } } private CancellationSignal mSignal; @Override public boolean onStartJob(JobParameters params) { mSignal = new CancellationSignal(); new Thread(() -> { NotificationManagerInternal nmInternal = LocalServices.getService(NotificationManagerInternal.class); nmInternal.cleanupHistoryFiles(); jobFinished(params, mSignal.isCanceled()); }).start(); return true; } @Override public boolean onStopJob(JobParameters params) { if (mSignal != null) { mSignal.cancel(); } return false; } } services/core/java/com/android/server/notification/NotificationHistoryManager.java +24 −1 Original line number Diff line number Diff line Loading @@ -84,6 +84,12 @@ public class NotificationHistoryManager { } void onBootPhaseAppsCanStart() { try { Slog.d("julia", "trying to schedule job"); NotificationHistoryJobService.scheduleJob(mContext); } catch (Throwable e) { Slog.e(TAG, "Failed to schedule cleanup job", e); } mSettingsObserver.observe(); } Loading Loading @@ -151,6 +157,24 @@ public class NotificationHistoryManager { } } public void cleanupHistoryFiles() { synchronized (mLock) { int n = mUserUnlockedStates.size(); for (int i = 0; i < n; i++) { // cleanup old files for currently unlocked users. User are additionally cleaned // on unlock in NotificationHistoryDatabase.init(). if (mUserUnlockedStates.valueAt(i)) { final NotificationHistoryDatabase userHistory = mUserState.get(mUserUnlockedStates.keyAt(i)); if (userHistory == null) { continue; } userHistory.prune(); } } } } public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) { synchronized (mLock) { int userId = UserHandle.getUserId(uid); Loading Loading @@ -288,7 +312,6 @@ public class NotificationHistoryManager { private void disableHistory(NotificationHistoryDatabase userHistory, @UserIdInt int userId) { userHistory.disableHistory(); userHistory.unregisterFileCleanupReceiver(); mUserPendingHistoryDisables.put(userId, false); mHistoryEnabled.put(userId, false); Loading Loading
core/res/AndroidManifest.xml +4 −0 Original line number Diff line number Diff line Loading @@ -7068,6 +7068,10 @@ android:permission="android.permission.BIND_JOB_SERVICE"> </service> <service android:name="com.android.server.notification.NotificationHistoryJobService" android:permission="android.permission.BIND_JOB_SERVICE" > </service> <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader" android:exported="false"> <intent-filter> Loading
services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +12 −85 Original line number Diff line number Diff line Loading @@ -16,15 +16,8 @@ package com.android.server.notification; import android.app.AlarmManager; import android.app.NotificationHistory; import android.app.NotificationHistory.HistoricalNotification; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.os.Handler; import android.util.AtomicFile; import android.util.Slog; Loading Loading @@ -60,18 +53,9 @@ public class NotificationHistoryDatabase { private static final String TAG = "NotiHistoryDatabase"; private static final boolean DEBUG = NotificationManagerService.DBG; private static final int HISTORY_RETENTION_DAYS = 1; private static final int HISTORY_RETENTION_MS = 24 * 60 * 60 * 1000; private static final long WRITE_BUFFER_INTERVAL_MS = 1000 * 60 * 20; private static final long INVALID_FILE_TIME_MS = -1; private static final String ACTION_HISTORY_DELETION = NotificationHistoryDatabase.class.getSimpleName() + ".CLEANUP"; private static final int REQUEST_CODE_DELETION = 1; private static final String SCHEME_DELETION = "delete"; private static final String EXTRA_KEY = "key"; private final Context mContext; private final AlarmManager mAlarmManager; private final Object mLock = new Object(); private final Handler mFileWriteHandler; @VisibleForTesting Loading @@ -87,9 +71,7 @@ public class NotificationHistoryDatabase { @VisibleForTesting NotificationHistory mBuffer; public NotificationHistoryDatabase(Context context, Handler fileWriteHandler, File dir) { mContext = context; mAlarmManager = context.getSystemService(AlarmManager.class); public NotificationHistoryDatabase(Handler fileWriteHandler, File dir) { mCurrentVersion = DEFAULT_CURRENT_VERSION; mFileWriteHandler = fileWriteHandler; mVersionFile = new File(dir, "version"); Loading @@ -97,11 +79,6 @@ public class NotificationHistoryDatabase { mHistoryFiles = new LinkedList<>(); mBuffer = new NotificationHistory(); mWriteBufferRunnable = new WriteBufferRunnable(); IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION); deletionFilter.addDataScheme(SCHEME_DELETION); mContext.registerReceiver(mFileCleanupReceiver, deletionFilter, Context.RECEIVER_EXPORTED_UNAUDITED); } public void init() { Loading @@ -117,7 +94,7 @@ public class NotificationHistoryDatabase { checkVersionAndBuildLocked(); indexFilesLocked(); prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis()); prune(); } } Loading Loading @@ -246,7 +223,14 @@ public class NotificationHistoryDatabase { } /** * Remove any files that are too old and schedule jobs to clean up the rest * Remove any files that are too old. */ void prune() { prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis()); } /** * Remove any files that are too old. */ void prune(final int retentionDays, final long currentTimeMillis) { synchronized (mLock) { Loading @@ -265,10 +249,6 @@ public class NotificationHistoryDatabase { if (creationTime <= retentionBoundary.getTimeInMillis()) { deleteFile(currentOldestFile); } else { // all remaining files are newer than the cut off; schedule jobs to delete scheduleDeletion( currentOldestFile.getBaseFile(), creationTime, retentionDays); } } } Loading Loading @@ -306,26 +286,6 @@ public class NotificationHistoryDatabase { removeFilePathFromHistory(file.getBaseFile().getAbsolutePath()); } private void scheduleDeletion(File file, long creationTime, int retentionDays) { final long deletionTime = creationTime + (retentionDays * HISTORY_RETENTION_MS); scheduleDeletion(file, deletionTime); } private void scheduleDeletion(File file, long deletionTime) { if (DEBUG) { Slog.d(TAG, "Scheduling deletion for " + file.getName() + " at " + deletionTime); } final PendingIntent pi = PendingIntent.getBroadcast(mContext, REQUEST_CODE_DELETION, new Intent(ACTION_HISTORY_DELETION) .setData(new Uri.Builder().scheme(SCHEME_DELETION) .appendPath(file.getAbsolutePath()).build()) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(EXTRA_KEY, file.getAbsolutePath()), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, deletionTime, pi); } private void writeLocked(AtomicFile file, NotificationHistory notifications) throws IOException { FileOutputStream fos = file.startWrite(); Loading Loading @@ -355,12 +315,6 @@ public class NotificationHistoryDatabase { } } public void unregisterFileCleanupReceiver() { if(mContext != null) { mContext.unregisterReceiver(mFileCleanupReceiver); } } private static long safeParseLong(String fileName) { // AtomicFile will create copies of the numeric files with ".new" and ".bak" // over the course of its processing. If these files still exist on boot we need to clean Loading @@ -372,40 +326,15 @@ public class NotificationHistoryDatabase { } } private final BroadcastReceiver mFileCleanupReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action == null) { return; } if (ACTION_HISTORY_DELETION.equals(action)) { try { synchronized (mLock) { final String filePath = intent.getStringExtra(EXTRA_KEY); AtomicFile fileToDelete = new AtomicFile(new File(filePath)); if (DEBUG) { Slog.d(TAG, "Removed " + fileToDelete.getBaseFile().getName()); } fileToDelete.delete(); removeFilePathFromHistory(filePath); } } catch (Exception e) { Slog.e(TAG, "Failed to delete notification history file", e); } } } }; final class WriteBufferRunnable implements Runnable { @Override public void run() { long time = System.currentTimeMillis(); run(time, new AtomicFile(new File(mHistoryDir, String.valueOf(time)))); run(new AtomicFile(new File(mHistoryDir, String.valueOf(time)))); } void run(long time, AtomicFile file) { void run(AtomicFile file) { synchronized (mLock) { if (DEBUG) Slog.d(TAG, "WriteBufferRunnable " + file.getBaseFile().getAbsolutePath()); Loading @@ -413,8 +342,6 @@ public class NotificationHistoryDatabase { writeLocked(file, mBuffer); mHistoryFiles.addFirst(file); mBuffer = new NotificationHistory(); scheduleDeletion(file.getBaseFile(), time, HISTORY_RETENTION_DAYS); } catch (IOException e) { Slog.e(TAG, "Failed to write buffer to disk. not flushing buffer", e); } Loading
services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java +1 −1 Original line number Diff line number Diff line Loading @@ -35,6 +35,6 @@ public class NotificationHistoryDatabaseFactory { if(sTestingNotificationHistoryDb != null) { return sTestingNotificationHistoryDb; } return new NotificationHistoryDatabase(context, handler, rootDir); return new NotificationHistoryDatabase(handler, rootDir); } }
services/core/java/com/android/server/notification/NotificationHistoryJobService.java 0 → 100644 +81 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import static android.app.job.JobScheduler.RESULT_SUCCESS; import android.app.job.JobInfo; import android.app.job.JobParameters; import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; import android.os.CancellationSignal; import android.util.Slog; import com.android.server.LocalServices; import java.util.concurrent.TimeUnit; /** * This service runs every twenty minutes to ensure the retention policy for notification history * data. */ public class NotificationHistoryJobService extends JobService { private final static String TAG = "NotificationHistoryJob"; private static final long JOB_RUN_INTERVAL = TimeUnit.MINUTES.toMillis(20); static final int BASE_JOB_ID = 237039804; static void scheduleJob(Context context) { JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); if (jobScheduler.getPendingJob(BASE_JOB_ID) == null) { ComponentName component = new ComponentName(context, NotificationHistoryJobService.class); JobInfo newJob = new JobInfo.Builder(BASE_JOB_ID, component) .setRequiresDeviceIdle(false) .setPeriodic(JOB_RUN_INTERVAL) .build(); if (jobScheduler.schedule(newJob) != RESULT_SUCCESS) { Slog.w(TAG, "Failed to schedule history cleanup job"); } } } private CancellationSignal mSignal; @Override public boolean onStartJob(JobParameters params) { mSignal = new CancellationSignal(); new Thread(() -> { NotificationManagerInternal nmInternal = LocalServices.getService(NotificationManagerInternal.class); nmInternal.cleanupHistoryFiles(); jobFinished(params, mSignal.isCanceled()); }).start(); return true; } @Override public boolean onStopJob(JobParameters params) { if (mSignal != null) { mSignal.cancel(); } return false; } }
services/core/java/com/android/server/notification/NotificationHistoryManager.java +24 −1 Original line number Diff line number Diff line Loading @@ -84,6 +84,12 @@ public class NotificationHistoryManager { } void onBootPhaseAppsCanStart() { try { Slog.d("julia", "trying to schedule job"); NotificationHistoryJobService.scheduleJob(mContext); } catch (Throwable e) { Slog.e(TAG, "Failed to schedule cleanup job", e); } mSettingsObserver.observe(); } Loading Loading @@ -151,6 +157,24 @@ public class NotificationHistoryManager { } } public void cleanupHistoryFiles() { synchronized (mLock) { int n = mUserUnlockedStates.size(); for (int i = 0; i < n; i++) { // cleanup old files for currently unlocked users. User are additionally cleaned // on unlock in NotificationHistoryDatabase.init(). if (mUserUnlockedStates.valueAt(i)) { final NotificationHistoryDatabase userHistory = mUserState.get(mUserUnlockedStates.keyAt(i)); if (userHistory == null) { continue; } userHistory.prune(); } } } } public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) { synchronized (mLock) { int userId = UserHandle.getUserId(uid); Loading Loading @@ -288,7 +312,6 @@ public class NotificationHistoryManager { private void disableHistory(NotificationHistoryDatabase userHistory, @UserIdInt int userId) { userHistory.disableHistory(); userHistory.unregisterFileCleanupReceiver(); mUserPendingHistoryDisables.put(userId, false); mHistoryEnabled.put(userId, false); Loading