Loading core/java/android/app/NotificationHistory.java +20 −0 Original line number Diff line number Diff line Loading @@ -383,6 +383,26 @@ public final class NotificationHistory implements Parcelable { return removed; } /** * Removes all notifications from a conversation and regenerates the string pool */ public boolean removeConversationFromWrite(String packageName, String conversationId) { boolean removed = false; for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) { HistoricalNotification hn = mNotificationsToWrite.get(i); if (packageName.equals(hn.getPackage()) && conversationId.equals(hn.getConversationId())) { removed = true; mNotificationsToWrite.remove(i); } } if (removed) { poolStringsFromNotifications(); } return removed; } /** * Gets pooled strings in order to write them to disk */ Loading core/tests/coretests/src/android/app/NotificationHistoryTest.java +38 −0 Original line number Diff line number Diff line Loading @@ -288,6 +288,44 @@ public class NotificationHistoryTest { .containsExactlyElementsIn(postRemoveExpectedEntries); } @Test public void testRemoveConversationNotificationFromWrite() { 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 (i != 2) { postRemoveExpectedStrings.add(n.getPackage()); postRemoveExpectedStrings.add(n.getChannelName()); postRemoveExpectedStrings.add(n.getChannelId()); if (n.getConversationId() != null) { postRemoveExpectedStrings.add(n.getConversationId()); } postRemoveExpectedEntries.add(n); } history.addNotificationToWrite(n); } // add second notification with the same conversation id that will be removed history.addNotificationToWrite(getHistoricalNotification("pkg", 2)); history.poolStringsFromNotifications(); assertThat(history.getNotificationsToWrite().size()).isEqualTo(11); // 1 package name and 20 unique channel names and ids and 5 conversation ids assertThat(history.getPooledStringsToWrite().length).isEqualTo(26); history.removeConversationFromWrite("pkg", "convo2"); // 1 package names and 9 * 2 unique channel names and ids and 4 conversation ids assertThat(history.getPooledStringsToWrite().length).isEqualTo(23); assertThat(history.getNotificationsToWrite()) .containsExactlyElementsIn(postRemoveExpectedEntries); } @Test public void testParceling() { NotificationHistory history = new NotificationHistory(); Loading services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +49 −1 Original line number Diff line number Diff line Loading @@ -175,6 +175,11 @@ public class NotificationHistoryDatabase { mFileWriteHandler.post(rnr); } public void deleteConversation(String pkg, String conversationId) { RemoveConversationRunnable rcr = new RemoveConversationRunnable(pkg, conversationId); mFileWriteHandler.post(rcr); } public void addNotification(final HistoricalNotification notification) { synchronized (mLock) { mBuffer.addNewNotificationToWrite(notification); Loading Loading @@ -396,7 +401,7 @@ public class NotificationHistoryDatabase { @Override public void run() { if (DEBUG) Slog.d(TAG, "RemovePackageRunnable"); if (DEBUG) Slog.d(TAG, "RemoveNotificationRunnable"); synchronized (mLock) { // Remove from pending history mBuffer.removeNotificationFromWrite(mPkg, mPostedTime); Loading @@ -422,6 +427,49 @@ public class NotificationHistoryDatabase { } } final class RemoveConversationRunnable implements Runnable { private String mPkg; private String mConversationId; private NotificationHistory mNotificationHistory; public RemoveConversationRunnable(String pkg, String conversationId) { mPkg = pkg; mConversationId = conversationId; } @VisibleForTesting void setNotificationHistory(NotificationHistory nh) { mNotificationHistory = nh; } @Override public void run() { if (DEBUG) Slog.d(TAG, "RemoveConversationRunnable"); synchronized (mLock) { // Remove from pending history mBuffer.removeConversationFromWrite(mPkg, mConversationId); 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.removeConversationFromWrite(mPkg, mConversationId)) { writeLocked(af, notificationHistory); } } catch (Exception e) { Slog.e(TAG, "Cannot clean up file on conversation removal " + af.getBaseFile().getName(), e); } } } } } public static final class NotificationHistoryFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider { final static String TAG = "NotifHistoryFileDate"; Loading services/core/java/com/android/server/notification/NotificationHistoryManager.java +16 −0 Original line number Diff line number Diff line Loading @@ -166,6 +166,22 @@ public class NotificationHistoryManager { } } public void deleteConversation(String pkg, int uid, String conversationId) { 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 conversation for locked/gone/disabled user " + userId); return; } userHistory.deleteConversation(pkg, conversationId); } } // TODO: wire this up to AMS when power button is long pressed public void triggerWriteToDisk() { synchronized (mLock) { Loading services/core/java/com/android/server/notification/NotificationManagerService.java +16 −0 Original line number Diff line number Diff line Loading @@ -5655,6 +5655,22 @@ public class NotificationManagerService extends SystemService { mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground)); } public void onConversationRemoved(String pkg, int uid, String conversationId) { checkCallerIsSystem(); Preconditions.checkStringNotEmpty(pkg); Preconditions.checkStringNotEmpty(conversationId); mHistoryManager.deleteConversation(pkg, uid, conversationId); List<String> deletedChannelIds = mPreferencesHelper.deleteConversation(pkg, uid, conversationId); for (String channelId : deletedChannelIds) { cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true, UserHandle.getUserId(uid), REASON_CHANNEL_BANNED, null); } handleSavePolicyFile(); } @VisibleForTesting protected void fixNotification(Notification notification, String pkg, String tag, int id, int userId) throws NameNotFoundException { Loading Loading
core/java/android/app/NotificationHistory.java +20 −0 Original line number Diff line number Diff line Loading @@ -383,6 +383,26 @@ public final class NotificationHistory implements Parcelable { return removed; } /** * Removes all notifications from a conversation and regenerates the string pool */ public boolean removeConversationFromWrite(String packageName, String conversationId) { boolean removed = false; for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) { HistoricalNotification hn = mNotificationsToWrite.get(i); if (packageName.equals(hn.getPackage()) && conversationId.equals(hn.getConversationId())) { removed = true; mNotificationsToWrite.remove(i); } } if (removed) { poolStringsFromNotifications(); } return removed; } /** * Gets pooled strings in order to write them to disk */ Loading
core/tests/coretests/src/android/app/NotificationHistoryTest.java +38 −0 Original line number Diff line number Diff line Loading @@ -288,6 +288,44 @@ public class NotificationHistoryTest { .containsExactlyElementsIn(postRemoveExpectedEntries); } @Test public void testRemoveConversationNotificationFromWrite() { 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 (i != 2) { postRemoveExpectedStrings.add(n.getPackage()); postRemoveExpectedStrings.add(n.getChannelName()); postRemoveExpectedStrings.add(n.getChannelId()); if (n.getConversationId() != null) { postRemoveExpectedStrings.add(n.getConversationId()); } postRemoveExpectedEntries.add(n); } history.addNotificationToWrite(n); } // add second notification with the same conversation id that will be removed history.addNotificationToWrite(getHistoricalNotification("pkg", 2)); history.poolStringsFromNotifications(); assertThat(history.getNotificationsToWrite().size()).isEqualTo(11); // 1 package name and 20 unique channel names and ids and 5 conversation ids assertThat(history.getPooledStringsToWrite().length).isEqualTo(26); history.removeConversationFromWrite("pkg", "convo2"); // 1 package names and 9 * 2 unique channel names and ids and 4 conversation ids assertThat(history.getPooledStringsToWrite().length).isEqualTo(23); assertThat(history.getNotificationsToWrite()) .containsExactlyElementsIn(postRemoveExpectedEntries); } @Test public void testParceling() { NotificationHistory history = new NotificationHistory(); Loading
services/core/java/com/android/server/notification/NotificationHistoryDatabase.java +49 −1 Original line number Diff line number Diff line Loading @@ -175,6 +175,11 @@ public class NotificationHistoryDatabase { mFileWriteHandler.post(rnr); } public void deleteConversation(String pkg, String conversationId) { RemoveConversationRunnable rcr = new RemoveConversationRunnable(pkg, conversationId); mFileWriteHandler.post(rcr); } public void addNotification(final HistoricalNotification notification) { synchronized (mLock) { mBuffer.addNewNotificationToWrite(notification); Loading Loading @@ -396,7 +401,7 @@ public class NotificationHistoryDatabase { @Override public void run() { if (DEBUG) Slog.d(TAG, "RemovePackageRunnable"); if (DEBUG) Slog.d(TAG, "RemoveNotificationRunnable"); synchronized (mLock) { // Remove from pending history mBuffer.removeNotificationFromWrite(mPkg, mPostedTime); Loading @@ -422,6 +427,49 @@ public class NotificationHistoryDatabase { } } final class RemoveConversationRunnable implements Runnable { private String mPkg; private String mConversationId; private NotificationHistory mNotificationHistory; public RemoveConversationRunnable(String pkg, String conversationId) { mPkg = pkg; mConversationId = conversationId; } @VisibleForTesting void setNotificationHistory(NotificationHistory nh) { mNotificationHistory = nh; } @Override public void run() { if (DEBUG) Slog.d(TAG, "RemoveConversationRunnable"); synchronized (mLock) { // Remove from pending history mBuffer.removeConversationFromWrite(mPkg, mConversationId); 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.removeConversationFromWrite(mPkg, mConversationId)) { writeLocked(af, notificationHistory); } } catch (Exception e) { Slog.e(TAG, "Cannot clean up file on conversation removal " + af.getBaseFile().getName(), e); } } } } } public static final class NotificationHistoryFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider { final static String TAG = "NotifHistoryFileDate"; Loading
services/core/java/com/android/server/notification/NotificationHistoryManager.java +16 −0 Original line number Diff line number Diff line Loading @@ -166,6 +166,22 @@ public class NotificationHistoryManager { } } public void deleteConversation(String pkg, int uid, String conversationId) { 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 conversation for locked/gone/disabled user " + userId); return; } userHistory.deleteConversation(pkg, conversationId); } } // TODO: wire this up to AMS when power button is long pressed public void triggerWriteToDisk() { synchronized (mLock) { Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +16 −0 Original line number Diff line number Diff line Loading @@ -5655,6 +5655,22 @@ public class NotificationManagerService extends SystemService { mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground)); } public void onConversationRemoved(String pkg, int uid, String conversationId) { checkCallerIsSystem(); Preconditions.checkStringNotEmpty(pkg); Preconditions.checkStringNotEmpty(conversationId); mHistoryManager.deleteConversation(pkg, uid, conversationId); List<String> deletedChannelIds = mPreferencesHelper.deleteConversation(pkg, uid, conversationId); for (String channelId : deletedChannelIds) { cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true, UserHandle.getUserId(uid), REASON_CHANNEL_BANNED, null); } handleSavePolicyFile(); } @VisibleForTesting protected void fixNotification(Notification notification, String pkg, String tag, int id, int userId) throws NameNotFoundException { Loading