diff options
| author | 2023-11-10 14:18:38 +0000 | |
|---|---|---|
| committer | 2023-11-10 14:18:38 +0000 | |
| commit | c5af4e148cf844e122874b2a90701e3bda72930d (patch) | |
| tree | 162e14edef1e87edafcbbd73840bbe82a616f66f | |
| parent | e3ea9b5d8300d44a9cd1a82a6a8bc2702559dd09 (diff) | |
| parent | 3a404c5b96f51b32cf84bf68bd3d8b5d5416fca3 (diff) | |
Merge "Tell apps when their rules are de/activated." into main
4 files changed, 317 insertions, 11 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index d75aafb3fef1..7e14ae1612fa 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -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 diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index b8bea9d102e1..b0332c32bfbc 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -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 {} @@ -320,6 +323,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. * * <p>This broadcast is only sent to registered receivers and (starting from diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 9802adf302d1..762c1a162302 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -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 (rule.enabled != automaticZenRule.isEnabled()) { - dispatchOnAutomaticRuleStatusChanged(mConfig.user, rule.getPkg(), ruleId, - automaticZenRule.isEnabled() - ? AUTOMATIC_RULE_STATUS_ENABLED : AUTOMATIC_RULE_STATUS_DISABLED); + if (!Flags.modesApi()) { + if (rule.enabled != automaticZenRule.isEnabled()) { + dispatchOnAutomaticRuleStatusChanged(mConfig.user, rule.getPkg(), ruleId, + automaticZenRule.isEnabled() + ? 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); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 0349ad9e30c7..e8201fdef8de 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -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; |