Loading core/java/android/app/notification.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -82,6 +82,13 @@ flag { } } flag { name: "modes_cleanup_implicit" namespace: "systemui" description: "Deletes implicit modes if never customized and not used for some time. Depends on MODES_UI" bug: "394087495" } flag { name: "api_tvextender" is_exported: true Loading core/java/android/service/notification/ZenModeConfig.java +86 −24 Original line number Diff line number Diff line Loading @@ -309,6 +309,7 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_DISABLED_ORIGIN = "disabledOrigin"; private static final String RULE_ATT_LEGACY_SUPPRESSED_EFFECTS = "legacySuppressedEffects"; private static final String RULE_ATT_CONDITION_OVERRIDE = "conditionOverride"; private static final String RULE_ATT_LAST_ACTIVATION = "lastActivation"; private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale"; private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY = Loading Loading @@ -1187,11 +1188,7 @@ public class ZenModeConfig implements Parcelable { rt.zenPolicyUserModifiedFields = safeInt(parser, POLICY_USER_MODIFIED_FIELDS, 0); rt.zenDeviceEffectsUserModifiedFields = safeInt(parser, DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0); Long deletionInstant = tryParseLong( parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null); if (deletionInstant != null) { rt.deletionInstant = Instant.ofEpochMilli(deletionInstant); } rt.deletionInstant = safeInstant(parser, RULE_ATT_DELETION_INSTANT, null); if (Flags.modesUi()) { rt.disabledOrigin = safeInt(parser, RULE_ATT_DISABLED_ORIGIN, ORIGIN_UNKNOWN); Loading @@ -1199,6 +1196,9 @@ public class ZenModeConfig implements Parcelable { RULE_ATT_LEGACY_SUPPRESSED_EFFECTS, 0); rt.conditionOverride = safeInt(parser, RULE_ATT_CONDITION_OVERRIDE, ZenRule.OVERRIDE_NONE); if (Flags.modesCleanupImplicit()) { rt.lastActivation = safeInstant(parser, RULE_ATT_LAST_ACTIVATION, null); } } return rt; Loading Loading @@ -1249,10 +1249,7 @@ public class ZenModeConfig implements Parcelable { out.attributeInt(null, POLICY_USER_MODIFIED_FIELDS, rule.zenPolicyUserModifiedFields); out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS, rule.zenDeviceEffectsUserModifiedFields); if (rule.deletionInstant != null) { out.attributeLong(null, RULE_ATT_DELETION_INSTANT, rule.deletionInstant.toEpochMilli()); } writeXmlAttributeInstant(out, RULE_ATT_DELETION_INSTANT, rule.deletionInstant); if (Flags.modesUi()) { out.attributeInt(null, RULE_ATT_DISABLED_ORIGIN, rule.disabledOrigin); out.attributeInt(null, RULE_ATT_LEGACY_SUPPRESSED_EFFECTS, Loading @@ -1260,6 +1257,16 @@ public class ZenModeConfig implements Parcelable { if (rule.conditionOverride == ZenRule.OVERRIDE_ACTIVATE && !forBackup) { out.attributeInt(null, RULE_ATT_CONDITION_OVERRIDE, rule.conditionOverride); } if (Flags.modesCleanupImplicit()) { writeXmlAttributeInstant(out, RULE_ATT_LAST_ACTIVATION, rule.lastActivation); } } } private static void writeXmlAttributeInstant(TypedXmlSerializer out, String att, @Nullable Instant instant) throws IOException { if (instant != null) { out.attributeLong(null, att, instant.toEpochMilli()); } } Loading Loading @@ -1600,6 +1607,19 @@ public class ZenModeConfig implements Parcelable { return values; } @Nullable private static Instant safeInstant(TypedXmlPullParser parser, String att, @Nullable Instant defValue) { final String strValue = parser.getAttributeValue(null, att); if (!TextUtils.isEmpty(strValue)) { Long longValue = tryParseLong(strValue, null); if (longValue != null) { return Instant.ofEpochMilli(longValue); } } return defValue; } @Override public int describeContents() { return 0; Loading Loading @@ -2598,6 +2618,18 @@ public class ZenModeConfig implements Parcelable { @ConditionOverride int conditionOverride = OVERRIDE_NONE; /** * Last time at which the rule was activated (for any reason, including overrides). * If {@code null}, the rule has never been activated since its creation. * * <p>Note that this was previously untracked, so it will also be {@code null} for rules * created before we started tracking and never activated since -- make sure to account for * it, for example by falling back to {@link #creationTime} in logic involving this field. */ @Nullable @FlaggedApi(Flags.FLAG_MODES_CLEANUP_IMPLICIT) public Instant lastActivation; public ZenRule() { } public ZenRule(Parcel source) { Loading Loading @@ -2635,24 +2667,29 @@ public class ZenModeConfig implements Parcelable { disabledOrigin = source.readInt(); legacySuppressedEffects = source.readInt(); conditionOverride = source.readInt(); if (Flags.modesCleanupImplicit()) { if (source.readInt() == 1) { lastActivation = Instant.ofEpochMilli(source.readLong()); } } } } /** * Whether this ZenRule can be updated by an app. In general, rules that have been * customized by the user cannot be further updated by an app, with some exceptions: * Whether this ZenRule has been customized by the user in any way. * <p>In general, rules that have been customized by the user cannot be further updated by * an app, with some exceptions: * <ul> * <li>Non user-configurable fields, like type, icon, configurationActivity, etc. * <li>Name, if the name was not specifically modified by the user (to support language * switches). * </ul> */ public boolean canBeUpdatedByApp() { // The rule is considered updateable if its bitmask has no user modifications, and // the bitmasks of the policy and device effects have no modification. return userModifiedFields == 0 && zenPolicyUserModifiedFields == 0 && zenDeviceEffectsUserModifiedFields == 0; public boolean isUserModified() { return userModifiedFields != 0 || zenPolicyUserModifiedFields != 0 || zenDeviceEffectsUserModifiedFields != 0; } @Override Loading Loading @@ -2708,6 +2745,14 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(disabledOrigin); dest.writeInt(legacySuppressedEffects); dest.writeInt(conditionOverride); if (Flags.modesCleanupImplicit()) { if (lastActivation != null) { dest.writeInt(1); dest.writeLong(lastActivation.toEpochMilli()); } else { dest.writeInt(0); } } } } Loading Loading @@ -2760,6 +2805,9 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesUi()) { sb.append(",disabledOrigin=").append(disabledOrigin); sb.append(",legacySuppressedEffects=").append(legacySuppressedEffects); if (Flags.modesCleanupImplicit()) { sb.append(",lastActivation=").append(lastActivation); } } return sb.append(']').toString(); Loading Loading @@ -2838,6 +2886,10 @@ public class ZenModeConfig implements Parcelable { && other.disabledOrigin == disabledOrigin && other.legacySuppressedEffects == legacySuppressedEffects && other.conditionOverride == conditionOverride; if (Flags.modesCleanupImplicit()) { finalEquals = finalEquals && Objects.equals(other.lastActivation, lastActivation); } } return finalEquals; Loading @@ -2846,6 +2898,15 @@ public class ZenModeConfig implements Parcelable { @Override public int hashCode() { if (Flags.modesUi()) { if (Flags.modesCleanupImplicit()) { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, zenDeviceEffects, allowManualInvocation, iconResName, triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields, deletionInstant, disabledOrigin, legacySuppressedEffects, conditionOverride, lastActivation); } else { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, zenDeviceEffects, allowManualInvocation, iconResName, Loading @@ -2853,6 +2914,7 @@ public class ZenModeConfig implements Parcelable { zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields, deletionInstant, disabledOrigin, legacySuppressedEffects, conditionOverride); } } else { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, Loading services/core/java/com/android/server/notification/ZenModeHelper.java +51 −3 Original line number Diff line number Diff line Loading @@ -157,6 +157,12 @@ public class ZenModeHelper { static final int RULE_LIMIT_PER_PACKAGE = 100; private static final Duration DELETED_RULE_KEPT_FOR = Duration.ofDays(30); /** * Amount of time since last activation after which implicit rules that have never been * customized by the user are automatically cleaned up. */ private static final Duration IMPLICIT_RULE_KEPT_FOR = Duration.ofDays(30); private static final int MAX_ICON_RESOURCE_NAME_LENGTH = 1000; /** Loading Loading @@ -534,7 +540,7 @@ public class ZenModeHelper { ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); if (sleepingRule != null && !sleepingRule.enabled && sleepingRule.canBeUpdatedByApp() /* meaning it's not user-customized */) { && !sleepingRule.isUserModified()) { config.automaticRules.remove(ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); } } Loading Loading @@ -864,7 +870,7 @@ public class ZenModeHelper { // We don't try to preserve system-owned rules because their conditionIds (used as // deletedRuleKey) are not stable. This is almost moot anyway because an app cannot // delete a system-owned rule. if (origin == ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp() if (origin == ORIGIN_APP && ruleToRemove.isUserModified() && !PACKAGE_ANDROID.equals(ruleToRemove.pkg)) { String deletedKey = ZenModeConfig.deletedRuleKey(ruleToRemove); if (deletedKey != null) { Loading Loading @@ -1282,7 +1288,7 @@ public class ZenModeHelper { // * the request comes from an origin that can always update values, like the user, or // * the rule has not yet been user modified, and thus can be updated by the app. boolean updateValues = isNew || doesOriginAlwaysUpdateValues(origin) || rule.canBeUpdatedByApp(); || !rule.isUserModified(); // For all other values, if updates are not allowed, we discard the update. if (!updateValues) { Loading Loading @@ -1914,6 +1920,7 @@ public class ZenModeHelper { * <ul> * <li>Rule instances whose owner is not installed. * <li>Deleted rules that were deleted more than 30 days ago. * <li>Implicit rules that haven't been used in 30 days (and have not been customized). * </ul> */ private void cleanUpZenRules() { Loading @@ -1932,6 +1939,10 @@ public class ZenModeHelper { } } if (Flags.modesUi() && Flags.modesCleanupImplicit()) { deleteUnusedImplicitRules(newConfig.automaticRules); } if (!newConfig.equals(mConfig)) { setConfigLocked(newConfig, null, ORIGIN_SYSTEM, "cleanUpZenRules", Process.SYSTEM_UID); Loading @@ -1957,6 +1968,29 @@ public class ZenModeHelper { } } private void deleteUnusedImplicitRules(ArrayMap<String, ZenRule> ruleList) { if (ruleList == null) { return; } Instant deleteIfUnusedSince = mClock.instant().minus(IMPLICIT_RULE_KEPT_FOR); for (int i = ruleList.size() - 1; i >= 0; i--) { ZenRule rule = ruleList.valueAt(i); if (isImplicitRuleId(rule.id) && !rule.isUserModified()) { if (rule.lastActivation == null) { // This rule existed before we started tracking activation time. It *might* be // in use. Set lastActivation=now so it has some time (IMPLICIT_RULE_KEPT_FOR) // before being removed if truly unused. rule.lastActivation = mClock.instant(); } if (rule.lastActivation.isBefore(deleteIfUnusedSince)) { ruleList.removeAt(i); } } } } /** * @return a copy of the zen mode configuration */ Loading Loading @@ -2091,6 +2125,20 @@ public class ZenModeHelper { } } // Update last activation for rules that are being activated. if (Flags.modesUi() && Flags.modesCleanupImplicit()) { Instant now = mClock.instant(); if (!mConfig.isManualActive() && config.isManualActive()) { config.manualRule.lastActivation = now; } for (ZenRule rule : config.automaticRules.values()) { ZenRule previousRule = mConfig.automaticRules.get(rule.id); if (rule.isActive() && (previousRule == null || !previousRule.isActive())) { rule.lastActivation = now; } } } mConfig = config; dispatchOnConfigChanged(); updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason); Loading services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +26 −8 Original line number Diff line number Diff line Loading @@ -488,33 +488,33 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenPolicy = null; rule.zenDeviceEffects = null; assertThat(rule.canBeUpdatedByApp()).isTrue(); assertThat(rule.isUserModified()).isFalse(); rule.userModifiedFields = 1; assertThat(rule.canBeUpdatedByApp()).isFalse(); assertThat(rule.isUserModified()).isTrue(); } @Test public void testCanBeUpdatedByApp_policyModified() throws Exception { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenPolicy = new ZenPolicy(); assertThat(rule.canBeUpdatedByApp()).isTrue(); assertThat(rule.isUserModified()).isFalse(); rule.zenPolicyUserModifiedFields = 1; assertThat(rule.canBeUpdatedByApp()).isFalse(); assertThat(rule.isUserModified()).isTrue(); } @Test public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenDeviceEffects = new ZenDeviceEffects.Builder().build(); assertThat(rule.canBeUpdatedByApp()).isTrue(); assertThat(rule.isUserModified()).isFalse(); rule.zenDeviceEffectsUserModifiedFields = 1; assertThat(rule.canBeUpdatedByApp()).isFalse(); assertThat(rule.isUserModified()).isTrue(); } @Test Loading Loading @@ -563,6 +563,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); if (Flags.modesUi()) { rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; if (Flags.modesCleanupImplicit()) { rule.lastActivation = Instant.ofEpochMilli(456); } } config.automaticRules.put(rule.id, rule); Loading Loading @@ -600,6 +603,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.deletionInstant, ruleActual.deletionInstant); if (Flags.modesUi()) { assertEquals(rule.disabledOrigin, ruleActual.disabledOrigin); if (Flags.modesCleanupImplicit()) { assertEquals(rule.lastActivation, ruleActual.lastActivation); } } if (Flags.backupRestoreLogging()) { verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_RULES, 2); Loading Loading @@ -633,6 +639,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); if (Flags.modesUi()) { rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; if (Flags.modesCleanupImplicit()) { rule.lastActivation = Instant.ofEpochMilli(789); } } Parcel parcel = Parcel.obtain(); Loading Loading @@ -664,6 +673,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.deletionInstant, parceled.deletionInstant); if (Flags.modesUi()) { assertEquals(rule.disabledOrigin, parceled.disabledOrigin); if (Flags.modesCleanupImplicit()) { assertEquals(rule.lastActivation, parceled.lastActivation); } } assertEquals(rule, parceled); Loading Loading @@ -746,6 +758,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); if (Flags.modesUi()) { rule.disabledOrigin = ZenModeConfig.ORIGIN_APP; if (Flags.modesCleanupImplicit()) { rule.lastActivation = Instant.ofEpochMilli(123); } } ByteArrayOutputStream baos = new ByteArrayOutputStream(); Loading Loading @@ -781,6 +796,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.deletionInstant, fromXml.deletionInstant); if (Flags.modesUi()) { assertEquals(rule.disabledOrigin, fromXml.disabledOrigin); if (Flags.modesCleanupImplicit()) { assertEquals(rule.lastActivation, fromXml.lastActivation); } } } Loading Loading @@ -908,7 +926,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME; assertThat(rule.userModifiedFields).isEqualTo(1); assertThat(rule.canBeUpdatedByApp()).isFalse(); assertThat(rule.isUserModified()).isTrue(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeRuleXml(rule, baos); Loading @@ -916,7 +934,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule fromXml = readRuleXml(bais); assertThat(fromXml.userModifiedFields).isEqualTo(rule.userModifiedFields); assertThat(fromXml.canBeUpdatedByApp()).isFalse(); assertThat(fromXml.isUserModified()).isTrue(); } @Test Loading services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -475,7 +475,8 @@ public class ZenModeDiffTest extends UiServiceTestCase { // "Metadata" fields are never compared. Set<String> exemptFields = new LinkedHashSet<>( Set.of("userModifiedFields", "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields", "deletionInstant", "disabledOrigin")); "zenDeviceEffectsUserModifiedFields", "deletionInstant", "disabledOrigin", "lastActivation")); // Flagged fields are only compared if their flag is on. if (Flags.modesUi()) { exemptFields.add(RuleDiff.FIELD_SNOOZING); // Obsolete. Loading Loading
core/java/android/app/notification.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -82,6 +82,13 @@ flag { } } flag { name: "modes_cleanup_implicit" namespace: "systemui" description: "Deletes implicit modes if never customized and not used for some time. Depends on MODES_UI" bug: "394087495" } flag { name: "api_tvextender" is_exported: true Loading
core/java/android/service/notification/ZenModeConfig.java +86 −24 Original line number Diff line number Diff line Loading @@ -309,6 +309,7 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_DISABLED_ORIGIN = "disabledOrigin"; private static final String RULE_ATT_LEGACY_SUPPRESSED_EFFECTS = "legacySuppressedEffects"; private static final String RULE_ATT_CONDITION_OVERRIDE = "conditionOverride"; private static final String RULE_ATT_LAST_ACTIVATION = "lastActivation"; private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale"; private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY = Loading Loading @@ -1187,11 +1188,7 @@ public class ZenModeConfig implements Parcelable { rt.zenPolicyUserModifiedFields = safeInt(parser, POLICY_USER_MODIFIED_FIELDS, 0); rt.zenDeviceEffectsUserModifiedFields = safeInt(parser, DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0); Long deletionInstant = tryParseLong( parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null); if (deletionInstant != null) { rt.deletionInstant = Instant.ofEpochMilli(deletionInstant); } rt.deletionInstant = safeInstant(parser, RULE_ATT_DELETION_INSTANT, null); if (Flags.modesUi()) { rt.disabledOrigin = safeInt(parser, RULE_ATT_DISABLED_ORIGIN, ORIGIN_UNKNOWN); Loading @@ -1199,6 +1196,9 @@ public class ZenModeConfig implements Parcelable { RULE_ATT_LEGACY_SUPPRESSED_EFFECTS, 0); rt.conditionOverride = safeInt(parser, RULE_ATT_CONDITION_OVERRIDE, ZenRule.OVERRIDE_NONE); if (Flags.modesCleanupImplicit()) { rt.lastActivation = safeInstant(parser, RULE_ATT_LAST_ACTIVATION, null); } } return rt; Loading Loading @@ -1249,10 +1249,7 @@ public class ZenModeConfig implements Parcelable { out.attributeInt(null, POLICY_USER_MODIFIED_FIELDS, rule.zenPolicyUserModifiedFields); out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS, rule.zenDeviceEffectsUserModifiedFields); if (rule.deletionInstant != null) { out.attributeLong(null, RULE_ATT_DELETION_INSTANT, rule.deletionInstant.toEpochMilli()); } writeXmlAttributeInstant(out, RULE_ATT_DELETION_INSTANT, rule.deletionInstant); if (Flags.modesUi()) { out.attributeInt(null, RULE_ATT_DISABLED_ORIGIN, rule.disabledOrigin); out.attributeInt(null, RULE_ATT_LEGACY_SUPPRESSED_EFFECTS, Loading @@ -1260,6 +1257,16 @@ public class ZenModeConfig implements Parcelable { if (rule.conditionOverride == ZenRule.OVERRIDE_ACTIVATE && !forBackup) { out.attributeInt(null, RULE_ATT_CONDITION_OVERRIDE, rule.conditionOverride); } if (Flags.modesCleanupImplicit()) { writeXmlAttributeInstant(out, RULE_ATT_LAST_ACTIVATION, rule.lastActivation); } } } private static void writeXmlAttributeInstant(TypedXmlSerializer out, String att, @Nullable Instant instant) throws IOException { if (instant != null) { out.attributeLong(null, att, instant.toEpochMilli()); } } Loading Loading @@ -1600,6 +1607,19 @@ public class ZenModeConfig implements Parcelable { return values; } @Nullable private static Instant safeInstant(TypedXmlPullParser parser, String att, @Nullable Instant defValue) { final String strValue = parser.getAttributeValue(null, att); if (!TextUtils.isEmpty(strValue)) { Long longValue = tryParseLong(strValue, null); if (longValue != null) { return Instant.ofEpochMilli(longValue); } } return defValue; } @Override public int describeContents() { return 0; Loading Loading @@ -2598,6 +2618,18 @@ public class ZenModeConfig implements Parcelable { @ConditionOverride int conditionOverride = OVERRIDE_NONE; /** * Last time at which the rule was activated (for any reason, including overrides). * If {@code null}, the rule has never been activated since its creation. * * <p>Note that this was previously untracked, so it will also be {@code null} for rules * created before we started tracking and never activated since -- make sure to account for * it, for example by falling back to {@link #creationTime} in logic involving this field. */ @Nullable @FlaggedApi(Flags.FLAG_MODES_CLEANUP_IMPLICIT) public Instant lastActivation; public ZenRule() { } public ZenRule(Parcel source) { Loading Loading @@ -2635,24 +2667,29 @@ public class ZenModeConfig implements Parcelable { disabledOrigin = source.readInt(); legacySuppressedEffects = source.readInt(); conditionOverride = source.readInt(); if (Flags.modesCleanupImplicit()) { if (source.readInt() == 1) { lastActivation = Instant.ofEpochMilli(source.readLong()); } } } } /** * Whether this ZenRule can be updated by an app. In general, rules that have been * customized by the user cannot be further updated by an app, with some exceptions: * Whether this ZenRule has been customized by the user in any way. * <p>In general, rules that have been customized by the user cannot be further updated by * an app, with some exceptions: * <ul> * <li>Non user-configurable fields, like type, icon, configurationActivity, etc. * <li>Name, if the name was not specifically modified by the user (to support language * switches). * </ul> */ public boolean canBeUpdatedByApp() { // The rule is considered updateable if its bitmask has no user modifications, and // the bitmasks of the policy and device effects have no modification. return userModifiedFields == 0 && zenPolicyUserModifiedFields == 0 && zenDeviceEffectsUserModifiedFields == 0; public boolean isUserModified() { return userModifiedFields != 0 || zenPolicyUserModifiedFields != 0 || zenDeviceEffectsUserModifiedFields != 0; } @Override Loading Loading @@ -2708,6 +2745,14 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(disabledOrigin); dest.writeInt(legacySuppressedEffects); dest.writeInt(conditionOverride); if (Flags.modesCleanupImplicit()) { if (lastActivation != null) { dest.writeInt(1); dest.writeLong(lastActivation.toEpochMilli()); } else { dest.writeInt(0); } } } } Loading Loading @@ -2760,6 +2805,9 @@ public class ZenModeConfig implements Parcelable { if (Flags.modesUi()) { sb.append(",disabledOrigin=").append(disabledOrigin); sb.append(",legacySuppressedEffects=").append(legacySuppressedEffects); if (Flags.modesCleanupImplicit()) { sb.append(",lastActivation=").append(lastActivation); } } return sb.append(']').toString(); Loading Loading @@ -2838,6 +2886,10 @@ public class ZenModeConfig implements Parcelable { && other.disabledOrigin == disabledOrigin && other.legacySuppressedEffects == legacySuppressedEffects && other.conditionOverride == conditionOverride; if (Flags.modesCleanupImplicit()) { finalEquals = finalEquals && Objects.equals(other.lastActivation, lastActivation); } } return finalEquals; Loading @@ -2846,6 +2898,15 @@ public class ZenModeConfig implements Parcelable { @Override public int hashCode() { if (Flags.modesUi()) { if (Flags.modesCleanupImplicit()) { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, zenDeviceEffects, allowManualInvocation, iconResName, triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields, deletionInstant, disabledOrigin, legacySuppressedEffects, conditionOverride, lastActivation); } else { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, zenDeviceEffects, allowManualInvocation, iconResName, Loading @@ -2853,6 +2914,7 @@ public class ZenModeConfig implements Parcelable { zenPolicyUserModifiedFields, zenDeviceEffectsUserModifiedFields, deletionInstant, disabledOrigin, legacySuppressedEffects, conditionOverride); } } else { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, Loading
services/core/java/com/android/server/notification/ZenModeHelper.java +51 −3 Original line number Diff line number Diff line Loading @@ -157,6 +157,12 @@ public class ZenModeHelper { static final int RULE_LIMIT_PER_PACKAGE = 100; private static final Duration DELETED_RULE_KEPT_FOR = Duration.ofDays(30); /** * Amount of time since last activation after which implicit rules that have never been * customized by the user are automatically cleaned up. */ private static final Duration IMPLICIT_RULE_KEPT_FOR = Duration.ofDays(30); private static final int MAX_ICON_RESOURCE_NAME_LENGTH = 1000; /** Loading Loading @@ -534,7 +540,7 @@ public class ZenModeHelper { ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); if (sleepingRule != null && !sleepingRule.enabled && sleepingRule.canBeUpdatedByApp() /* meaning it's not user-customized */) { && !sleepingRule.isUserModified()) { config.automaticRules.remove(ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID); } } Loading Loading @@ -864,7 +870,7 @@ public class ZenModeHelper { // We don't try to preserve system-owned rules because their conditionIds (used as // deletedRuleKey) are not stable. This is almost moot anyway because an app cannot // delete a system-owned rule. if (origin == ORIGIN_APP && !ruleToRemove.canBeUpdatedByApp() if (origin == ORIGIN_APP && ruleToRemove.isUserModified() && !PACKAGE_ANDROID.equals(ruleToRemove.pkg)) { String deletedKey = ZenModeConfig.deletedRuleKey(ruleToRemove); if (deletedKey != null) { Loading Loading @@ -1282,7 +1288,7 @@ public class ZenModeHelper { // * the request comes from an origin that can always update values, like the user, or // * the rule has not yet been user modified, and thus can be updated by the app. boolean updateValues = isNew || doesOriginAlwaysUpdateValues(origin) || rule.canBeUpdatedByApp(); || !rule.isUserModified(); // For all other values, if updates are not allowed, we discard the update. if (!updateValues) { Loading Loading @@ -1914,6 +1920,7 @@ public class ZenModeHelper { * <ul> * <li>Rule instances whose owner is not installed. * <li>Deleted rules that were deleted more than 30 days ago. * <li>Implicit rules that haven't been used in 30 days (and have not been customized). * </ul> */ private void cleanUpZenRules() { Loading @@ -1932,6 +1939,10 @@ public class ZenModeHelper { } } if (Flags.modesUi() && Flags.modesCleanupImplicit()) { deleteUnusedImplicitRules(newConfig.automaticRules); } if (!newConfig.equals(mConfig)) { setConfigLocked(newConfig, null, ORIGIN_SYSTEM, "cleanUpZenRules", Process.SYSTEM_UID); Loading @@ -1957,6 +1968,29 @@ public class ZenModeHelper { } } private void deleteUnusedImplicitRules(ArrayMap<String, ZenRule> ruleList) { if (ruleList == null) { return; } Instant deleteIfUnusedSince = mClock.instant().minus(IMPLICIT_RULE_KEPT_FOR); for (int i = ruleList.size() - 1; i >= 0; i--) { ZenRule rule = ruleList.valueAt(i); if (isImplicitRuleId(rule.id) && !rule.isUserModified()) { if (rule.lastActivation == null) { // This rule existed before we started tracking activation time. It *might* be // in use. Set lastActivation=now so it has some time (IMPLICIT_RULE_KEPT_FOR) // before being removed if truly unused. rule.lastActivation = mClock.instant(); } if (rule.lastActivation.isBefore(deleteIfUnusedSince)) { ruleList.removeAt(i); } } } } /** * @return a copy of the zen mode configuration */ Loading Loading @@ -2091,6 +2125,20 @@ public class ZenModeHelper { } } // Update last activation for rules that are being activated. if (Flags.modesUi() && Flags.modesCleanupImplicit()) { Instant now = mClock.instant(); if (!mConfig.isManualActive() && config.isManualActive()) { config.manualRule.lastActivation = now; } for (ZenRule rule : config.automaticRules.values()) { ZenRule previousRule = mConfig.automaticRules.get(rule.id); if (rule.isActive() && (previousRule == null || !previousRule.isActive())) { rule.lastActivation = now; } } } mConfig = config; dispatchOnConfigChanged(); updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason); Loading
services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +26 −8 Original line number Diff line number Diff line Loading @@ -488,33 +488,33 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenPolicy = null; rule.zenDeviceEffects = null; assertThat(rule.canBeUpdatedByApp()).isTrue(); assertThat(rule.isUserModified()).isFalse(); rule.userModifiedFields = 1; assertThat(rule.canBeUpdatedByApp()).isFalse(); assertThat(rule.isUserModified()).isTrue(); } @Test public void testCanBeUpdatedByApp_policyModified() throws Exception { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenPolicy = new ZenPolicy(); assertThat(rule.canBeUpdatedByApp()).isTrue(); assertThat(rule.isUserModified()).isFalse(); rule.zenPolicyUserModifiedFields = 1; assertThat(rule.canBeUpdatedByApp()).isFalse(); assertThat(rule.isUserModified()).isTrue(); } @Test public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.zenDeviceEffects = new ZenDeviceEffects.Builder().build(); assertThat(rule.canBeUpdatedByApp()).isTrue(); assertThat(rule.isUserModified()).isFalse(); rule.zenDeviceEffectsUserModifiedFields = 1; assertThat(rule.canBeUpdatedByApp()).isFalse(); assertThat(rule.isUserModified()).isTrue(); } @Test Loading Loading @@ -563,6 +563,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); if (Flags.modesUi()) { rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; if (Flags.modesCleanupImplicit()) { rule.lastActivation = Instant.ofEpochMilli(456); } } config.automaticRules.put(rule.id, rule); Loading Loading @@ -600,6 +603,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.deletionInstant, ruleActual.deletionInstant); if (Flags.modesUi()) { assertEquals(rule.disabledOrigin, ruleActual.disabledOrigin); if (Flags.modesCleanupImplicit()) { assertEquals(rule.lastActivation, ruleActual.lastActivation); } } if (Flags.backupRestoreLogging()) { verify(logger).logItemsBackedUp(DATA_TYPE_ZEN_RULES, 2); Loading Loading @@ -633,6 +639,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); if (Flags.modesUi()) { rule.disabledOrigin = ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI; if (Flags.modesCleanupImplicit()) { rule.lastActivation = Instant.ofEpochMilli(789); } } Parcel parcel = Parcel.obtain(); Loading Loading @@ -664,6 +673,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.deletionInstant, parceled.deletionInstant); if (Flags.modesUi()) { assertEquals(rule.disabledOrigin, parceled.disabledOrigin); if (Flags.modesCleanupImplicit()) { assertEquals(rule.lastActivation, parceled.lastActivation); } } assertEquals(rule, parceled); Loading Loading @@ -746,6 +758,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); if (Flags.modesUi()) { rule.disabledOrigin = ZenModeConfig.ORIGIN_APP; if (Flags.modesCleanupImplicit()) { rule.lastActivation = Instant.ofEpochMilli(123); } } ByteArrayOutputStream baos = new ByteArrayOutputStream(); Loading Loading @@ -781,6 +796,9 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.deletionInstant, fromXml.deletionInstant); if (Flags.modesUi()) { assertEquals(rule.disabledOrigin, fromXml.disabledOrigin); if (Flags.modesCleanupImplicit()) { assertEquals(rule.lastActivation, fromXml.lastActivation); } } } Loading Loading @@ -908,7 +926,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.userModifiedFields |= AutomaticZenRule.FIELD_NAME; assertThat(rule.userModifiedFields).isEqualTo(1); assertThat(rule.canBeUpdatedByApp()).isFalse(); assertThat(rule.isUserModified()).isTrue(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeRuleXml(rule, baos); Loading @@ -916,7 +934,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { ZenModeConfig.ZenRule fromXml = readRuleXml(bais); assertThat(fromXml.userModifiedFields).isEqualTo(rule.userModifiedFields); assertThat(fromXml.canBeUpdatedByApp()).isFalse(); assertThat(fromXml.isUserModified()).isTrue(); } @Test Loading
services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -475,7 +475,8 @@ public class ZenModeDiffTest extends UiServiceTestCase { // "Metadata" fields are never compared. Set<String> exemptFields = new LinkedHashSet<>( Set.of("userModifiedFields", "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields", "deletionInstant", "disabledOrigin")); "zenDeviceEffectsUserModifiedFields", "deletionInstant", "disabledOrigin", "lastActivation")); // Flagged fields are only compared if their flag is on. if (Flags.modesUi()) { exemptFields.add(RuleDiff.FIELD_SNOOZING); // Obsolete. Loading