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

Commit 2ca7c59b authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Prevent apps from modifing reserved channels" into main

parents 89017f09 056e801c
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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;

/**
@@ -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
+11 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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,
+12 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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);
                }
@@ -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
+20 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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))
+41 −0
Original line number Diff line number Diff line
@@ -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 {