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

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

Merge "Tell apps when their rules are de/activated." into main

parents e3ea9b5d 3a404c5b
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -6952,6 +6952,8 @@ package android.app {
    field public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
    field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
    field public static final String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
    field @FlaggedApi("android.app.modes_api") public static final int AUTOMATIC_RULE_STATUS_ACTIVATED = 4; // 0x4
    field @FlaggedApi("android.app.modes_api") public static final int AUTOMATIC_RULE_STATUS_DEACTIVATED = 5; // 0x5
    field public static final int AUTOMATIC_RULE_STATUS_DISABLED = 2; // 0x2
    field public static final int AUTOMATIC_RULE_STATUS_ENABLED = 1; // 0x1
    field public static final int AUTOMATIC_RULE_STATUS_REMOVED = 3; // 0x3
+25 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.app;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -272,6 +273,7 @@ public class NotificationManager {
     *     {@link #AUTOMATIC_RULE_STATUS_UNKNOWN}.
     * </p>
     */
    // TODO (b/309101513): Add new status types to javadoc
    public static final String EXTRA_AUTOMATIC_ZEN_RULE_STATUS =
            "android.app.extra.AUTOMATIC_ZEN_RULE_STATUS";

@@ -286,7 +288,8 @@ public class NotificationManager {
    /** @hide */
    @IntDef(prefix = { "AUTOMATIC_RULE_STATUS" }, value = {
            AUTOMATIC_RULE_STATUS_ENABLED, AUTOMATIC_RULE_STATUS_DISABLED,
            AUTOMATIC_RULE_STATUS_REMOVED, AUTOMATIC_RULE_STATUS_UNKNOWN
            AUTOMATIC_RULE_STATUS_REMOVED, AUTOMATIC_RULE_STATUS_UNKNOWN,
            AUTOMATIC_RULE_STATUS_ACTIVATED, AUTOMATIC_RULE_STATUS_DEACTIVATED
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface AutomaticZenRuleStatus {}
@@ -319,6 +322,27 @@ public class NotificationManager {
     */
    public static final int AUTOMATIC_RULE_STATUS_REMOVED = 3;

    /**
     * Constant value for {@link #EXTRA_AUTOMATIC_ZEN_RULE_STATUS} - the given rule has been
     * activated by the user or cross device sync. Sent from
     * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}. If the rule owner has a mode that includes
     * a DND component, the rule owner should activate any extra behavior that's part of that mode
     * in response to this broadcast.
     */
    @FlaggedApi(Flags.FLAG_MODES_API)
    public static final int AUTOMATIC_RULE_STATUS_ACTIVATED = 4;

    /**
     * Constant value for {@link #EXTRA_AUTOMATIC_ZEN_RULE_STATUS} - the given rule has been
     * deactivated ("snoozed") by the user. The rule will not return to an activated state until
     * the app calls {@link #setAutomaticZenRuleState(String, Condition)} with
     * {@link Condition#STATE_FALSE} (either immediately or when the trigger criteria is no
     * longer met) and then {@link Condition#STATE_TRUE} when the trigger criteria is freshly met,
     * or when the user re-activates it.
     */
    @FlaggedApi(Flags.FLAG_MODES_API)
    public static final int AUTOMATIC_RULE_STATUS_DEACTIVATED = 5;

    /**
     * Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes.
     *
+69 −10
Original line number Diff line number Diff line
@@ -16,14 +16,19 @@

package com.android.server.notification;

import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY;
import static android.service.notification.NotificationServiceProto.ROOT_CONFIG;

import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;

import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.AutomaticZenRule;
import android.app.Flags;
@@ -31,6 +36,9 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.app.PendingIntent;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -51,6 +59,7 @@ import android.media.AudioSystem;
import android.media.VolumePolicy;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -109,6 +118,13 @@ public class ZenModeHelper {
    private static final int RULE_INSTANCE_GRACE_PERIOD = 1000 * 60 * 60 * 72;
    static final int RULE_LIMIT_PER_PACKAGE = 100;

    /**
     * Send new activation AutomaticZenRule statuses to apps with a min target SDK version
     */
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
    static final long SEND_ACTIVATION_AZR_STATUSES = 308673617L;

    // pkg|userId => uid
    @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>();

@@ -229,7 +245,8 @@ public class ZenModeHelper {
            // was read in via XML, but will initialize zen mode if nothing was read in and the
            // config remains the default.
            updateConfigAndZenModeLocked(mConfig, "init", true /*setRingerMode*/,
                    Process.SYSTEM_UID /* callingUid */, true /* is system */);
                    Process.SYSTEM_UID /* callingUid */, true /* is system */,
                    false /* no broadcasts*/);
        }
    }

@@ -407,10 +424,13 @@ public class ZenModeHelper {
                            "Cannot update rules not owned by your condition provider");
                }
            }
            if (!Flags.modesApi()) {
                if (rule.enabled != automaticZenRule.isEnabled()) {
                    dispatchOnAutomaticRuleStatusChanged(mConfig.user, rule.getPkg(), ruleId,
                            automaticZenRule.isEnabled()
                                ? AUTOMATIC_RULE_STATUS_ENABLED : AUTOMATIC_RULE_STATUS_DISABLED);
                                    ? AUTOMATIC_RULE_STATUS_ENABLED
                                    : AUTOMATIC_RULE_STATUS_DISABLED);
                }
            }

            populateZenRule(rule.pkg, automaticZenRule, rule, false);
@@ -651,6 +671,9 @@ public class ZenModeHelper {

    private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
            boolean isNew) {
        if (rule.enabled != automaticZenRule.isEnabled()) {
            rule.snoozing = false;
        }
        rule.name = automaticZenRule.getName();
        rule.condition = null;
        rule.conditionId = automaticZenRule.getConditionId();
@@ -668,9 +691,6 @@ public class ZenModeHelper {
            rule.pkg = pkg;
        }

        if (rule.enabled != automaticZenRule.isEnabled()) {
            rule.snoozing = false;
        }
        if (Flags.modesApi()) {
            rule.allowManualInvocation = automaticZenRule.isManualInvocationAllowed();
            rule.iconResId = automaticZenRule.getIconResId();
@@ -706,6 +726,27 @@ public class ZenModeHelper {
        return azr;
    }

    @SuppressLint("MissingPermission")
    void scheduleActivationBroadcast(String pkg, @UserIdInt int userId, String ruleId,
            boolean activated) {
        if (CompatChanges.isChangeEnabled(
                SEND_ACTIVATION_AZR_STATUSES, pkg, UserHandle.of(userId))) {
            dispatchOnAutomaticRuleStatusChanged(userId, pkg, ruleId, activated
                    ? AUTOMATIC_RULE_STATUS_ACTIVATED
                    : AUTOMATIC_RULE_STATUS_DEACTIVATED);
        } else {
            dispatchOnAutomaticRuleStatusChanged(
                    userId, pkg, ruleId, AUTOMATIC_RULE_STATUS_UNKNOWN);
        }
    }

    void scheduleEnabledBroadcast(String pkg, @UserIdInt int userId, String ruleId,
            boolean enabled) {
        dispatchOnAutomaticRuleStatusChanged(userId, pkg, ruleId, enabled
                ? AUTOMATIC_RULE_STATUS_ENABLED
                : AUTOMATIC_RULE_STATUS_DISABLED);
    }

    public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason,
            int callingUid, boolean fromSystemOrSystemUi) {
        setManualZenMode(zenMode, conditionId, reason, caller, true /*setRingerMode*/, callingUid,
@@ -1002,7 +1043,7 @@ public class ZenModeHelper {
                dispatchOnPolicyChanged();
            }
            updateConfigAndZenModeLocked(config, reason, setRingerMode, callingUid,
                    fromSystemOrSystemUi);
                    fromSystemOrSystemUi, true);
            mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/);
            return true;
        } catch (SecurityException e) {
@@ -1019,13 +1060,31 @@ public class ZenModeHelper {
     */
    @GuardedBy("mConfigLock")
    private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason,
            boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) {
            boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi,
            boolean sendBroadcasts) {
        final boolean logZenModeEvents = mFlagResolver.isEnabled(
                SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS);
        // Store (a copy of) all config and zen mode info prior to any changes taking effect
        ZenModeEventLogger.ZenModeInfo prevInfo = new ZenModeEventLogger.ZenModeInfo(
                mZenMode, mConfig, mConsolidatedPolicy);
        if (!config.equals(mConfig)) {
            // schedule broadcasts
            if (Flags.modesApi() && sendBroadcasts) {
                for (ZenRule rule : config.automaticRules.values()) {
                    ZenRule original = mConfig.automaticRules.get(rule.id);
                    if (original != null) {
                        if (original.enabled != rule.enabled) {
                            scheduleEnabledBroadcast(
                                    rule.getPkg(), config.user, rule.id, rule.enabled);
                        }
                        if (original.isAutomaticActive() != rule.isAutomaticActive()) {
                            scheduleActivationBroadcast(
                                    rule.getPkg(), config.user, rule.id, rule.isAutomaticActive());
                        }
                    }
                }
            }

            mConfig = config;
            dispatchOnConfigChanged();
            updateConsolidatedPolicy(reason);
+221 −0
Original line number Diff line number Diff line
@@ -17,6 +17,10 @@
package com.android.server.notification;

import static android.app.AutomaticZenRule.TYPE_BEDTIME;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DEACTIVATED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
@@ -141,6 +145,8 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
@@ -2694,6 +2700,221 @@ public class ZenModeHelperTest extends UiServiceTestCase {
        assertEquals(TRIGGER_DESC, actual.getTriggerDescription());
    }

    @Test
    public void testUpdateAutomaticRule_disabled_triggersBroadcast() throws Exception {
        setupZenConfig();

        // Add a new automatic zen rule that's enabled
        AutomaticZenRule zenRule = new AutomaticZenRule("name",
                null,
                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                null,
                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                zenRule, "test", Process.SYSTEM_UID, true);

        CountDownLatch latch = new CountDownLatch(1);
        final int[] actualStatus = new int[1];
        ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
            @Override
            void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
                if (Objects.equals(createdId, id)) {
                    actualStatus[0] = status;
                    latch.countDown();
                }
            }
        };
        mZenModeHelper.addCallback(callback);

        zenRule.setEnabled(false);
        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true);

        assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
        assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]);
    }

    @Test
    public void testUpdateAutomaticRule_enabled_triggersBroadcast() throws Exception {
        setupZenConfig();

        // Add a new automatic zen rule that's enabled
        AutomaticZenRule zenRule = new AutomaticZenRule("name",
                null,
                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                null,
                NotificationManager.INTERRUPTION_FILTER_PRIORITY, false);
        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                zenRule, "test", Process.SYSTEM_UID, true);

        CountDownLatch latch = new CountDownLatch(1);
        final int[] actualStatus = new int[1];
        ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
            @Override
            void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
                if (Objects.equals(createdId, id)) {
                    actualStatus[0] = status;
                    latch.countDown();
                }
            }
        };
        mZenModeHelper.addCallback(callback);

        zenRule.setEnabled(true);
        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true);

        assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
        assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]);
    }

    @Test
    public void testUpdateAutomaticRule_activated_triggersBroadcast() throws Exception {
        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
        setupZenConfig();

        // Add a new automatic zen rule that's enabled
        AutomaticZenRule zenRule = new AutomaticZenRule("name",
                null,
                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                null,
                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                zenRule, "test", Process.SYSTEM_UID, true);

        CountDownLatch latch = new CountDownLatch(1);
        final int[] actualStatus = new int[1];
        ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
            @Override
            void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
                if (Objects.equals(createdId, id)) {
                    actualStatus[0] = status;
                    latch.countDown();
                }
            }
        };
        mZenModeHelper.addCallback(callback);

        mZenModeHelper.setAutomaticZenRuleState(createdId,
                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
                Process.SYSTEM_UID, true);

        assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
        assertEquals(AUTOMATIC_RULE_STATUS_ACTIVATED, actualStatus[0]);
    }

    @Test
    public void testUpdateAutomaticRule_deactivatedByUser_triggersBroadcast() throws Exception {
        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
        setupZenConfig();

        // Add a new automatic zen rule that's enabled
        AutomaticZenRule zenRule = new AutomaticZenRule("name",
                null,
                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                null,
                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                zenRule, "test", Process.SYSTEM_UID, true);

        CountDownLatch latch = new CountDownLatch(1);
        final int[] actualStatus = new int[2];
        ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
            int i = 0;
            @Override
            void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
                if (Objects.equals(createdId, id)) {
                    actualStatus[i++] = status;
                    latch.countDown();
                }
            }
        };
        mZenModeHelper.addCallback(callback);

        mZenModeHelper.setAutomaticZenRuleState(createdId,
                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
                Process.SYSTEM_UID, true);

        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
                Process.SYSTEM_UID, true);

        assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
        assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
    }

    @Test
    public void testUpdateAutomaticRule_deactivatedByApp_triggersBroadcast() throws Exception {
        mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
        setupZenConfig();

        // Add a new automatic zen rule that's enabled
        AutomaticZenRule zenRule = new AutomaticZenRule("name",
                null,
                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                null,
                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                zenRule, "test", Process.SYSTEM_UID, true);

        CountDownLatch latch = new CountDownLatch(1);
        final int[] actualStatus = new int[2];
        ZenModeHelper.Callback callback = new ZenModeHelper.Callback() {
            int i = 0;
            @Override
            void onAutomaticRuleStatusChanged(int userId, String pkg, String id, int status) {
                if (Objects.equals(createdId, id)) {
                    actualStatus[i++] = status;
                    latch.countDown();
                }
            }
        };
        mZenModeHelper.addCallback(callback);

        mZenModeHelper.setAutomaticZenRuleState(createdId,
                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
                Process.SYSTEM_UID, true);

        mZenModeHelper.setAutomaticZenRuleState(createdId,
                new Condition(zenRule.getConditionId(), "", STATE_FALSE),
                Process.SYSTEM_UID, true);

        assertTrue(latch.await(500, TimeUnit.MILLISECONDS));
        assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]);
    }

    @Test
    public void testUpdateAutomaticRule_unsnoozes() throws IllegalArgumentException {
        setupZenConfig();

        // Add a new automatic zen rule that's enabled
        AutomaticZenRule zenRule = new AutomaticZenRule("name",
                null,
                new ComponentName(CUSTOM_PKG_NAME, "ScheduleConditionProvider"),
                ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
                null,
                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
        final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
                zenRule, "test", Process.SYSTEM_UID, true);

        // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE
        mZenModeHelper.setAutomaticZenRuleState(createdId,
                new Condition(zenRule.getConditionId(), "", STATE_TRUE),
                Process.SYSTEM_UID, true);

        // Event 2: Snooze rule by turning off DND
        mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "",
                Process.SYSTEM_UID, true);

        // Event 3: "User" turns off the automatic rule (sets it to not enabled)
        zenRule.setEnabled(false);
        mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, true);

        assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing);
    }

    private void setupZenConfig() {
        mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF;
        mZenModeHelper.mConfig.allowAlarms = false;