Merge "Clean up settings and history for deleted convos"

This commit is contained in:
Julia Reynolds
2020-02-18 15:57:39 +00:00
committed by Android (Google) Code Review
9 changed files with 269 additions and 2 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 {

View File

@@ -1223,6 +1223,33 @@ public class PreferencesHelper implements RankingConfig {
}
}
public @NonNull List<String> deleteConversation(String pkg, int uid, String conversationId) {
synchronized (mPackagePreferences) {
List<String> deletedChannelIds = new ArrayList<>();
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return deletedChannelIds;
}
int N = r.channels.size();
for (int i = 0; i < N; i++) {
final NotificationChannel nc = r.channels.valueAt(i);
if (conversationId.equals(nc.getConversationId())) {
nc.setDeleted(true);
LogMaker lm = getChannelLog(nc, pkg);
lm.setType(
com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE);
MetricsLogger.action(lm);
deletedChannelIds.add(nc.getId());
}
}
if (!deletedChannelIds.isEmpty() && mAreChannelsBypassingDnd) {
updateChannelsBypassingDnd(mContext.getUserId());
}
return deletedChannelIds;
}
}
@Override
public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
boolean includeDeleted) {

View File

@@ -286,6 +286,52 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
verify(af, never()).startWrite();
}
@Test
public void testRemoveConversationRunnable() throws Exception {
NotificationHistory nh = mock(NotificationHistory.class);
NotificationHistoryDatabase.RemoveConversationRunnable rcr =
mDataBase.new RemoveConversationRunnable("pkg", "convo");
rcr.setNotificationHistory(nh);
AtomicFile af = mock(AtomicFile.class);
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
mDataBase.mHistoryFiles.addLast(af);
when(nh.removeConversationFromWrite("pkg", "convo")).thenReturn(true);
mDataBase.mBuffer = mock(NotificationHistory.class);
rcr.run();
verify(mDataBase.mBuffer).removeConversationFromWrite("pkg", "convo");
verify(af).openRead();
verify(nh).removeConversationFromWrite("pkg", "convo");
verify(af).startWrite();
}
@Test
public void testRemoveConversationRunnable_noChanges() throws Exception {
NotificationHistory nh = mock(NotificationHistory.class);
NotificationHistoryDatabase.RemoveConversationRunnable rcr =
mDataBase.new RemoveConversationRunnable("pkg", "convo");
rcr.setNotificationHistory(nh);
AtomicFile af = mock(AtomicFile.class);
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af"));
mDataBase.mHistoryFiles.addLast(af);
when(nh.removeConversationFromWrite("pkg", "convo")).thenReturn(false);
mDataBase.mBuffer = mock(NotificationHistory.class);
rcr.run();
verify(mDataBase.mBuffer).removeConversationFromWrite("pkg", "convo");
verify(af).openRead();
verify(nh).removeConversationFromWrite("pkg", "convo");
verify(af, never()).startWrite();
}
private class TestFileAttrProvider implements NotificationHistoryDatabase.FileAttrProvider {
public Map<File, Long> creationDates = new HashMap<>();

View File

@@ -302,6 +302,20 @@ public class NotificationHistoryManagerTest extends UiServiceTestCase {
verify(userHistory, times(1)).deleteNotificationHistoryItem(pkg, time);
}
@Test
public void testDeleteConversation_userUnlocked() {
String pkg = "pkg";
String convo = "convo";
NotificationHistoryDatabase userHistory = mock(NotificationHistoryDatabase.class);
mHistoryManager.onUserUnlocked(USER_SYSTEM);
mHistoryManager.replaceNotificationHistoryDatabase(USER_SYSTEM, userHistory);
mHistoryManager.deleteConversation(pkg, 1, convo);
verify(userHistory, times(1)).deleteConversation(pkg, convo);
}
@Test
public void testTriggerWriteToDisk() {
NotificationHistoryDatabase userHistorySystem = mock(NotificationHistoryDatabase.class);

View File

@@ -3009,7 +3009,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
NotificationChannel channel2 =
new NotificationChannel("B person", "B fabulous person", IMPORTANCE_DEFAULT);
channel2.setConversationId(calls.getId(), channel.getName().toString());
channel2.setConversationId(calls.getId(), channel2.getName().toString());
mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
Map<String, NotificationChannel> expected = new HashMap<>();
@@ -3036,4 +3036,46 @@ public class PreferencesHelperTest extends UiServiceTestCase {
.isEqualTo(expectedGroup.get(convo.getNotificationChannel().getId()));
}
}
@Test
public void testDeleteConversation() {
String convoId = "convo";
NotificationChannel messages =
new NotificationChannel("messages", "Messages", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_O, UID_O, messages, true, false);
NotificationChannel calls =
new NotificationChannel("calls", "Calls", IMPORTANCE_DEFAULT);
mHelper.createNotificationChannel(PKG_O, UID_O, calls, true, false);
NotificationChannel channel =
new NotificationChannel("A person msgs", "messages from A", IMPORTANCE_DEFAULT);
channel.setConversationId(messages.getId(), convoId);
mHelper.createNotificationChannel(PKG_O, UID_O, channel, true, false);
NotificationChannel noMatch =
new NotificationChannel("B person msgs", "messages from B", IMPORTANCE_DEFAULT);
noMatch.setConversationId(messages.getId(), "different convo");
mHelper.createNotificationChannel(PKG_O, UID_O, noMatch, true, false);
NotificationChannel channel2 =
new NotificationChannel("A person calls", "calls from A", IMPORTANCE_DEFAULT);
channel2.setConversationId(calls.getId(), convoId);
mHelper.createNotificationChannel(PKG_O, UID_O, channel2, true, false);
assertEquals(channel, mHelper.getNotificationChannel(PKG_O, UID_O, channel.getId(), false));
assertEquals(channel2,
mHelper.getNotificationChannel(PKG_O, UID_O, channel2.getId(), false));
assertEquals(2, mHelper.deleteConversation(PKG_O, UID_O, convoId).size());
assertEquals(messages,
mHelper.getNotificationChannel(PKG_O, UID_O, messages.getId(), false));
assertEquals(noMatch,
mHelper.getNotificationChannel(PKG_O, UID_O, noMatch.getId(), false));
assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, channel.getId(), false));
assertNull(mHelper.getNotificationChannel(PKG_O, UID_O, channel2.getId(), false));
assertEquals(channel, mHelper.getNotificationChannel(PKG_O, UID_O, channel.getId(), true));
assertEquals(channel2,
mHelper.getNotificationChannel(PKG_O, UID_O, channel2.getId(), true));
}
}