Remove notification channels from history when they're deleted.
To make this possible, this change adds functionality for notification channel removal to NotificationHistory, NotificationHistoryDatabase, and NotificationHistoryManager and corresponding tests. This change does not remove the notifications from deleted channels from the "recently dismissed" list (stored in mArchive); will follow up with that later. Test: atest NotificationHistoryTest, NotificationHistoryDatabaseTest, NotificationManagerServiceTest; manual via creating channels, deleting them and verifying Bug: 169349809 Change-Id: I7b1c137e516d14703665750f06554ac7ca44c90e
This commit is contained in:
@@ -403,6 +403,26 @@ public final class NotificationHistory implements Parcelable {
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all notifications from a channel and regenerates the string pool
|
||||
*/
|
||||
public boolean removeChannelFromWrite(String packageName, String channelId) {
|
||||
boolean removed = false;
|
||||
for (int i = mNotificationsToWrite.size() - 1; i >= 0; i--) {
|
||||
HistoricalNotification hn = mNotificationsToWrite.get(i);
|
||||
if (packageName.equals(hn.getPackage())
|
||||
&& Objects.equals(channelId, hn.getChannelId())) {
|
||||
removed = true;
|
||||
mNotificationsToWrite.remove(i);
|
||||
}
|
||||
}
|
||||
if (removed) {
|
||||
poolStringsFromNotifications();
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets pooled strings in order to write them to disk
|
||||
*/
|
||||
|
||||
@@ -29,6 +29,8 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -326,6 +328,48 @@ public class NotificationHistoryTest {
|
||||
.containsExactlyElementsIn(postRemoveExpectedEntries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveChannelFromWrite() {
|
||||
NotificationHistory history = new NotificationHistory();
|
||||
|
||||
List<HistoricalNotification> postRemoveExpectedEntries = new ArrayList<>();
|
||||
Set<String> postRemoveExpectedStrings = new HashSet<>();
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
HistoricalNotification n = getHistoricalNotification("pkg", i);
|
||||
|
||||
// Remove channel numbers 5 and 6
|
||||
if (i != 5 && i != 6) {
|
||||
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 channel id that will also be removed
|
||||
history.addNotificationToWrite(getHistoricalNotification("pkg", 6));
|
||||
|
||||
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.removeChannelFromWrite("pkg", "channelId5");
|
||||
history.removeChannelFromWrite("pkg", "channelId6");
|
||||
|
||||
// 1 package names and 8 * 2 unique channel names and ids and 4 conversation ids
|
||||
assertThat(history.getPooledStringsToWrite().length).isEqualTo(21);
|
||||
assertThat(Arrays.asList(history.getPooledStringsToWrite()))
|
||||
.containsExactlyElementsIn(postRemoveExpectedStrings);
|
||||
assertThat(history.getNotificationsToWrite())
|
||||
.containsExactlyElementsIn(postRemoveExpectedEntries);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParceling() {
|
||||
NotificationHistory history = new NotificationHistory();
|
||||
|
||||
@@ -175,6 +175,11 @@ public class NotificationHistoryDatabase {
|
||||
mFileWriteHandler.post(rcr);
|
||||
}
|
||||
|
||||
public void deleteNotificationChannel(String pkg, String channelId) {
|
||||
RemoveChannelRunnable rcr = new RemoveChannelRunnable(pkg, channelId);
|
||||
mFileWriteHandler.post(rcr);
|
||||
}
|
||||
|
||||
public void addNotification(final HistoricalNotification notification) {
|
||||
synchronized (mLock) {
|
||||
mBuffer.addNewNotificationToWrite(notification);
|
||||
@@ -505,4 +510,47 @@ public class NotificationHistoryDatabase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class RemoveChannelRunnable implements Runnable {
|
||||
private String mPkg;
|
||||
private String mChannelId;
|
||||
private NotificationHistory mNotificationHistory;
|
||||
|
||||
RemoveChannelRunnable(String pkg, String channelId) {
|
||||
mPkg = pkg;
|
||||
mChannelId = channelId;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setNotificationHistory(NotificationHistory nh) {
|
||||
mNotificationHistory = nh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (DEBUG) Slog.d(TAG, "RemoveChannelRunnable");
|
||||
synchronized (mLock) {
|
||||
// Remove from pending history
|
||||
mBuffer.removeChannelFromWrite(mPkg, mChannelId);
|
||||
|
||||
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.removeChannelFromWrite(mPkg, mChannelId)) {
|
||||
writeLocked(af, notificationHistory);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Cannot clean up file on channel removal "
|
||||
+ af.getBaseFile().getName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +183,22 @@ public class NotificationHistoryManager {
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteNotificationChannel(String pkg, int uid, String channelId) {
|
||||
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 channel for locked/gone/disabled user "
|
||||
+ userId);
|
||||
return;
|
||||
}
|
||||
userHistory.deleteNotificationChannel(pkg, channelId);
|
||||
}
|
||||
}
|
||||
|
||||
public void triggerWriteToDisk() {
|
||||
synchronized (mLock) {
|
||||
final int userCount = mUserState.size();
|
||||
|
||||
@@ -3623,6 +3623,7 @@ public class NotificationManagerService extends SystemService {
|
||||
cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, true,
|
||||
callingUser, REASON_CHANNEL_REMOVED, null);
|
||||
mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, channelId);
|
||||
mHistoryManager.deleteNotificationChannel(pkg, callingUid, channelId);
|
||||
mListeners.notifyNotificationChannelChanged(pkg,
|
||||
UserHandle.getUserHandleForUid(callingUid),
|
||||
mPreferencesHelper.getNotificationChannel(pkg, callingUid, channelId, true),
|
||||
|
||||
@@ -349,6 +349,52 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
|
||||
verify(af, never()).startWrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveChannelRunnable() throws Exception {
|
||||
NotificationHistory nh = mock(NotificationHistory.class);
|
||||
NotificationHistoryDatabase.RemoveChannelRunnable rcr =
|
||||
mDataBase.new RemoveChannelRunnable("pkg", "channel");
|
||||
rcr.setNotificationHistory(nh);
|
||||
|
||||
AtomicFile af = mock(AtomicFile.class);
|
||||
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
|
||||
mDataBase.mHistoryFiles.addLast(af);
|
||||
|
||||
when(nh.removeChannelFromWrite("pkg", "channel")).thenReturn(true);
|
||||
|
||||
mDataBase.mBuffer = mock(NotificationHistory.class);
|
||||
|
||||
rcr.run();
|
||||
|
||||
verify(mDataBase.mBuffer).removeChannelFromWrite("pkg", "channel");
|
||||
verify(af).openRead();
|
||||
verify(nh).removeChannelFromWrite("pkg", "channel");
|
||||
verify(af).startWrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveChannelRunnable_noChanges() throws Exception {
|
||||
NotificationHistory nh = mock(NotificationHistory.class);
|
||||
NotificationHistoryDatabase.RemoveChannelRunnable rcr =
|
||||
mDataBase.new RemoveChannelRunnable("pkg", "channel");
|
||||
rcr.setNotificationHistory(nh);
|
||||
|
||||
AtomicFile af = mock(AtomicFile.class);
|
||||
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
|
||||
mDataBase.mHistoryFiles.addLast(af);
|
||||
|
||||
when(nh.removeChannelFromWrite("pkg", "channel")).thenReturn(false);
|
||||
|
||||
mDataBase.mBuffer = mock(NotificationHistory.class);
|
||||
|
||||
rcr.run();
|
||||
|
||||
verify(mDataBase.mBuffer).removeChannelFromWrite("pkg", "channel");
|
||||
verify(af).openRead();
|
||||
verify(nh).removeChannelFromWrite("pkg", "channel");
|
||||
verify(af, never()).startWrite();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteBufferRunnable() throws Exception {
|
||||
NotificationHistory nh = mock(NotificationHistory.class);
|
||||
|
||||
@@ -366,7 +366,7 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase {
|
||||
@Test
|
||||
public void testDeleteConversation_userUnlocked() {
|
||||
String pkg = "pkg";
|
||||
Set<String> convos = Set.of("convo", "another");
|
||||
Set<String> convos = Set.of("convo", "another");
|
||||
NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class);
|
||||
|
||||
mHistoryManager.onUserUnlocked(USER_SYSTEM);
|
||||
@@ -377,6 +377,20 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase {
|
||||
verify(userHistory, times(1)).deleteConversations(pkg, convos);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteNotificationChannel_userUnlocked() {
|
||||
String pkg = "pkg";
|
||||
String channelId = "channelId";
|
||||
NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class);
|
||||
|
||||
mHistoryManager.onUserUnlocked(USER_SYSTEM);
|
||||
mHistoryManager.replaceNotificationHistoryDatabase(USER_SYSTEM, userHistory);
|
||||
|
||||
mHistoryManager.deleteNotificationChannel(pkg, 1, channelId);
|
||||
|
||||
verify(userHistory, times(1)).deleteNotificationChannel(pkg, channelId);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTriggerWriteToDisk() {
|
||||
NotificationHistoryDatabase userHistorySystem = mock(NotificationHistoryDatabase.class);
|
||||
|
||||
Reference in New Issue
Block a user