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

Commit 6480b8b7 authored by Matías Hernández's avatar Matías Hernández
Browse files

Preserve user customization when an app deletes and recreates an AutomaticZenRule

Test: atest ZenModeHelperTest
Bug: 308672001
Change-Id: Iccbef610c50a062f627fdedca54881b8fd0264ed
parent 65403b12
Loading
Loading
Loading
Loading
+113 −36
Original line number Diff line number Diff line
@@ -64,6 +64,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.time.Instant;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
@@ -233,6 +234,7 @@ public class ZenModeConfig implements Parcelable {

    private static final String MANUAL_TAG = "manual";
    private static final String AUTOMATIC_TAG = "automatic";
    private static final String AUTOMATIC_DELETED_TAG = "deleted";

    private static final String RULE_ATT_ID = "ruleId";
    private static final String RULE_ATT_ENABLED = "enabled";
@@ -251,6 +253,7 @@ public class ZenModeConfig implements Parcelable {
    private static final String RULE_ATT_USER_MODIFIED_FIELDS = "userModifiedFields";
    private static final String RULE_ATT_ICON = "rule_icon";
    private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc";
    private static final String RULE_ATT_DELETION_INSTANT = "deletionInstant";

    private static final String DEVICE_EFFECT_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale";
    private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY =
@@ -292,6 +295,10 @@ public class ZenModeConfig implements Parcelable {
    @UnsupportedAppUsage
    public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>();

    // Note: Map is *pkg|conditionId* (see deletedRuleKey()) -> ZenRule,
    // unlike automaticRules (which is id -> rule).
    public final ArrayMap<String, ZenRule> deletedRules = new ArrayMap<>();

    @UnsupportedAppUsage
    public ZenModeConfig() {
    }
@@ -306,15 +313,9 @@ public class ZenModeConfig implements Parcelable {
        allowMessagesFrom = source.readInt();
        user = source.readInt();
        manualRule = source.readParcelable(null, ZenRule.class);
        final int len = source.readInt();
        if (len > 0) {
            final String[] ids = new String[len];
            final ZenRule[] rules = new ZenRule[len];
            source.readStringArray(ids);
            source.readTypedArray(rules, ZenRule.CREATOR);
            for (int i = 0; i < len; i++) {
                automaticRules.put(ids[i], rules[i]);
            }
        readRulesFromParcel(automaticRules, source);
        if (Flags.modesApi()) {
            readRulesFromParcel(deletedRules, source);
        }
        allowAlarms = source.readInt() == 1;
        allowMedia = source.readInt() == 1;
@@ -328,6 +329,19 @@ public class ZenModeConfig implements Parcelable {
        }
    }

    private static void readRulesFromParcel(ArrayMap<String, ZenRule> ruleMap, Parcel source) {
        final int len = source.readInt();
        if (len > 0) {
            final String[] ids = new String[len];
            final ZenRule[] rules = new ZenRule[len];
            source.readStringArray(ids);
            source.readTypedArray(rules, ZenRule.CREATOR);
            for (int i = 0; i < len; i++) {
                ruleMap.put(ids[i], rules[i]);
            }
        }
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(allowCalls ? 1 : 0);
@@ -339,19 +353,9 @@ public class ZenModeConfig implements Parcelable {
        dest.writeInt(allowMessagesFrom);
        dest.writeInt(user);
        dest.writeParcelable(manualRule, 0);
        if (!automaticRules.isEmpty()) {
            final int len = automaticRules.size();
            final String[] ids = new String[len];
            final ZenRule[] rules = new ZenRule[len];
            for (int i = 0; i < len; i++) {
                ids[i] = automaticRules.keyAt(i);
                rules[i] = automaticRules.valueAt(i);
            }
            dest.writeInt(len);
            dest.writeStringArray(ids);
            dest.writeTypedArray(rules, 0);
        } else {
            dest.writeInt(0);
        writeRulesToParcel(automaticRules, dest);
        if (Flags.modesApi()) {
            writeRulesToParcel(deletedRules, dest);
        }
        dest.writeInt(allowAlarms ? 1 : 0);
        dest.writeInt(allowMedia ? 1 : 0);
@@ -365,6 +369,23 @@ public class ZenModeConfig implements Parcelable {
        }
    }

    private static void writeRulesToParcel(ArrayMap<String, ZenRule> ruleMap, Parcel dest) {
        if (!ruleMap.isEmpty()) {
            final int len = ruleMap.size();
            final String[] ids = new String[len];
            final ZenRule[] rules = new ZenRule[len];
            for (int i = 0; i < len; i++) {
                ids[i] = ruleMap.keyAt(i);
                rules[i] = ruleMap.valueAt(i);
            }
            dest.writeInt(len);
            dest.writeStringArray(ids);
            dest.writeTypedArray(rules, 0);
        } else {
            dest.writeInt(0);
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[')
@@ -389,23 +410,26 @@ public class ZenModeConfig implements Parcelable {
        } else {
            sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd);
        }
        return sb.append(",\nautomaticRules=").append(rulesToString())
                .append(",\nmanualRule=").append(manualRule)
                .append(']').toString();
        sb.append(",\nautomaticRules=").append(rulesToString(automaticRules))
                .append(",\nmanualRule=").append(manualRule);
        if (Flags.modesApi()) {
            sb.append(",\ndeletedRules=").append(rulesToString(deletedRules));
        }
        return sb.append(']').toString();
    }

    private String rulesToString() {
        if (automaticRules.isEmpty()) {
    private static String rulesToString(ArrayMap<String, ZenRule> ruleList) {
        if (ruleList.isEmpty()) {
            return "{}";
        }

        StringBuilder buffer = new StringBuilder(automaticRules.size() * 28);
        StringBuilder buffer = new StringBuilder(ruleList.size() * 28);
        buffer.append("{\n");
        for (int i = 0; i < automaticRules.size(); i++) {
        for (int i = 0; i < ruleList.size(); i++) {
            if (i > 0) {
                buffer.append(",\n");
            }
            Object value = automaticRules.valueAt(i);
            Object value = ruleList.valueAt(i);
            buffer.append(value);
        }
        buffer.append('}');
@@ -487,7 +511,9 @@ public class ZenModeConfig implements Parcelable {
                && other.allowConversations == allowConversations
                && other.allowConversationsFrom == allowConversationsFrom;
        if (Flags.modesApi()) {
            return eq && other.allowPriorityChannels == allowPriorityChannels;
            return eq
                    && Objects.equals(other.deletedRules, deletedRules)
                    && other.allowPriorityChannels == allowPriorityChannels;
        }
        return eq;
    }
@@ -644,13 +670,21 @@ public class ZenModeConfig implements Parcelable {
                            DEFAULT_SUPPRESSED_VISUAL_EFFECTS);
                } else if (MANUAL_TAG.equals(tag)) {
                    rt.manualRule = readRuleXml(parser);
                } else if (AUTOMATIC_TAG.equals(tag)) {
                } else if (AUTOMATIC_TAG.equals(tag)
                        || (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag))) {
                    final String id = parser.getAttributeValue(null, RULE_ATT_ID);
                    final ZenRule automaticRule = readRuleXml(parser);
                    if (id != null && automaticRule != null) {
                        automaticRule.id = id;
                        if (Flags.modesApi() && AUTOMATIC_DELETED_TAG.equals(tag)) {
                            String deletedRuleKey = deletedRuleKey(automaticRule);
                            if (deletedRuleKey != null) {
                                rt.deletedRules.put(deletedRuleKey, automaticRule);
                            }
                        } else if (AUTOMATIC_TAG.equals(tag)) {
                            rt.automaticRules.put(id, automaticRule);
                        }
                    }
                } else if (STATE_TAG.equals(tag)) {
                    rt.areChannelsBypassingDnd = safeBoolean(parser,
                            STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND);
@@ -660,13 +694,24 @@ public class ZenModeConfig implements Parcelable {
        throw new IllegalStateException("Failed to reach END_DOCUMENT");
    }

    /** Generates the map key used for a {@link ZenRule} in {@link #deletedRules}. */
    @Nullable
    public static String deletedRuleKey(ZenRule rule) {
        if (rule.pkg != null && rule.conditionId != null) {
            return rule.pkg + "|" + rule.conditionId.toString();
        } else {
            return null;
        }
    }

    /**
     * Writes XML of current ZenModeConfig
     * @param out serializer
     * @param version uses XML_VERSION if version is null
     * @throws IOException
     */
    public void writeXml(TypedXmlSerializer out, Integer version) throws IOException {
    public void writeXml(TypedXmlSerializer out, Integer version, boolean forBackup)
            throws IOException {
        out.startTag(null, ZEN_TAG);
        out.attribute(null, ZEN_ATT_VERSION, version == null
                ? Integer.toString(XML_VERSION) : Integer.toString(version));
@@ -707,6 +752,15 @@ public class ZenModeConfig implements Parcelable {
            writeRuleXml(automaticRule, out);
            out.endTag(null, AUTOMATIC_TAG);
        }
        if (Flags.modesApi() && !forBackup) {
            for (int i = 0; i < deletedRules.size(); i++) {
                final ZenRule deletedRule = deletedRules.valueAt(i);
                out.startTag(null, AUTOMATIC_DELETED_TAG);
                out.attribute(null, RULE_ATT_ID, deletedRule.id);
                writeRuleXml(deletedRule, out);
                out.endTag(null, AUTOMATIC_DELETED_TAG);
            }
        }

        out.startTag(null, STATE_TAG);
        out.attributeBoolean(null, STATE_ATT_CHANNELS_BYPASSING_DND, areChannelsBypassingDnd);
@@ -752,6 +806,11 @@ public class ZenModeConfig implements Parcelable {
            rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC);
            rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN);
            rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0);
            Long deletionInstant = tryParseLong(
                    parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null);
            if (deletionInstant != null) {
                rt.deletionInstant = Instant.ofEpochMilli(deletionInstant);
            }
        }
        return rt;
    }
@@ -799,6 +858,10 @@ public class ZenModeConfig implements Parcelable {
            }
            out.attributeInt(null, RULE_ATT_TYPE, rule.type);
            out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields);
            if (rule.deletionInstant != null) {
                out.attributeLong(null, RULE_ATT_DELETION_INSTANT,
                        rule.deletionInstant.toEpochMilli());
            }
        }
    }

@@ -1998,6 +2061,7 @@ public class ZenModeConfig implements Parcelable {
        public String iconResName;
        public boolean allowManualInvocation;
        public int userModifiedFields;
        @Nullable public Instant deletionInstant; // Only set on deleted rules.

        public ZenRule() { }

@@ -2031,6 +2095,9 @@ public class ZenModeConfig implements Parcelable {
                triggerDescription = source.readString();
                type = source.readInt();
                userModifiedFields = source.readInt();
                if (source.readInt() == 1) {
                    deletionInstant = Instant.ofEpochMilli(source.readLong());
                }
            }
        }

@@ -2091,6 +2158,12 @@ public class ZenModeConfig implements Parcelable {
                dest.writeString(triggerDescription);
                dest.writeInt(type);
                dest.writeInt(userModifiedFields);
                if (deletionInstant != null) {
                    dest.writeInt(1);
                    dest.writeLong(deletionInstant.toEpochMilli());
                } else {
                    dest.writeInt(0);
                }
            }
        }

@@ -2121,6 +2194,9 @@ public class ZenModeConfig implements Parcelable {
                        .append(",triggerDescription=").append(triggerDescription)
                        .append(",type=").append(type)
                        .append(",userModifiedFields=").append(userModifiedFields);
                if (deletionInstant != null) {
                    sb.append(",deletionInstant=").append(deletionInstant);
                }
            }

            return sb.append(']').toString();
@@ -2180,7 +2256,8 @@ public class ZenModeConfig implements Parcelable {
                        && Objects.equals(other.iconResName, iconResName)
                        && Objects.equals(other.triggerDescription, triggerDescription)
                        && other.type == type
                        && other.userModifiedFields == userModifiedFields;
                        && other.userModifiedFields == userModifiedFields
                        && Objects.equals(other.deletionInstant, deletionInstant);
            }

            return finalEquals;
@@ -2192,7 +2269,7 @@ public class ZenModeConfig implements Parcelable {
                return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                        component, configurationActivity, pkg, id, enabler, zenPolicy,
                        zenDeviceEffects, modified, allowManualInvocation, iconResName,
                        triggerDescription, type, userModifiedFields);
                        triggerDescription, type, userModifiedFields, deletionInstant);
            }
            return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
                    component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
+5 −5
Original line number Diff line number Diff line
@@ -30,6 +30,11 @@ import java.util.Set;
/**
 * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
 * subcomponents (automatic and manual ZenRules).
 *
 * <p>Note that this class is intended to detect <em>meaningful</em> differences, so objects that
 * are not identical (as per their {@code equals()} implementation) can still produce an empty diff
 * if only "metadata" fields are updated.
 *
 * @hide
 */
public class ZenModeDiff {
@@ -467,7 +472,6 @@ public class ZenModeDiff {
        public static final String FIELD_ICON_RES = "iconResName";
        public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription";
        public static final String FIELD_TYPE = "type";
        public static final String FIELD_USER_MODIFIED_FIELDS = "userModifiedFields";
        // NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule

        // Special field to track whether this rule became active or inactive
@@ -563,10 +567,6 @@ public class ZenModeDiff {
                if (!Objects.equals(from.iconResName, to.iconResName)) {
                    addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResName, to.iconResName));
                }
                if (from.userModifiedFields != to.userModifiedFields) {
                    addField(FIELD_USER_MODIFIED_FIELDS,
                            new FieldDiff<>(from.userModifiedFields, to.userModifiedFields));
                }
            }
        }

+3 −3
Original line number Diff line number Diff line
@@ -215,7 +215,6 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
@@ -373,6 +372,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -2466,8 +2466,8 @@ public class NotificationManagerService extends SystemService {
        mMetricsLogger = new MetricsLogger();
        mRankingHandler = rankingHandler;
        mConditionProviders = conditionProviders;
        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders,
                flagResolver, new ZenModeEventLogger(mPackageManagerClient));
        mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), Clock.systemUTC(),
                mConditionProviders, flagResolver, new ZenModeEventLogger(mPackageManagerClient));
        mZenModeHelper.addCallback(new ZenModeHelper.Callback() {
            @Override
            public void onConfigChanged() {
+125 −21

File changed.

Preview size limit exceeded, changes collapsed.

+6 −1
Original line number Diff line number Diff line
@@ -60,6 +60,7 @@ import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.time.Instant;

@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -407,6 +408,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
        rule.userModifiedFields = 16;
        rule.iconResName = ICON_RES_NAME;
        rule.triggerDescription = TRIGGER_DESC;
        rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);

        Parcel parcel = Parcel.obtain();
        rule.writeToParcel(parcel, 0);
@@ -432,9 +434,10 @@ public class ZenModeConfigTest extends UiServiceTestCase {
        assertEquals(rule.userModifiedFields, parceled.userModifiedFields);
        assertEquals(rule.triggerDescription, parceled.triggerDescription);
        assertEquals(rule.zenPolicy, parceled.zenPolicy);
        assertEquals(rule.deletionInstant, parceled.deletionInstant);

        assertEquals(rule, parceled);
        assertEquals(rule.hashCode(), parceled.hashCode());

    }

    @Test
@@ -510,6 +513,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
        rule.userModifiedFields = 4;
        rule.iconResName = ICON_RES_NAME;
        rule.triggerDescription = TRIGGER_DESC;
        rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        writeRuleXml(rule, baos);
@@ -539,6 +543,7 @@ public class ZenModeConfigTest extends UiServiceTestCase {
        assertEquals(rule.userModifiedFields, fromXml.userModifiedFields);
        assertEquals(rule.triggerDescription, fromXml.triggerDescription);
        assertEquals(rule.iconResName, fromXml.iconResName);
        assertEquals(rule.deletionInstant, fromXml.deletionInstant);
    }

    @Test
Loading