Merge "Remove notification channels from history when they're deleted." into sc-dev

This commit is contained in:
Yuri Lin
2021-04-05 18:39:40 +00:00
committed by Android (Google) Code Review
7 changed files with 190 additions and 1 deletions

View File

@@ -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
*/

View File

@@ -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();

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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();

View File

@@ -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),

View File

@@ -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);

View File

@@ -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);