Loading core/java/android/app/NotificationChannel.java +7 −0 Original line number Diff line number Diff line Loading @@ -55,7 +55,9 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; /** Loading Loading @@ -100,6 +102,11 @@ public final class NotificationChannel implements Parcelable { @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) public static final String RECS_ID = "android.app.recs"; /** @hide */ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) public static final ArrayList<String> SYSTEM_RESERVED_IDS = new ArrayList<>( List.of(NEWS_ID, SOCIAL_MEDIA_ID, PROMOTIONS_ID, RECS_ID)); /** * The formatter used by the system to create an id for notification * channels when it automatically creates conversation channels on behalf of an app. The format Loading services/core/java/com/android/server/notification/NotificationManagerService.java +11 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import static android.app.NotificationChannel.NEWS_ID; import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED; import static android.app.NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED; Loading Loading @@ -109,6 +110,7 @@ import static android.service.notification.Adjustment.TYPE_NEWS; import static android.service.notification.Adjustment.TYPE_PROMOTION; import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA; import static android.service.notification.Flags.callstyleCallbackApi; import static android.service.notification.Flags.notificationClassification; import static android.service.notification.Flags.notificationForceGrouping; import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle; import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners; Loading Loading @@ -4405,6 +4407,15 @@ public class NotificationManagerService extends SystemService { if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { throw new IllegalArgumentException("Cannot delete default channel"); } if (notificationClassification()) { // Check for all reserved channels, but do not throw because it's a common // preexisting pattern for apps to (try to) delete all channels that don't match // their current desired channel structure if (SYSTEM_RESERVED_IDS.contains(channelId)) { Log.v(TAG, "Package " + pkg + " cannot delete a reserved channel"); return; } } enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId); enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId); cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, Loading services/core/java/com/android/server/notification/PreferencesHelper.java +12 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID; import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS; import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; Loading Loading @@ -440,6 +441,12 @@ public class PreferencesHelper implements RankingConfig { channel.setImportanceLockedByCriticalDeviceFunction( r.defaultAppLockedImportance || r.fixedImportance); if (notificationClassification()) { if (SYSTEM_RESERVED_IDS.contains(id) && channel.isDeleted() ) { channel.setDeleted(false); } } if (isShortcutOk(channel) && isDeletionOk(channel)) { r.channels.put(id, channel); } Loading Loading @@ -1023,6 +1030,11 @@ public class PreferencesHelper implements RankingConfig { if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) { throw new IllegalArgumentException("Reserved id"); } // Only the user can update bundle channel settings if (notificationClassification() && !fromSystemOrSystemUi && SYSTEM_RESERVED_IDS.contains(channel.getId())) { return false; } NotificationChannel existing = r.channels.get(channel.getId()); if (existing != null && fromTargetApp) { // Actually modifying an existing channel - keep most of the existing settings Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +20 −1 Original line number Diff line number Diff line Loading @@ -99,6 +99,7 @@ import static android.service.notification.Adjustment.TYPE_NEWS; import static android.service.notification.Condition.SOURCE_CONTEXT; import static android.service.notification.Condition.SOURCE_USER_ACTION; import static android.service.notification.Condition.STATE_TRUE; import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION; import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING; import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; Loading Loading @@ -4420,6 +4421,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED)); } @Test @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) public void testAppsCannotDeleteBundleChannel() throws Exception { when(mCompanionMgr.getAssociations(mPkg, mUserId)) .thenReturn(singletonList(mock(AssociationInfo.class))); mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(), eq(NEWS_ID), anyBoolean())) .thenReturn(mTestNotificationChannel); when(mPreferencesHelper.deleteNotificationChannel(eq(mPkg), anyInt(), eq(NEWS_ID), anyInt(), anyBoolean())).thenReturn(true); reset(mListeners); mBinderService.deleteNotificationChannel(mPkg, NEWS_ID); verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg), eq(Process.myUserHandle()), any(), eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED)); } @Test public void testDeleteChannelOnlyDoExtraWorkIfExisted() throws Exception { when(mCompanionMgr.getAssociations(mPkg, mUserId)) Loading services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +41 −0 Original line number Diff line number Diff line Loading @@ -6222,6 +6222,47 @@ public class PreferencesHelperTest extends UiServiceTestCase { .isEqualTo(IMPORTANCE_LOW); } @Test @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) public void testNotificationBundles_appsCannotUpdate() { // do something that triggers settings creation for an app mHelper.setShowBadge(PKG_O, UID_O, true); NotificationChannel fromApp = new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH); mHelper.createNotificationChannel(PKG_O, UID_O, fromApp, true, false, UID_O, false); assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, false).getImportance()) .isEqualTo(IMPORTANCE_LOW); } @Test @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) public void testNotificationBundles_osCanAllowToBypassDnd() { // do something that triggers settings creation for an app mHelper.setShowBadge(PKG_O, UID_O, true); NotificationChannel fromApp = new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH); mHelper.createNotificationChannel(PKG_O, UID_O, fromApp, true, false, UID_O, false); } @Test @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) public void testUnDeleteBundleChannelsOnLoadIfNotUserChange() throws Exception { mHelper.setShowBadge(PKG_P, UID_P, true); // the public create/update methods should prevent this, so take advantage of the fact that // the object is in the same process mHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).setDeleted(true); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false, UserHandle.USER_ALL, SOCIAL_MEDIA_ID); loadStreamXml(baos, false, UserHandle.USER_ALL); assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true). isDeleted()).isFalse(); } @Test public void testRestoredWithoutUid_threadSafety() throws Exception { Loading Loading
core/java/android/app/NotificationChannel.java +7 −0 Original line number Diff line number Diff line Loading @@ -55,7 +55,9 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; /** Loading Loading @@ -100,6 +102,11 @@ public final class NotificationChannel implements Parcelable { @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) public static final String RECS_ID = "android.app.recs"; /** @hide */ @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION) public static final ArrayList<String> SYSTEM_RESERVED_IDS = new ArrayList<>( List.of(NEWS_ID, SOCIAL_MEDIA_ID, PROMOTIONS_ID, RECS_ID)); /** * The formatter used by the system to create an id for notification * channels when it automatically creates conversation channels on behalf of an app. The format Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +11 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import static android.app.NotificationChannel.NEWS_ID; import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS; import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED; import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED; import static android.app.NotificationManager.ACTION_CONSOLIDATED_NOTIFICATION_POLICY_CHANGED; Loading Loading @@ -109,6 +110,7 @@ import static android.service.notification.Adjustment.TYPE_NEWS; import static android.service.notification.Adjustment.TYPE_PROMOTION; import static android.service.notification.Adjustment.TYPE_SOCIAL_MEDIA; import static android.service.notification.Flags.callstyleCallbackApi; import static android.service.notification.Flags.notificationClassification; import static android.service.notification.Flags.notificationForceGrouping; import static android.service.notification.Flags.redactSensitiveNotificationsBigTextStyle; import static android.service.notification.Flags.redactSensitiveNotificationsFromUntrustedListeners; Loading Loading @@ -4405,6 +4407,15 @@ public class NotificationManagerService extends SystemService { if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) { throw new IllegalArgumentException("Cannot delete default channel"); } if (notificationClassification()) { // Check for all reserved channels, but do not throw because it's a common // preexisting pattern for apps to (try to) delete all channels that don't match // their current desired channel structure if (SYSTEM_RESERVED_IDS.contains(channelId)) { Log.v(TAG, "Package " + pkg + " cannot delete a reserved channel"); return; } } enforceDeletingChannelHasNoFgService(pkg, callingUser, channelId); enforceDeletingChannelHasNoUserInitiatedJob(pkg, callingUser, channelId); cancelAllNotificationsInt(MY_UID, MY_PID, pkg, channelId, 0, 0, Loading
services/core/java/com/android/server/notification/PreferencesHelper.java +12 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID; import static android.app.NotificationChannel.PROMOTIONS_ID; import static android.app.NotificationChannel.RECS_ID; import static android.app.NotificationChannel.SOCIAL_MEDIA_ID; import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS; import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; Loading Loading @@ -440,6 +441,12 @@ public class PreferencesHelper implements RankingConfig { channel.setImportanceLockedByCriticalDeviceFunction( r.defaultAppLockedImportance || r.fixedImportance); if (notificationClassification()) { if (SYSTEM_RESERVED_IDS.contains(id) && channel.isDeleted() ) { channel.setDeleted(false); } } if (isShortcutOk(channel) && isDeletionOk(channel)) { r.channels.put(id, channel); } Loading Loading @@ -1023,6 +1030,11 @@ public class PreferencesHelper implements RankingConfig { if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(channel.getId())) { throw new IllegalArgumentException("Reserved id"); } // Only the user can update bundle channel settings if (notificationClassification() && !fromSystemOrSystemUi && SYSTEM_RESERVED_IDS.contains(channel.getId())) { return false; } NotificationChannel existing = r.channels.get(channel.getId()); if (existing != null && fromTargetApp) { // Actually modifying an existing channel - keep most of the existing settings Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +20 −1 Original line number Diff line number Diff line Loading @@ -99,6 +99,7 @@ import static android.service.notification.Adjustment.TYPE_NEWS; import static android.service.notification.Condition.SOURCE_CONTEXT; import static android.service.notification.Condition.SOURCE_USER_ACTION; import static android.service.notification.Condition.STATE_TRUE; import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION; import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING; import static android.service.notification.Flags.FLAG_REDACT_SENSITIVE_NOTIFICATIONS_FROM_UNTRUSTED_LISTENERS; import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING; Loading Loading @@ -4420,6 +4421,24 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED)); } @Test @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) public void testAppsCannotDeleteBundleChannel() throws Exception { when(mCompanionMgr.getAssociations(mPkg, mUserId)) .thenReturn(singletonList(mock(AssociationInfo.class))); mService.setPreferencesHelper(mPreferencesHelper); when(mPreferencesHelper.getNotificationChannel(eq(mPkg), anyInt(), eq(NEWS_ID), anyBoolean())) .thenReturn(mTestNotificationChannel); when(mPreferencesHelper.deleteNotificationChannel(eq(mPkg), anyInt(), eq(NEWS_ID), anyInt(), anyBoolean())).thenReturn(true); reset(mListeners); mBinderService.deleteNotificationChannel(mPkg, NEWS_ID); verify(mListeners, never()).notifyNotificationChannelChanged(eq(mPkg), eq(Process.myUserHandle()), any(), eq(NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED)); } @Test public void testDeleteChannelOnlyDoExtraWorkIfExisted() throws Exception { when(mCompanionMgr.getAssociations(mPkg, mUserId)) Loading
services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +41 −0 Original line number Diff line number Diff line Loading @@ -6222,6 +6222,47 @@ public class PreferencesHelperTest extends UiServiceTestCase { .isEqualTo(IMPORTANCE_LOW); } @Test @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) public void testNotificationBundles_appsCannotUpdate() { // do something that triggers settings creation for an app mHelper.setShowBadge(PKG_O, UID_O, true); NotificationChannel fromApp = new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH); mHelper.createNotificationChannel(PKG_O, UID_O, fromApp, true, false, UID_O, false); assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, false).getImportance()) .isEqualTo(IMPORTANCE_LOW); } @Test @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) public void testNotificationBundles_osCanAllowToBypassDnd() { // do something that triggers settings creation for an app mHelper.setShowBadge(PKG_O, UID_O, true); NotificationChannel fromApp = new NotificationChannel(NEWS_ID, "The best channel", IMPORTANCE_HIGH); mHelper.createNotificationChannel(PKG_O, UID_O, fromApp, true, false, UID_O, false); } @Test @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) public void testUnDeleteBundleChannelsOnLoadIfNotUserChange() throws Exception { mHelper.setShowBadge(PKG_P, UID_P, true); // the public create/update methods should prevent this, so take advantage of the fact that // the object is in the same process mHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true).setDeleted(true); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false, UserHandle.USER_ALL, SOCIAL_MEDIA_ID); loadStreamXml(baos, false, UserHandle.USER_ALL); assertThat(mXmlHelper.getNotificationChannel(PKG_P, UID_P, SOCIAL_MEDIA_ID, true). isDeleted()).isFalse(); } @Test public void testRestoredWithoutUid_threadSafety() throws Exception { Loading