Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ebee7a5c authored by Yuri Lin's avatar Yuri Lin
Browse files

Delete/restore bundle channels on classification permissions change

This change marks all bundle channels as deleted when a bundle type is disabled, and re-enables any formerly existing bundle channels if a bundle type is re-enabled.

Also removes the un-deletion of channels on restore, which is no longer needed (and, now that channel deletion should mirror enbaled types, incorrect)

Flag: android.service.notification.notification_classification
Bug: 380288703
Test: NotificationManagerServiceTest, PreferencesHelperTest

Change-Id: Iff8e790df0c8b012408e73c3cd42da3ed066782f
parent 5ba7eda2
Loading
Loading
Loading
Loading
+31 −10
Original line number Diff line number Diff line
@@ -4450,10 +4450,14 @@ public class NotificationManagerService extends SystemService {
        public void allowAssistantAdjustment(String adjustmentType) {
            checkCallerIsSystemOrSystemUiOrShell();
            mAssistants.allowAdjustmentType(adjustmentType);
            int userId = UserHandle.getUserId(Binder.getCallingUid());
            if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
            if (KEY_TYPE.equals(adjustmentType)) {
                    applyNotificationUpdateForUser(userId,
                if (notificationClassification()) {
                    // restore any existing channels if they previously existed
                    mPreferencesHelper.updateReservedChannels(
                            mAssistants.getAllowedClassificationTypeList(), true);
                }
                if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
                    applyNotificationUpdateForUser(UserHandle.getUserId(Binder.getCallingUid()),
                            NotificationManagerService.this::reclassifyNotificationLocked);
                }
            }
@@ -4466,8 +4470,13 @@ public class NotificationManagerService extends SystemService {
            checkCallerIsSystemOrSystemUiOrShell();
            mAssistants.disallowAdjustmentType(adjustmentType);
            int userId = UserHandle.getUserId(Binder.getCallingUid());
            if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
            if (KEY_TYPE.equals(adjustmentType)) {
                if (notificationClassification()) {
                    // mark any existing channels for all currently allowed types as deleted
                    mPreferencesHelper.updateReservedChannels(
                            mAssistants.getAllowedClassificationTypeList(), false);
                }
                if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
                    applyNotificationUpdateForUser(userId,
                            NotificationManagerService.this::unclassifyNotificationLocked);
                }
@@ -4522,14 +4531,16 @@ public class NotificationManagerService extends SystemService {
        public void setAssistantAdjustmentKeyTypeState(int type, boolean enabled) {
            checkCallerIsSystemOrSystemUiOrShell();
            mAssistants.setAssistantAdjustmentKeyTypeState(type, enabled);
            int userId = UserHandle.getUserId(Binder.getCallingUid());
            if (notificationClassification()) {
                mPreferencesHelper.updateReservedChannels(List.of(type), enabled);
            }
            if ((notificationClassificationUi() && notificationRegroupOnClassification())) {
                if (enabled) {
                    applyNotificationUpdateForUserAndType(
                            UserHandle.getUserId(Binder.getCallingUid()), type,
                    applyNotificationUpdateForUserAndType(userId, type,
                            NotificationManagerService.this::reclassifyNotificationLocked);
                } else {
                    applyNotificationUpdateForUserAndChannelType(
                            UserHandle.getUserId(Binder.getCallingUid()), type,
                    applyNotificationUpdateForUserAndChannelType(userId, type,
                            NotificationManagerService.this::unclassifyNotificationLocked);
                }
            }
@@ -12274,6 +12285,16 @@ public class NotificationManagerService extends SystemService {
            return new int[]{};
        }
        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
        protected @NonNull List<Integer> getAllowedClassificationTypeList() {
            synchronized (mLock) {
                if (notificationClassification()) {
                    return mAllowedClassificationTypes.stream().toList();
                }
            }
            return new ArrayList<>();
        }
        @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
        public void setAssistantAdjustmentKeyTypeState(@Adjustment.Types int type,
                boolean enabled) {
+31 −6
Original line number Diff line number Diff line
@@ -476,12 +476,6 @@ 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);
                }
@@ -1610,6 +1604,37 @@ public class PreferencesHelper implements RankingConfig {
        }
    }

    // Update all reserved channels for the given adjustment type(s) when enabled or disabled.
    // If disabled, all relevant channels are marked as deleted until the type is re-enabled.
    @FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION)
    void updateReservedChannels(List<Integer> changedTypes, boolean enabled) {
        if (!notificationClassification()) {
            return;
        }
        boolean shouldBeDeleted = !enabled;  // just for ease of reading boolean logic
        boolean updated = false;
        synchronized (mLock) {
            for (PackagePreferences p : mPackagePreferences.values()) {
                for (int type : changedTypes) {
                    String channelId = NotificationChannel.getChannelIdForBundleType(type);
                    NotificationChannel c = p.channels.get(channelId);
                    if (c != null && c.isDeleted() != shouldBeDeleted) {
                        c.setDeleted(shouldBeDeleted);
                        c.setDeletedTimeMs(shouldBeDeleted ? System.currentTimeMillis() : -1);
                        updated = true;
                    }
                }
            }
        }
        if (updated) {
            // We shouldn't need to sort upon update: if any current notifications are affected
            // they should be reclassified as part of the enable/disable operation.
            if (android.app.Flags.nmBinderPerfCacheChannels()) {
                invalidateNotificationChannelCache();
            }
        }
    }

    public boolean shouldHideSilentStatusIcons() {
        return mHideSilentStatusBarIcons;
    }
+51 −0
Original line number Diff line number Diff line
@@ -18714,6 +18714,57 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        }
    }
    @Test
    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
    public void testAllowAndDisallowBundling_updatesChannels() throws Exception {
        NotificationManagerService.WorkerHandler handler = mock(
                NotificationManagerService.WorkerHandler.class);
        mService.setHandler(handler);
        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
        when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
        when(mAssistants.isAdjustmentAllowedForPackage(anyString(), anyString())).thenReturn(true);
        List<Integer> allowedTypes = List.of(Adjustment.TYPE_NEWS, Adjustment.TYPE_SOCIAL_MEDIA);
        when(mAssistants.getAllowedClassificationTypeList()).thenReturn(allowedTypes);
        // set mock preferences helper
        mService.setPreferencesHelper(mPreferencesHelper);
        // Disable KEY_TYPE adjustment
        mBinderService.disallowAssistantAdjustment(Adjustment.KEY_TYPE);
        waitForIdle();
        verify(mPreferencesHelper).updateReservedChannels(allowedTypes, false);
        mBinderService.allowAssistantAdjustment(Adjustment.KEY_TYPE);
        waitForIdle();
        verify(mPreferencesHelper).updateReservedChannels(allowedTypes, true);
    }
    @Test
    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
    public void testAllowBundleTypes_updatesChannels() throws Exception {
        NotificationManagerService.WorkerHandler handler = mock(
                NotificationManagerService.WorkerHandler.class);
        mService.setHandler(handler);
        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
        when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true);
        when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true);
        when(mAssistants.isAdjustmentAllowedForPackage(anyString(), anyString())).thenReturn(true);
        // set mock preferences helper
        mService.setPreferencesHelper(mPreferencesHelper);
        mBinderService.setAssistantAdjustmentKeyTypeState(Adjustment.TYPE_NEWS, true);
        waitForIdle();
        verify(mPreferencesHelper).updateReservedChannels(List.of(Adjustment.TYPE_NEWS), true);
        mBinderService.setAssistantAdjustmentKeyTypeState(Adjustment.TYPE_SOCIAL_MEDIA, false);
        waitForIdle();
        verify(mPreferencesHelper).updateReservedChannels(List.of(Adjustment.TYPE_SOCIAL_MEDIA),
                false);
    }
    @Test
    @EnableFlags({FLAG_NM_SUMMARIZATION})
    public void testDisableBundleAdjustment_unsummarizesNotifications() throws Exception {
+25 −14
Original line number Diff line number Diff line
@@ -50,7 +50,6 @@ import static android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
import static android.media.AudioAttributes.USAGE_NOTIFICATION;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;

import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.service.notification.Adjustment.TYPE_CONTENT_RECOMMENDATION;
import static android.service.notification.Adjustment.TYPE_NEWS;
@@ -64,7 +63,6 @@ import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.No
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__DENIED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
import static com.android.server.notification.Flags.FLAG_ALL_NOTIFS_NEED_TTL;
import static com.android.server.notification.Flags.FLAG_PERSIST_INCOMPLETE_RESTORE_DATA;
import static com.android.server.notification.NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER;
import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE;
@@ -164,7 +162,6 @@ import com.android.os.AtomsProto.PackageNotificationPreferences;
import com.android.os.notification.NotificationProtoEnums;
import com.android.server.UiServiceTestCase;
import com.android.server.notification.PermissionHelper.PackagePermission;
import com.android.server.uri.UriGrantsManagerInternal;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -6631,18 +6628,32 @@ public class PreferencesHelperTest extends UiServiceTestCase {

    @Test
    @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
    public void testUnDeleteBundleChannelsOnLoadIfNotUserChange() throws Exception {
        // the public create/update methods should prevent this, so take advantage of the fact that
        // the object is in the same process
        mHelper.createReservedChannel(PKG_N_MR1, UID_N_MR1, TYPE_SOCIAL_MEDIA).setDeleted(true);

        ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
                UserHandle.USER_ALL, SOCIAL_MEDIA_ID);

        loadStreamXml(baos, false, UserHandle.USER_ALL);
    public void testUpdateReservedChannels_disableAndEnable() {
        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_NEWS);
        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_SOCIAL_MEDIA);
        mHelper.createReservedChannel(PKG_O, UID_O, TYPE_CONTENT_RECOMMENDATION);

        assertThat(mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, SOCIAL_MEDIA_ID, true)
                .isDeleted()).isFalse();
        // Ban news & social media types, leave recs as-is
        mHelper.updateReservedChannels(List.of(TYPE_NEWS, TYPE_SOCIAL_MEDIA), false);

        assertThat(
                mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, true).isDeleted()).isTrue();
        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID,
                true).isDeleted()).isTrue();
        assertThat(
                mHelper.getNotificationChannel(PKG_O, UID_O, RECS_ID, true).isDeleted()).isFalse();

        // Enable news (re-enable) and promos (no existing channel; should do nothing)
        mHelper.updateReservedChannels(List.of(TYPE_NEWS, TYPE_PROMOTION), true);
        assertThat(
                mHelper.getNotificationChannel(PKG_O, UID_O, NEWS_ID, true).isDeleted()).isFalse();
        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, PROMOTIONS_ID, true)).isNull();

        // Other channels unaffected
        assertThat(mHelper.getNotificationChannel(PKG_O, UID_O, SOCIAL_MEDIA_ID,
                true).isDeleted()).isTrue();
        assertThat(
                mHelper.getNotificationChannel(PKG_O, UID_O, RECS_ID, true).isDeleted()).isFalse();
    }

    @Test