Restore deleted conversation channel on notification posted
Restore a deleted conversation channel when posting a conversation
notification. Previously, the conversation channel was restored/created
when opening the notification guts (long-press on notification). This caused some notifications be posted with sound even though the user chose to silence the conversation via guts because the channel was deleted on reposting: b/280235550.
Test: atest com.android.server.notification.NotificationManagerServiceTest#testRestoreConversationChannel_deleted
Test: atest PreferencesHelperTest
Bug: 304016442
Change-Id: I8be8e280e9a87278635228c7a9dd5baa2e29eb26
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2ec3700..fcae8c0 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7031,9 +7031,8 @@
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
String shortcutId = n.getShortcutId();
- final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
- pkg, notificationUid, channelId, shortcutId,
- true /* parent ok */, false /* includeDeleted */);
+ final NotificationChannel channel = getNotificationChannelRestoreDeleted(pkg,
+ callingUid, notificationUid, channelId, shortcutId);
if (channel == null) {
final String noChannelStr = "No Channel found for "
+ "pkg=" + pkg
@@ -7151,6 +7150,35 @@
return true;
}
+ /**
+ * Returns a channel, if exists, and restores deleted conversation channels.
+ */
+ @Nullable
+ private NotificationChannel getNotificationChannelRestoreDeleted(String pkg,
+ int callingUid, int notificationUid, String channelId, String conversationId) {
+ // Restore a deleted conversation channel, if exists. Otherwise use the parent channel.
+ NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
+ pkg, notificationUid, channelId, conversationId,
+ true /* parent ok */, !TextUtils.isEmpty(conversationId) /* includeDeleted */);
+ // Restore deleted conversation channel
+ if (channel != null && channel.isDeleted()) {
+ if (Objects.equals(conversationId, channel.getConversationId())) {
+ boolean needsPolicyFileChange = mPreferencesHelper.createNotificationChannel(
+ pkg, notificationUid, channel, true /* fromTargetApp */,
+ mConditionProviders.isPackageOrComponentAllowed(pkg,
+ UserHandle.getUserId(notificationUid)), callingUid, true);
+ // Update policy file if the conversation channel was restored
+ if (needsPolicyFileChange) {
+ handleSavePolicyFile();
+ }
+ } else {
+ // Do not restore parent channel
+ channel = null;
+ }
+ }
+ return channel;
+ }
+
private void onConversationRemovedInternal(String pkg, int uid, Set<String> shortcuts) {
checkCallerIsSystem();
Preconditions.checkStringNotEmpty(pkg);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 3803244..7fb8b30 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3239,7 +3239,7 @@
mService.setPreferencesHelper(mPreferencesHelper);
when(mPreferencesHelper.getNotificationChannel(
anyString(), anyInt(), eq("foo"), anyBoolean())).thenReturn(
- new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
+ new NotificationChannel("foo", "foo", IMPORTANCE_HIGH));
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
@@ -9927,6 +9927,174 @@
}
@Test
+ public void testRestoreConversationChannel_deleted() throws Exception {
+ // Create parent channel
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+ IMPORTANCE_DEFAULT);
+ NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+ NotificationChannel.CREATOR);
+ assertEquals(originalChannel, parentChannel);
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(parentChannel)));
+
+ //Create deleted conversation channel
+ mBinderService.createConversationNotificationChannelForPackage(
+ PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID);
+ final NotificationChannel conversationChannel =
+ mBinderService.getConversationNotificationChannel(
+ PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID);
+ conversationChannel.setDeleted(true);
+
+ //Create notification record
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+ null /* groupKey */, false /* isSummary */);
+ nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
+ nb.setChannelId(originalChannel.getId());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+ assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Verify that the channel was changed to the conversation channel and restored
+ assertThat(mService.getNotificationRecord(nr.getKey()).isConversation()).isTrue();
+ assertThat(mService.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo(
+ conversationChannel);
+ assertThat(mService.getNotificationRecord(nr.getKey()).getChannel().isDeleted()).isFalse();
+ assertThat(mService.getNotificationRecord(nr.getKey()).getChannel().getDeletedTimeMs())
+ .isEqualTo(-1);
+ }
+
+ @Test
+ public void testDoNotRestoreParentChannel_deleted() throws Exception {
+ // Create parent channel and set as deleted
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+ IMPORTANCE_DEFAULT);
+ NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+ NotificationChannel.CREATOR);
+ assertEquals(originalChannel, parentChannel);
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(parentChannel)));
+ parentChannel.setDeleted(true);
+
+ //Create notification record
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+ null /* groupKey */, false /* isSummary */);
+ nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
+ nb.setChannelId(originalChannel.getId());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+ assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+ when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Verify that the channel was not restored and the notification was not posted
+ assertThat(mService.mChannelToastsSent).contains(mUid);
+ assertThat(mService.getNotificationRecord(nr.getKey())).isNull();
+ assertThat(parentChannel.isDeleted()).isTrue();
+ }
+
+ @Test
+ public void testEnqueueToConversationChannel_notDeleted_doesNotRestore() throws Exception {
+ TestableNotificationManagerService service = spy(mService);
+ PreferencesHelper preferencesHelper = spy(mService.mPreferencesHelper);
+ service.setPreferencesHelper(preferencesHelper);
+ // Create parent channel
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+ IMPORTANCE_DEFAULT);
+ NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+ NotificationChannel.CREATOR);
+ assertEquals(originalChannel, parentChannel);
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(parentChannel)));
+
+ //Create conversation channel
+ mBinderService.createConversationNotificationChannelForPackage(
+ PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID);
+ final NotificationChannel conversationChannel =
+ mBinderService.getConversationNotificationChannel(
+ PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID);
+
+ //Create notification record
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+ null /* groupKey */, false /* isSummary */);
+ nb.setShortcutId(VALID_CONVO_SHORTCUT_ID);
+ nb.setChannelId(originalChannel.getId());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+ assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Verify that the channel was changed to the conversation channel and not restored
+ assertThat(service.getNotificationRecord(nr.getKey()).isConversation()).isTrue();
+ assertThat(service.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo(
+ conversationChannel);
+ verify(service, never()).handleSavePolicyFile();
+ verify(preferencesHelper, never()).createNotificationChannel(anyString(),
+ anyInt(), any(), anyBoolean(), anyBoolean(), anyInt(), anyBoolean());
+ }
+
+ @Test
+ public void testEnqueueToParentChannel_notDeleted_doesNotRestore() throws Exception {
+ TestableNotificationManagerService service = spy(mService);
+ PreferencesHelper preferencesHelper = spy(mService.mPreferencesHelper);
+ service.setPreferencesHelper(preferencesHelper);
+ // Create parent channel
+ when(mPackageManager.isPackageSuspendedForUser(anyString(), anyInt())).thenReturn(false);
+ final NotificationChannel originalChannel = new NotificationChannel("id", "name",
+ IMPORTANCE_DEFAULT);
+ NotificationChannel parentChannel = parcelAndUnparcel(originalChannel,
+ NotificationChannel.CREATOR);
+ assertEquals(originalChannel, parentChannel);
+ mBinderService.createNotificationChannels(PKG,
+ new ParceledListSlice(Arrays.asList(parentChannel)));
+
+ //Create deleted conversation channel
+ mBinderService.createConversationNotificationChannelForPackage(
+ PKG, mUid, parentChannel, VALID_CONVO_SHORTCUT_ID);
+ final NotificationChannel conversationChannel =
+ mBinderService.getConversationNotificationChannel(
+ PKG, mUserId, PKG, originalChannel.getId(), false, VALID_CONVO_SHORTCUT_ID);
+
+ //Create notification record without a shortcutId
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addDefaultMetadata */,
+ null /* groupKey */, false /* isSummary */);
+ nb.setShortcutId(null);
+ nb.setChannelId(originalChannel.getId());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "tag", mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, originalChannel);
+ assertThat(nr.getChannel()).isEqualTo(originalChannel);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+ nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+ waitForIdle();
+
+ // Verify that the channel is the parent channel and no channel was restored
+ //assertThat(service.getNotificationRecord(nr.getKey()).isConversation()).isFalse();
+ assertThat(service.getNotificationRecord(nr.getKey()).getChannel()).isEqualTo(
+ parentChannel);
+ verify(service, never()).handleSavePolicyFile();
+ verify(preferencesHelper, never()).createNotificationChannel(anyString(),
+ anyInt(), any(), anyBoolean(), anyBoolean(), anyInt(), anyBoolean());
+ }
+
+ @Test
public void testGetConversationsForPackage_hasShortcut() throws Exception {
mService.setPreferencesHelper(mPreferencesHelper);
ArrayList<ConversationChannelWrapper> convos = new ArrayList<>();