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

Commit 4f4ad6bf authored by Matías Hernández's avatar Matías Hernández
Browse files

Respect user customization for implicit Zen rules

For both setInterruptionFilter() and setNotificationPolicy(), we ignore the app-supplied value only if the user has customized that specific part of the rule (interruption filter and ZenPolicy respectively). This is to make the API behavior a bit less surprising. (In particular, rules with a customized interruption filter may not exist, depending on how we implement MODES_UI).

Note that this blocking is more localized than the one for updateAutomaticZenRule() (where ~all updates are blocked for a rule customized in any way).

Fixes: 319242222
Test: atest ZenModeHelperTest
Change-Id: Ie6ab94c73f4675b2432b0b130c2ea54616035a5f
parent 2bb60eb6
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -5931,8 +5931,7 @@ public class NotificationManagerService extends SystemService {
                        newVisualEffects, policy.priorityConversationSenders);
                if (shouldApplyAsImplicitRule) {
                    mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy,
                            origin);
                    mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
                } else {
                    ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
                            policy);
+17 −6
Original line number Diff line number Diff line
@@ -605,7 +605,11 @@ public class ZenModeHelper {
                    rule = newImplicitZenRule(callingPkg);
                    newConfig.automaticRules.put(rule.id, rule);
                }
                // If the user has changed the rule's *zenMode*, then don't let app overwrite it.
                // We allow the update if the user has only changed other aspects of the rule.
                if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) {
                    rule.zenMode = zenMode;
                }
                rule.snoozing = false;
                rule.condition = new Condition(rule.conditionId,
                        mContext.getString(R.string.zen_mode_implicit_activated),
@@ -628,7 +632,7 @@ public class ZenModeHelper {
     * {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
     */
    void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
            NotificationManager.Policy policy, @ConfigChangeOrigin int origin) {
            NotificationManager.Policy policy) {
        if (!android.app.Flags.modesApi()) {
            Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
            return;
@@ -644,12 +648,19 @@ public class ZenModeHelper {
                rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
                newConfig.automaticRules.put(rule.id, rule);
            }
            // TODO: b/308673679 - Keep user customization of this rule!
            rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
            setConfigLocked(newConfig, /* triggeringComponent= */ null, origin,
            // If the user has changed the rule's *ZenPolicy*, then don't let app overwrite it.
            // We allow the update if the user has only changed other aspects of the rule.
            if (rule.zenPolicyUserModifiedFields == 0) {
                updatePolicy(
                        rule,
                        ZenAdapters.notificationPolicyToZenPolicy(policy),
                        /* updateBitmask= */ false);

                setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
                        "applyGlobalPolicyAsImplicitZenRule", callingUid);
            }
        }
    }

    /**
     * Returns the {@link Policy} associated to the "implicit" {@link ZenRule} of a package that has
+2 −3
Original line number Diff line number Diff line
@@ -13792,8 +13792,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
        NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
        mBinderService.setNotificationPolicy("package", policy, false);
        verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy),
                eq(ZenModeConfig.UPDATE_ORIGIN_APP));
        verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
    }
    @Test
@@ -13859,7 +13858,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
            verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
        } else {
            verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
                    eq(policy), anyInt());
                    eq(policy));
        }
    }
+148 −13
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static android.service.notification.Condition.SOURCE_SCHEDULE;
import static android.service.notification.Condition.SOURCE_USER_ACTION;
@@ -67,6 +68,7 @@ import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;

import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;

import static junit.framework.Assert.assertEquals;
@@ -295,6 +297,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
        when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
        when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt()))
                .thenReturn(appInfoSpy);
        when(mPackageManager.getApplicationInfo(eq(mContext.getPackageName()), anyInt()))
                .thenReturn(appInfoSpy);
        mZenModeHelper.mPm = mPackageManager;

        mZenModeEventLogger.reset();
@@ -4578,7 +4582,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
                ZEN_MODE_IMPORTANT_INTERRUPTIONS);

        assertThat(mZenModeHelper.mConfig.automaticRules.values())
                .comparingElementsUsing(IGNORE_TIMESTAMPS)
                .comparingElementsUsing(IGNORE_METADATA)
                .containsExactly(
                        expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                                null, true));
@@ -4598,11 +4602,74 @@ public class ZenModeHelperTest extends UiServiceTestCase {
                ZEN_MODE_ALARMS);

        assertThat(mZenModeHelper.mConfig.automaticRules.values())
                .comparingElementsUsing(IGNORE_TIMESTAMPS)
                .comparingElementsUsing(IGNORE_METADATA)
                .containsExactly(
                        expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true));
    }

    @Test
    @EnableFlags(android.app.Flags.FLAG_MODES_API)
    public void applyGlobalZenModeAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
        mZenModeHelper.mConfig.automaticRules.clear();
        String pkg = mContext.getPackageName();

        // From app, call "setInterruptionFilter" and create and implicit rule.
        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
                ZEN_MODE_IMPORTANT_INTERRUPTIONS);
        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
                .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);

        // From user, update that rule's interruption filter.
        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
                .build();
        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
                Process.SYSTEM_UID);

        // From app, call "setInterruptionFilter" again.
        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
                ZEN_MODE_NO_INTERRUPTIONS);

        // The app's update was ignored, and the user's update is still current, and the current
        // mode is the one they chose.
        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
                .isEqualTo(ZEN_MODE_ALARMS);
        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
    }

    @Test
    @EnableFlags(android.app.Flags.FLAG_MODES_API)
    public void applyGlobalZenModeAsImplicitZenRule_ruleCustomizedButNotFilter_updatesRule() {
        mZenModeHelper.mConfig.automaticRules.clear();
        String pkg = mContext.getPackageName();

        // From app, call "setInterruptionFilter" and create and implicit rule.
        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
                ZEN_MODE_IMPORTANT_INTERRUPTIONS);
        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
                .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);

        // From user, update something in that rule, but not the interruption filter.
        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
                .setName("Renamed")
                .build();
        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
                Process.SYSTEM_UID);

        // From app, call "setInterruptionFilter" again.
        mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
                ZEN_MODE_NO_INTERRUPTIONS);

        // The app's update was accepted, and the current mode is the one that they wanted.
        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
                .isEqualTo(ZEN_MODE_NO_INTERRUPTIONS);
        assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_NO_INTERRUPTIONS);
    }

    @Test
    public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() {
        mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
@@ -4673,8 +4740,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
        Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
                PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
                Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy,
                UPDATE_ORIGIN_APP);
        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy);

        ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
                .disallowAllSounds()
@@ -4684,7 +4750,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
                .allowPriorityChannels(true)
                .build();
        assertThat(mZenModeHelper.mConfig.automaticRules.values())
                .comparingElementsUsing(IGNORE_TIMESTAMPS)
                .comparingElementsUsing(IGNORE_METADATA)
                .containsExactly(
                        expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                                expectedZenPolicy, /* conditionActive= */ null));
@@ -4699,14 +4765,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
                PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
                Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
                original, UPDATE_ORIGIN_APP);
                original);

        // Change priorityCallSenders: contacts -> starred.
        Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
                PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED,
                Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated,
                UPDATE_ORIGIN_APP);
        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated);

        ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
                .disallowAllSounds()
@@ -4716,12 +4781,79 @@ public class ZenModeHelperTest extends UiServiceTestCase {
                .allowPriorityChannels(true)
                .build();
        assertThat(mZenModeHelper.mConfig.automaticRules.values())
                .comparingElementsUsing(IGNORE_TIMESTAMPS)
                .comparingElementsUsing(IGNORE_METADATA)
                .containsExactly(
                        expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                                expectedZenPolicy, /* conditionActive= */ null));
    }

    @Test
    @EnableFlags(android.app.Flags.FLAG_MODES_API)
    public void applyGlobalPolicyAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
        mZenModeHelper.mConfig.automaticRules.clear();
        String pkg = mContext.getPackageName();

        // From app, call "setNotificationPolicy" and create and implicit rule.
        Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());

        // From user, update that rule's policy.
        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
        ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds()
                .allowAlarms(true).build();
        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
                .setZenPolicy(userUpdateZenPolicy)
                .build();
        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
                Process.SYSTEM_UID);

        // From app, call "setNotificationPolicy" again.
        Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);

        // The app's update was ignored, and the user's update is still current.
        assertThat(mZenModeHelper.mConfig.automaticRules.values())
                .comparingElementsUsing(IGNORE_METADATA)
                .containsExactly(
                        expectedImplicitRule(pkg, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                                userUpdateZenPolicy,
                                /* conditionActive= */ null));
    }

    @Test
    @EnableFlags(android.app.Flags.FLAG_MODES_API)
    public void applyGlobalPolicyAsImplicitZenRule_ruleCustomizedButNotZenPolicy_updatesRule() {
        mZenModeHelper.mConfig.automaticRules.clear();
        String pkg = mContext.getPackageName();

        // From app, call "setNotificationPolicy" and create and implicit rule.
        Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
        String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());

        // From user, update something in that rule, but not the ZenPolicy.
        AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
        AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
                .setName("Rule renamed, not touching policy")
                .build();
        mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
                Process.SYSTEM_UID);

        // From app, call "setNotificationPolicy" again.
        Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);

        // The app's update was applied.
        ZenPolicy appsSecondZenPolicy = new ZenPolicy.Builder()
                .disallowAllSounds()
                .allowSystem(true)
                .allowPriorityChannels(true)
                .build();
        assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenPolicy)
                .isEqualTo(appsSecondZenPolicy);
    }

    @Test
    public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() {
        mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API);
@@ -4729,7 +4861,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {

        withoutWtfCrash(
                () -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
                        CUSTOM_PKG_UID, new Policy(0, 0, 0), UPDATE_ORIGIN_APP));
                        CUSTOM_PKG_UID, new Policy(0, 0, 0)));

        assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
    }
@@ -4742,7 +4874,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
                Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
                CONVERSATION_SENDERS_IMPORTANT);
        mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
                writtenPolicy, UPDATE_ORIGIN_APP);
                writtenPolicy);

        Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
                CUSTOM_PKG_NAME);
@@ -4782,7 +4914,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
        assertThat(readPolicy.allowConversations()).isFalse();
    }

    private static final Correspondence<ZenRule, ZenRule> IGNORE_TIMESTAMPS =
    private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
            Correspondence.transforming(zr -> {
                Parcel p = Parcel.obtain();
                try {
@@ -4790,12 +4922,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
                    p.setDataPosition(0);
                    ZenRule copy = new ZenRule(p);
                    copy.creationTime = 0;
                    copy.userModifiedFields = 0;
                    copy.zenPolicyUserModifiedFields = 0;
                    copy.zenDeviceEffectsUserModifiedFields = 0;
                    return copy;
                } finally {
                    p.recycle();
                }
            },
            "Ignoring timestamps");
            "Ignoring timestamp and userModifiedFields");

    private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
            @Nullable Boolean conditionActive) {