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

Commit 387b32ce authored by Matías Hernández's avatar Matías Hernández Committed by Android (Google) Code Review
Browse files

Merge "Remove long unused (and non-user-modified) implicit ZenRules" into main

parents c26379f2 dfce623b
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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
+86 −24
Original line number Diff line number Diff line
@@ -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 =
@@ -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);
@@ -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;
@@ -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,
@@ -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());
        }
    }

@@ -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;
@@ -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) {
@@ -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
@@ -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);
                    }
                }
            }
        }

@@ -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();
@@ -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;
@@ -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,
@@ -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,
+51 −3
Original line number Diff line number Diff line
@@ -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;

    /**
@@ -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);
            }
        }
@@ -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) {
@@ -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) {
@@ -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() {
@@ -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);
@@ -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
     */
@@ -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);
+26 −8
Original line number Diff line number Diff line
@@ -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
@@ -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);

@@ -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);
@@ -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();
@@ -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);
@@ -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();
@@ -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);
            }
        }
    }

@@ -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);
@@ -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
+2 −1
Original line number Diff line number Diff line
@@ -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