Merge "Remove notification channels from history when they're deleted." into sc-dev
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