diff options
8 files changed, 193 insertions, 7 deletions
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 16c0910f1273..19397ed3ebaa 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -113,6 +113,7 @@ interface INotificationManager int getAppsBypassingDndCount(int uid); ParceledListSlice getNotificationChannelsBypassingDnd(String pkg, int userId); boolean isPackagePaused(String pkg); + void deleteNotificationHistoryItem(String pkg, int uid, long postedTime); void silenceNotificationSound(); diff --git a/core/java/android/app/NotificationHistory.java b/core/java/android/app/NotificationHistory.java index 909a476f3c94..f26e62874347 100644 --- a/core/java/android/app/NotificationHistory.java +++ b/core/java/android/app/NotificationHistory.java @@ -346,6 +346,26 @@ public final class NotificationHistory implements Parcelable { } /** + * Removes an individual historical notification and regenerates the string pool + */ + public boolean removeNotificationFromWrite(String packageName, long postedTime) { + boolean removed = false; + for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) { + HistoricalNotification hn = mNotificationsToWrite.get(i); + if (packageName.equals(hn.getPackage()) + && postedTime == hn.getPostedTimeMs()) { + removed = true; + mNotificationsToWrite.remove(i); + } + } + if (removed) { + poolStringsFromNotifications(); + } + + return removed; + } + + /** * Gets pooled strings in order to write them to disk */ public @NonNull String[] getPooledStringsToWrite() { diff --git a/core/tests/coretests/src/android/app/NotificationHistoryTest.java b/core/tests/coretests/src/android/app/NotificationHistoryTest.java index 0a21875fd77d..d1608d055604 100644 --- a/core/tests/coretests/src/android/app/NotificationHistoryTest.java +++ b/core/tests/coretests/src/android/app/NotificationHistoryTest.java @@ -234,6 +234,40 @@ public class NotificationHistoryTest { } @Test + public void testRemoveNotificationFromWrite() { + NotificationHistory history = new NotificationHistory(); + + List<HistoricalNotification> postRemoveExpectedEntries = new ArrayList<>(); + List<String> postRemoveExpectedStrings = new ArrayList<>(); + for (int i = 1; i <= 10; i++) { + HistoricalNotification n = getHistoricalNotification("pkg", i); + + if (987654323 != n.getPostedTimeMs()) { + postRemoveExpectedStrings.add(n.getPackage()); + postRemoveExpectedStrings.add(n.getChannelName()); + postRemoveExpectedStrings.add(n.getChannelId()); + postRemoveExpectedEntries.add(n); + } + + history.addNotificationToWrite(n); + } + + history.poolStringsFromNotifications(); + + assertThat(history.getNotificationsToWrite().size()).isEqualTo(10); + // 1 package name and 10 unique channel names and ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(21); + + history.removeNotificationFromWrite("pkg", 987654323); + + + // 1 package names and 9 * 2 unique channel names and ids + assertThat(history.getPooledStringsToWrite().length).isEqualTo(19); + assertThat(history.getNotificationsToWrite()) + .containsExactlyElementsIn(postRemoveExpectedEntries); + } + + @Test public void testParceling() { NotificationHistory history = new NotificationHistory(); diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java index e8cb1632bdc3..061cbd8a1a40 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java @@ -170,6 +170,11 @@ public class NotificationHistoryDatabase { mFileWriteHandler.post(rpr); } + public void deleteNotificationHistoryItem(String pkg, long postedTime) { + RemoveNotificationRunnable rnr = new RemoveNotificationRunnable(pkg, postedTime); + mFileWriteHandler.post(rnr); + } + public void addNotification(final HistoricalNotification notification) { synchronized (mLock) { mBuffer.addNewNotificationToWrite(notification); @@ -367,6 +372,49 @@ public class NotificationHistoryDatabase { } } + final class RemoveNotificationRunnable implements Runnable { + private String mPkg; + private long mPostedTime; + private NotificationHistory mNotificationHistory; + + public RemoveNotificationRunnable(String pkg, long postedTime) { + mPkg = pkg; + mPostedTime = postedTime; + } + + @VisibleForTesting + void setNotificationHistory(NotificationHistory nh) { + mNotificationHistory = nh; + } + + @Override + public void run() { + if (DEBUG) Slog.d(TAG, "RemovePackageRunnable"); + synchronized (mLock) { + // Remove from pending history + mBuffer.removeNotificationFromWrite(mPkg, mPostedTime); + + Iterator<AtomicFile> historyFileItr = mHistoryFiles.iterator(); + while (historyFileItr.hasNext()) { + final AtomicFile af = historyFileItr.next(); + try { + NotificationHistory notificationHistory = mNotificationHistory != null + ? mNotificationHistory + : new NotificationHistory(); + readLocked(af, notificationHistory, + new NotificationHistoryFilter.Builder().build()); + if(notificationHistory.removeNotificationFromWrite(mPkg, mPostedTime)) { + writeLocked(af, notificationHistory); + } + } catch (Exception e) { + Slog.e(TAG, "Cannot clean up file on notification removal " + + af.getBaseFile().getName(), e); + } + } + } + } + } + public static final class NotificationHistoryFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider { final static String TAG = "NotifHistoryFileDate"; diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java index 41bc29f7a82e..9aab0fd912e8 100644 --- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java +++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java @@ -130,7 +130,7 @@ public class NotificationHistoryManager { } } - public void onPackageRemoved(int userId, String packageName) { + public void onPackageRemoved(@UserIdInt int userId, String packageName) { synchronized (mLock) { if (!mUserUnlockedStates.get(userId, false)) { if (mHistoryEnabled.get(userId, false)) { @@ -150,6 +150,22 @@ public class NotificationHistoryManager { } } + public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) { + synchronized (mLock) { + int userId = UserHandle.getUserId(uid); + final NotificationHistoryDatabase userHistory = + getUserHistoryAndInitializeIfNeededLocked(userId); + // TODO: it shouldn't be possible to delete a notification entry while the user is + // locked but we should handle it + if (userHistory == null) { + Slog.w(TAG, "Attempted to remove notif for locked/gone/disabled user " + + userId); + return; + } + userHistory.deleteNotificationHistoryItem(pkg, postedTime); + } + } + // TODO: wire this up to AMS when power button is long pressed public void triggerWriteToDisk() { synchronized (mLock) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f07113591fa5..5fa536caf349 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2780,7 +2780,7 @@ public class NotificationManagerService extends SystemService { if (!isSystemToast) { int count = 0; final int N = mToastQueue.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { final ToastRecord r = mToastQueue.get(i); if (r.pkg.equals(pkg)) { count++; @@ -2820,7 +2820,7 @@ public class NotificationManagerService extends SystemService { if (pkg == null || token == null) { Slog.e(TAG, "Not cancelling notification. pkg=" + pkg + " token=" + token); - return ; + return; } synchronized (mToastQueue) { @@ -2933,14 +2933,14 @@ public class NotificationManagerService extends SystemService { /** * Updates the enabled state for notifications for the given package (and uid). - * Additionally, this method marks the app importance as locked by the user, which means + * Additionally, this method marks the app importance as locked by the user, which + * means * that notifications from the app will <b>not</b> be considered for showing a * blocking helper. * - * @param pkg package that owns the notifications to update - * @param uid uid of the app providing notifications + * @param pkg package that owns the notifications to update + * @param uid uid of the app providing notifications * @param enabled whether notifications should be enabled for the app - * * @see #setNotificationsEnabledForPackage(String, int, boolean) */ @Override @@ -3031,6 +3031,12 @@ public class NotificationManagerService extends SystemService { } @Override + public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) { + checkCallerIsSystem(); + mHistoryManager.deleteNotificationHistoryItem(pkg, uid, postedTime); + } + + @Override public int getPackageImportance(String pkg) { checkCallerIsSystemOrSameApp(pkg); return mPreferencesHelper.getImportance(pkg, Binder.getCallingUid()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java index 5b5ad877081c..cbb760a7a871 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java @@ -44,6 +44,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.internal.matchers.Not; import java.io.File; import java.util.ArrayList; @@ -239,6 +240,52 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { verify(af2, never()).openRead(); } + @Test + public void testRemoveNotificationRunnable() throws Exception { + NotificationHistory nh = mock(NotificationHistory.class); + NotificationHistoryDatabase.RemoveNotificationRunnable rnr = + mDataBase.new RemoveNotificationRunnable("pkg", 123); + rnr.setNotificationHistory(nh); + + AtomicFile af = mock(AtomicFile.class); + when(af.getBaseFile()).thenReturn(new File(mRootDir, "af")); + mDataBase.mHistoryFiles.addLast(af); + + when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(true); + + mDataBase.mBuffer = mock(NotificationHistory.class); + + rnr.run(); + + verify(mDataBase.mBuffer).removeNotificationFromWrite("pkg", 123); + verify(af).openRead(); + verify(nh).removeNotificationFromWrite("pkg", 123); + verify(af).startWrite(); + } + + @Test + public void testRemoveNotificationRunnable_noChanges() throws Exception { + NotificationHistory nh = mock(NotificationHistory.class); + NotificationHistoryDatabase.RemoveNotificationRunnable rnr = + mDataBase.new RemoveNotificationRunnable("pkg", 123); + rnr.setNotificationHistory(nh); + + AtomicFile af = mock(AtomicFile.class); + when(af.getBaseFile()).thenReturn(new File(mRootDir, "af")); + mDataBase.mHistoryFiles.addLast(af); + + when(nh.removeNotificationFromWrite("pkg", 123)).thenReturn(false); + + mDataBase.mBuffer = mock(NotificationHistory.class); + + rnr.run(); + + verify(mDataBase.mBuffer).removeNotificationFromWrite("pkg", 123); + verify(af).openRead(); + verify(nh).removeNotificationFromWrite("pkg", 123); + verify(af, never()).startWrite(); + } + private class TestFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider { public Map<File, Long> creationDates = new HashMap<>(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java index b5eb1bf31848..2c548be185c9 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryManagerTest.java @@ -289,6 +289,20 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase { } @Test + public void testDeleteNotificationHistoryItem_userUnlocked() { + String pkg = "pkg"; + long time = 235; + NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class); + + mHistoryManager.onUserUnlocked(USER_SYSTEM); + mHistoryManager.replaceNotificationHistoryDatabase(USER_SYSTEM, userHistory); + + mHistoryManager.deleteNotificationHistoryItem(pkg, 1, time); + + verify(userHistory, times(1)).deleteNotificationHistoryItem(pkg, time); + } + + @Test public void testTriggerWriteToDisk() { NotificationHistoryDatabase userHistorySystem = mock(NotificationHistoryDatabase.class); NotificationHistoryDatabase userHistoryAll = mock(NotificationHistoryDatabase.class); |