Loading core/java/android/service/notification/ZenModeConfig.java +113 −36 Original line number Original line Diff line number Diff line Loading @@ -64,6 +64,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy; import java.time.Instant; import java.util.Arrays; import java.util.Arrays; import java.util.Calendar; import java.util.Calendar; import java.util.Date; import java.util.Date; Loading Loading @@ -233,6 +234,7 @@ public class ZenModeConfig implements Parcelable { private static final String MANUAL_TAG = "manual"; private static final String MANUAL_TAG = "manual"; private static final String AUTOMATIC_TAG = "automatic"; 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_ID = "ruleId"; private static final String RULE_ATT_ENABLED = "enabled"; private static final String RULE_ATT_ENABLED = "enabled"; Loading @@ -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_USER_MODIFIED_FIELDS = "userModifiedFields"; private static final String RULE_ATT_ICON = "rule_icon"; private static final String RULE_ATT_ICON = "rule_icon"; private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc"; 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_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale"; private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY = private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY = Loading Loading @@ -292,6 +295,10 @@ public class ZenModeConfig implements Parcelable { @UnsupportedAppUsage @UnsupportedAppUsage public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>(); 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 @UnsupportedAppUsage public ZenModeConfig() { public ZenModeConfig() { } } Loading @@ -306,15 +313,9 @@ public class ZenModeConfig implements Parcelable { allowMessagesFrom = source.readInt(); allowMessagesFrom = source.readInt(); user = source.readInt(); user = source.readInt(); manualRule = source.readParcelable(null, ZenRule.class); manualRule = source.readParcelable(null, ZenRule.class); final int len = source.readInt(); readRulesFromParcel(automaticRules, source); if (len > 0) { if (Flags.modesApi()) { final String[] ids = new String[len]; readRulesFromParcel(deletedRules, source); 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]); } } } allowAlarms = source.readInt() == 1; allowAlarms = source.readInt() == 1; allowMedia = source.readInt() == 1; allowMedia = source.readInt() == 1; Loading @@ -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 @Override public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(allowCalls ? 1 : 0); dest.writeInt(allowCalls ? 1 : 0); Loading @@ -339,19 +353,9 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(allowMessagesFrom); dest.writeInt(allowMessagesFrom); dest.writeInt(user); dest.writeInt(user); dest.writeParcelable(manualRule, 0); dest.writeParcelable(manualRule, 0); if (!automaticRules.isEmpty()) { writeRulesToParcel(automaticRules, dest); final int len = automaticRules.size(); if (Flags.modesApi()) { final String[] ids = new String[len]; writeRulesToParcel(deletedRules, dest); 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); } } dest.writeInt(allowAlarms ? 1 : 0); dest.writeInt(allowAlarms ? 1 : 0); dest.writeInt(allowMedia ? 1 : 0); dest.writeInt(allowMedia ? 1 : 0); Loading @@ -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 @Override public String toString() { public String toString() { StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') Loading @@ -389,23 +410,26 @@ public class ZenModeConfig implements Parcelable { } else { } else { sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd); sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd); } } return sb.append(",\nautomaticRules=").append(rulesToString()) sb.append(",\nautomaticRules=").append(rulesToString(automaticRules)) .append(",\nmanualRule=").append(manualRule) .append(",\nmanualRule=").append(manualRule); .append(']').toString(); if (Flags.modesApi()) { sb.append(",\ndeletedRules=").append(rulesToString(deletedRules)); } return sb.append(']').toString(); } } private String rulesToString() { private static String rulesToString(ArrayMap<String, ZenRule> ruleList) { if (automaticRules.isEmpty()) { if (ruleList.isEmpty()) { return "{}"; return "{}"; } } StringBuilder buffer = new StringBuilder(automaticRules.size() * 28); StringBuilder buffer = new StringBuilder(ruleList.size() * 28); buffer.append("{\n"); buffer.append("{\n"); for (int i = 0; i < automaticRules.size(); i++) { for (int i = 0; i < ruleList.size(); i++) { if (i > 0) { if (i > 0) { buffer.append(",\n"); buffer.append(",\n"); } } Object value = automaticRules.valueAt(i); Object value = ruleList.valueAt(i); buffer.append(value); buffer.append(value); } } buffer.append('}'); buffer.append('}'); Loading Loading @@ -487,7 +511,9 @@ public class ZenModeConfig implements Parcelable { && other.allowConversations == allowConversations && other.allowConversations == allowConversations && other.allowConversationsFrom == allowConversationsFrom; && other.allowConversationsFrom == allowConversationsFrom; if (Flags.modesApi()) { if (Flags.modesApi()) { return eq && other.allowPriorityChannels == allowPriorityChannels; return eq && Objects.equals(other.deletedRules, deletedRules) && other.allowPriorityChannels == allowPriorityChannels; } } return eq; return eq; } } Loading Loading @@ -644,13 +670,21 @@ public class ZenModeConfig implements Parcelable { DEFAULT_SUPPRESSED_VISUAL_EFFECTS); DEFAULT_SUPPRESSED_VISUAL_EFFECTS); } else if (MANUAL_TAG.equals(tag)) { } else if (MANUAL_TAG.equals(tag)) { rt.manualRule = readRuleXml(parser); 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 String id = parser.getAttributeValue(null, RULE_ATT_ID); final ZenRule automaticRule = readRuleXml(parser); final ZenRule automaticRule = readRuleXml(parser); if (id != null && automaticRule != null) { if (id != null && automaticRule != null) { automaticRule.id = id; 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); rt.automaticRules.put(id, automaticRule); } } } } else if (STATE_TAG.equals(tag)) { } else if (STATE_TAG.equals(tag)) { rt.areChannelsBypassingDnd = safeBoolean(parser, rt.areChannelsBypassingDnd = safeBoolean(parser, STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND); STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND); Loading @@ -660,13 +694,24 @@ public class ZenModeConfig implements Parcelable { throw new IllegalStateException("Failed to reach END_DOCUMENT"); 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 * Writes XML of current ZenModeConfig * @param out serializer * @param out serializer * @param version uses XML_VERSION if version is null * @param version uses XML_VERSION if version is null * @throws IOException * @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.startTag(null, ZEN_TAG); out.attribute(null, ZEN_ATT_VERSION, version == null out.attribute(null, ZEN_ATT_VERSION, version == null ? Integer.toString(XML_VERSION) : Integer.toString(version)); ? Integer.toString(XML_VERSION) : Integer.toString(version)); Loading Loading @@ -707,6 +752,15 @@ public class ZenModeConfig implements Parcelable { writeRuleXml(automaticRule, out); writeRuleXml(automaticRule, out); out.endTag(null, AUTOMATIC_TAG); 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.startTag(null, STATE_TAG); out.attributeBoolean(null, STATE_ATT_CHANNELS_BYPASSING_DND, areChannelsBypassingDnd); out.attributeBoolean(null, STATE_ATT_CHANNELS_BYPASSING_DND, areChannelsBypassingDnd); Loading Loading @@ -752,6 +806,11 @@ public class ZenModeConfig implements Parcelable { rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC); rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC); rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN); rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN); rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0); 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; return rt; } } Loading Loading @@ -799,6 +858,10 @@ public class ZenModeConfig implements Parcelable { } } out.attributeInt(null, RULE_ATT_TYPE, rule.type); out.attributeInt(null, RULE_ATT_TYPE, rule.type); out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields); out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields); if (rule.deletionInstant != null) { out.attributeLong(null, RULE_ATT_DELETION_INSTANT, rule.deletionInstant.toEpochMilli()); } } } } } Loading Loading @@ -1998,6 +2061,7 @@ public class ZenModeConfig implements Parcelable { public String iconResName; public String iconResName; public boolean allowManualInvocation; public boolean allowManualInvocation; public int userModifiedFields; public int userModifiedFields; @Nullable public Instant deletionInstant; // Only set on deleted rules. public ZenRule() { } public ZenRule() { } Loading Loading @@ -2031,6 +2095,9 @@ public class ZenModeConfig implements Parcelable { triggerDescription = source.readString(); triggerDescription = source.readString(); type = source.readInt(); type = source.readInt(); userModifiedFields = source.readInt(); userModifiedFields = source.readInt(); if (source.readInt() == 1) { deletionInstant = Instant.ofEpochMilli(source.readLong()); } } } } } Loading Loading @@ -2091,6 +2158,12 @@ public class ZenModeConfig implements Parcelable { dest.writeString(triggerDescription); dest.writeString(triggerDescription); dest.writeInt(type); dest.writeInt(type); dest.writeInt(userModifiedFields); dest.writeInt(userModifiedFields); if (deletionInstant != null) { dest.writeInt(1); dest.writeLong(deletionInstant.toEpochMilli()); } else { dest.writeInt(0); } } } } } Loading Loading @@ -2121,6 +2194,9 @@ public class ZenModeConfig implements Parcelable { .append(",triggerDescription=").append(triggerDescription) .append(",triggerDescription=").append(triggerDescription) .append(",type=").append(type) .append(",type=").append(type) .append(",userModifiedFields=").append(userModifiedFields); .append(",userModifiedFields=").append(userModifiedFields); if (deletionInstant != null) { sb.append(",deletionInstant=").append(deletionInstant); } } } return sb.append(']').toString(); return sb.append(']').toString(); Loading Loading @@ -2180,7 +2256,8 @@ public class ZenModeConfig implements Parcelable { && Objects.equals(other.iconResName, iconResName) && Objects.equals(other.iconResName, iconResName) && Objects.equals(other.triggerDescription, triggerDescription) && Objects.equals(other.triggerDescription, triggerDescription) && other.type == type && other.type == type && other.userModifiedFields == userModifiedFields; && other.userModifiedFields == userModifiedFields && Objects.equals(other.deletionInstant, deletionInstant); } } return finalEquals; return finalEquals; Loading @@ -2192,7 +2269,7 @@ public class ZenModeConfig implements Parcelable { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, component, configurationActivity, pkg, id, enabler, zenPolicy, zenDeviceEffects, modified, allowManualInvocation, iconResName, zenDeviceEffects, modified, allowManualInvocation, iconResName, triggerDescription, type, userModifiedFields); triggerDescription, type, userModifiedFields, deletionInstant); } } return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, modified); component, configurationActivity, pkg, id, enabler, zenPolicy, modified); Loading core/java/android/service/notification/ZenModeDiff.java +5 −5 Original line number Original line Diff line number Diff line Loading @@ -30,6 +30,11 @@ import java.util.Set; /** /** * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their * subcomponents (automatic and manual ZenRules). * 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 * @hide */ */ public class ZenModeDiff { public class ZenModeDiff { Loading Loading @@ -467,7 +472,6 @@ public class ZenModeDiff { public static final String FIELD_ICON_RES = "iconResName"; public static final String FIELD_ICON_RES = "iconResName"; public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription"; public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription"; public static final String FIELD_TYPE = "type"; 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 // NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule // Special field to track whether this rule became active or inactive // Special field to track whether this rule became active or inactive Loading Loading @@ -563,10 +567,6 @@ public class ZenModeDiff { if (!Objects.equals(from.iconResName, to.iconResName)) { if (!Objects.equals(from.iconResName, to.iconResName)) { addField(FIELD_ICON_RES, new FieldDiff<>(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)); } } } } } Loading services/core/java/com/android/server/notification/NotificationManagerService.java +3 −3 Original line number Original line Diff line number Diff line Loading @@ -215,7 +215,6 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal; Loading Loading @@ -373,6 +372,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Duration; import java.time.Duration; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; Loading Loading @@ -2466,8 +2466,8 @@ public class NotificationManagerService extends SystemService { mMetricsLogger = new MetricsLogger(); mMetricsLogger = new MetricsLogger(); mRankingHandler = rankingHandler; mRankingHandler = rankingHandler; mConditionProviders = conditionProviders; mConditionProviders = conditionProviders; mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders, mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), Clock.systemUTC(), flagResolver, new ZenModeEventLogger(mPackageManagerClient)); mConditionProviders, flagResolver, new ZenModeEventLogger(mPackageManagerClient)); mZenModeHelper.addCallback(new ZenModeHelper.Callback() { mZenModeHelper.addCallback(new ZenModeHelper.Callback() { @Override @Override public void onConfigChanged() { public void onConfigChanged() { Loading services/core/java/com/android/server/notification/ZenModeHelper.java +125 −21 File changed.Preview size limit exceeded, changes collapsed. Show changes services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +6 −1 Original line number Original line Diff line number Diff line Loading @@ -60,6 +60,7 @@ import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.IOException; import java.time.Instant; @SmallTest @SmallTest @RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class) Loading Loading @@ -407,6 +408,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.userModifiedFields = 16; rule.userModifiedFields = 16; rule.iconResName = ICON_RES_NAME; rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain(); rule.writeToParcel(parcel, 0); rule.writeToParcel(parcel, 0); Loading @@ -432,9 +434,10 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.userModifiedFields, parceled.userModifiedFields); assertEquals(rule.userModifiedFields, parceled.userModifiedFields); assertEquals(rule.triggerDescription, parceled.triggerDescription); assertEquals(rule.triggerDescription, parceled.triggerDescription); assertEquals(rule.zenPolicy, parceled.zenPolicy); assertEquals(rule.zenPolicy, parceled.zenPolicy); assertEquals(rule.deletionInstant, parceled.deletionInstant); assertEquals(rule, parceled); assertEquals(rule, parceled); assertEquals(rule.hashCode(), parceled.hashCode()); assertEquals(rule.hashCode(), parceled.hashCode()); } } @Test @Test Loading Loading @@ -510,6 +513,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.userModifiedFields = 4; rule.userModifiedFields = 4; rule.iconResName = ICON_RES_NAME; rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeRuleXml(rule, baos); writeRuleXml(rule, baos); Loading Loading @@ -539,6 +543,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.userModifiedFields, fromXml.userModifiedFields); assertEquals(rule.userModifiedFields, fromXml.userModifiedFields); assertEquals(rule.triggerDescription, fromXml.triggerDescription); assertEquals(rule.triggerDescription, fromXml.triggerDescription); assertEquals(rule.iconResName, fromXml.iconResName); assertEquals(rule.iconResName, fromXml.iconResName); assertEquals(rule.deletionInstant, fromXml.deletionInstant); } } @Test @Test Loading Loading
core/java/android/service/notification/ZenModeConfig.java +113 −36 Original line number Original line Diff line number Diff line Loading @@ -64,6 +64,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy; import java.time.Instant; import java.util.Arrays; import java.util.Arrays; import java.util.Calendar; import java.util.Calendar; import java.util.Date; import java.util.Date; Loading Loading @@ -233,6 +234,7 @@ public class ZenModeConfig implements Parcelable { private static final String MANUAL_TAG = "manual"; private static final String MANUAL_TAG = "manual"; private static final String AUTOMATIC_TAG = "automatic"; 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_ID = "ruleId"; private static final String RULE_ATT_ENABLED = "enabled"; private static final String RULE_ATT_ENABLED = "enabled"; Loading @@ -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_USER_MODIFIED_FIELDS = "userModifiedFields"; private static final String RULE_ATT_ICON = "rule_icon"; private static final String RULE_ATT_ICON = "rule_icon"; private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc"; 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_DISPLAY_GRAYSCALE = "zdeDisplayGrayscale"; private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY = private static final String DEVICE_EFFECT_SUPPRESS_AMBIENT_DISPLAY = Loading Loading @@ -292,6 +295,10 @@ public class ZenModeConfig implements Parcelable { @UnsupportedAppUsage @UnsupportedAppUsage public ArrayMap<String, ZenRule> automaticRules = new ArrayMap<>(); 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 @UnsupportedAppUsage public ZenModeConfig() { public ZenModeConfig() { } } Loading @@ -306,15 +313,9 @@ public class ZenModeConfig implements Parcelable { allowMessagesFrom = source.readInt(); allowMessagesFrom = source.readInt(); user = source.readInt(); user = source.readInt(); manualRule = source.readParcelable(null, ZenRule.class); manualRule = source.readParcelable(null, ZenRule.class); final int len = source.readInt(); readRulesFromParcel(automaticRules, source); if (len > 0) { if (Flags.modesApi()) { final String[] ids = new String[len]; readRulesFromParcel(deletedRules, source); 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]); } } } allowAlarms = source.readInt() == 1; allowAlarms = source.readInt() == 1; allowMedia = source.readInt() == 1; allowMedia = source.readInt() == 1; Loading @@ -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 @Override public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) { dest.writeInt(allowCalls ? 1 : 0); dest.writeInt(allowCalls ? 1 : 0); Loading @@ -339,19 +353,9 @@ public class ZenModeConfig implements Parcelable { dest.writeInt(allowMessagesFrom); dest.writeInt(allowMessagesFrom); dest.writeInt(user); dest.writeInt(user); dest.writeParcelable(manualRule, 0); dest.writeParcelable(manualRule, 0); if (!automaticRules.isEmpty()) { writeRulesToParcel(automaticRules, dest); final int len = automaticRules.size(); if (Flags.modesApi()) { final String[] ids = new String[len]; writeRulesToParcel(deletedRules, dest); 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); } } dest.writeInt(allowAlarms ? 1 : 0); dest.writeInt(allowAlarms ? 1 : 0); dest.writeInt(allowMedia ? 1 : 0); dest.writeInt(allowMedia ? 1 : 0); Loading @@ -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 @Override public String toString() { public String toString() { StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') StringBuilder sb = new StringBuilder(ZenModeConfig.class.getSimpleName()).append('[') Loading @@ -389,23 +410,26 @@ public class ZenModeConfig implements Parcelable { } else { } else { sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd); sb.append(",areChannelsBypassingDnd=").append(areChannelsBypassingDnd); } } return sb.append(",\nautomaticRules=").append(rulesToString()) sb.append(",\nautomaticRules=").append(rulesToString(automaticRules)) .append(",\nmanualRule=").append(manualRule) .append(",\nmanualRule=").append(manualRule); .append(']').toString(); if (Flags.modesApi()) { sb.append(",\ndeletedRules=").append(rulesToString(deletedRules)); } return sb.append(']').toString(); } } private String rulesToString() { private static String rulesToString(ArrayMap<String, ZenRule> ruleList) { if (automaticRules.isEmpty()) { if (ruleList.isEmpty()) { return "{}"; return "{}"; } } StringBuilder buffer = new StringBuilder(automaticRules.size() * 28); StringBuilder buffer = new StringBuilder(ruleList.size() * 28); buffer.append("{\n"); buffer.append("{\n"); for (int i = 0; i < automaticRules.size(); i++) { for (int i = 0; i < ruleList.size(); i++) { if (i > 0) { if (i > 0) { buffer.append(",\n"); buffer.append(",\n"); } } Object value = automaticRules.valueAt(i); Object value = ruleList.valueAt(i); buffer.append(value); buffer.append(value); } } buffer.append('}'); buffer.append('}'); Loading Loading @@ -487,7 +511,9 @@ public class ZenModeConfig implements Parcelable { && other.allowConversations == allowConversations && other.allowConversations == allowConversations && other.allowConversationsFrom == allowConversationsFrom; && other.allowConversationsFrom == allowConversationsFrom; if (Flags.modesApi()) { if (Flags.modesApi()) { return eq && other.allowPriorityChannels == allowPriorityChannels; return eq && Objects.equals(other.deletedRules, deletedRules) && other.allowPriorityChannels == allowPriorityChannels; } } return eq; return eq; } } Loading Loading @@ -644,13 +670,21 @@ public class ZenModeConfig implements Parcelable { DEFAULT_SUPPRESSED_VISUAL_EFFECTS); DEFAULT_SUPPRESSED_VISUAL_EFFECTS); } else if (MANUAL_TAG.equals(tag)) { } else if (MANUAL_TAG.equals(tag)) { rt.manualRule = readRuleXml(parser); 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 String id = parser.getAttributeValue(null, RULE_ATT_ID); final ZenRule automaticRule = readRuleXml(parser); final ZenRule automaticRule = readRuleXml(parser); if (id != null && automaticRule != null) { if (id != null && automaticRule != null) { automaticRule.id = id; 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); rt.automaticRules.put(id, automaticRule); } } } } else if (STATE_TAG.equals(tag)) { } else if (STATE_TAG.equals(tag)) { rt.areChannelsBypassingDnd = safeBoolean(parser, rt.areChannelsBypassingDnd = safeBoolean(parser, STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND); STATE_ATT_CHANNELS_BYPASSING_DND, DEFAULT_CHANNELS_BYPASSING_DND); Loading @@ -660,13 +694,24 @@ public class ZenModeConfig implements Parcelable { throw new IllegalStateException("Failed to reach END_DOCUMENT"); 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 * Writes XML of current ZenModeConfig * @param out serializer * @param out serializer * @param version uses XML_VERSION if version is null * @param version uses XML_VERSION if version is null * @throws IOException * @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.startTag(null, ZEN_TAG); out.attribute(null, ZEN_ATT_VERSION, version == null out.attribute(null, ZEN_ATT_VERSION, version == null ? Integer.toString(XML_VERSION) : Integer.toString(version)); ? Integer.toString(XML_VERSION) : Integer.toString(version)); Loading Loading @@ -707,6 +752,15 @@ public class ZenModeConfig implements Parcelable { writeRuleXml(automaticRule, out); writeRuleXml(automaticRule, out); out.endTag(null, AUTOMATIC_TAG); 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.startTag(null, STATE_TAG); out.attributeBoolean(null, STATE_ATT_CHANNELS_BYPASSING_DND, areChannelsBypassingDnd); out.attributeBoolean(null, STATE_ATT_CHANNELS_BYPASSING_DND, areChannelsBypassingDnd); Loading Loading @@ -752,6 +806,11 @@ public class ZenModeConfig implements Parcelable { rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC); rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC); rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN); rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN); rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0); 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; return rt; } } Loading Loading @@ -799,6 +858,10 @@ public class ZenModeConfig implements Parcelable { } } out.attributeInt(null, RULE_ATT_TYPE, rule.type); out.attributeInt(null, RULE_ATT_TYPE, rule.type); out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields); out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields); if (rule.deletionInstant != null) { out.attributeLong(null, RULE_ATT_DELETION_INSTANT, rule.deletionInstant.toEpochMilli()); } } } } } Loading Loading @@ -1998,6 +2061,7 @@ public class ZenModeConfig implements Parcelable { public String iconResName; public String iconResName; public boolean allowManualInvocation; public boolean allowManualInvocation; public int userModifiedFields; public int userModifiedFields; @Nullable public Instant deletionInstant; // Only set on deleted rules. public ZenRule() { } public ZenRule() { } Loading Loading @@ -2031,6 +2095,9 @@ public class ZenModeConfig implements Parcelable { triggerDescription = source.readString(); triggerDescription = source.readString(); type = source.readInt(); type = source.readInt(); userModifiedFields = source.readInt(); userModifiedFields = source.readInt(); if (source.readInt() == 1) { deletionInstant = Instant.ofEpochMilli(source.readLong()); } } } } } Loading Loading @@ -2091,6 +2158,12 @@ public class ZenModeConfig implements Parcelable { dest.writeString(triggerDescription); dest.writeString(triggerDescription); dest.writeInt(type); dest.writeInt(type); dest.writeInt(userModifiedFields); dest.writeInt(userModifiedFields); if (deletionInstant != null) { dest.writeInt(1); dest.writeLong(deletionInstant.toEpochMilli()); } else { dest.writeInt(0); } } } } } Loading Loading @@ -2121,6 +2194,9 @@ public class ZenModeConfig implements Parcelable { .append(",triggerDescription=").append(triggerDescription) .append(",triggerDescription=").append(triggerDescription) .append(",type=").append(type) .append(",type=").append(type) .append(",userModifiedFields=").append(userModifiedFields); .append(",userModifiedFields=").append(userModifiedFields); if (deletionInstant != null) { sb.append(",deletionInstant=").append(deletionInstant); } } } return sb.append(']').toString(); return sb.append(']').toString(); Loading Loading @@ -2180,7 +2256,8 @@ public class ZenModeConfig implements Parcelable { && Objects.equals(other.iconResName, iconResName) && Objects.equals(other.iconResName, iconResName) && Objects.equals(other.triggerDescription, triggerDescription) && Objects.equals(other.triggerDescription, triggerDescription) && other.type == type && other.type == type && other.userModifiedFields == userModifiedFields; && other.userModifiedFields == userModifiedFields && Objects.equals(other.deletionInstant, deletionInstant); } } return finalEquals; return finalEquals; Loading @@ -2192,7 +2269,7 @@ public class ZenModeConfig implements Parcelable { return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, component, configurationActivity, pkg, id, enabler, zenPolicy, zenDeviceEffects, modified, allowManualInvocation, iconResName, zenDeviceEffects, modified, allowManualInvocation, iconResName, triggerDescription, type, userModifiedFields); triggerDescription, type, userModifiedFields, deletionInstant); } } return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, modified); component, configurationActivity, pkg, id, enabler, zenPolicy, modified); Loading
core/java/android/service/notification/ZenModeDiff.java +5 −5 Original line number Original line Diff line number Diff line Loading @@ -30,6 +30,11 @@ import java.util.Set; /** /** * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their * subcomponents (automatic and manual ZenRules). * 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 * @hide */ */ public class ZenModeDiff { public class ZenModeDiff { Loading Loading @@ -467,7 +472,6 @@ public class ZenModeDiff { public static final String FIELD_ICON_RES = "iconResName"; public static final String FIELD_ICON_RES = "iconResName"; public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription"; public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription"; public static final String FIELD_TYPE = "type"; 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 // NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule // Special field to track whether this rule became active or inactive // Special field to track whether this rule became active or inactive Loading Loading @@ -563,10 +567,6 @@ public class ZenModeDiff { if (!Objects.equals(from.iconResName, to.iconResName)) { if (!Objects.equals(from.iconResName, to.iconResName)) { addField(FIELD_ICON_RES, new FieldDiff<>(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)); } } } } } Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +3 −3 Original line number Original line Diff line number Diff line Loading @@ -215,7 +215,6 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.IPackageManager; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageManagerInternal; Loading Loading @@ -373,6 +372,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets; import java.time.Clock; import java.time.Duration; import java.time.Duration; import java.util.ArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Arrays; Loading Loading @@ -2466,8 +2466,8 @@ public class NotificationManagerService extends SystemService { mMetricsLogger = new MetricsLogger(); mMetricsLogger = new MetricsLogger(); mRankingHandler = rankingHandler; mRankingHandler = rankingHandler; mConditionProviders = conditionProviders; mConditionProviders = conditionProviders; mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), mConditionProviders, mZenModeHelper = new ZenModeHelper(getContext(), mHandler.getLooper(), Clock.systemUTC(), flagResolver, new ZenModeEventLogger(mPackageManagerClient)); mConditionProviders, flagResolver, new ZenModeEventLogger(mPackageManagerClient)); mZenModeHelper.addCallback(new ZenModeHelper.Callback() { mZenModeHelper.addCallback(new ZenModeHelper.Callback() { @Override @Override public void onConfigChanged() { public void onConfigChanged() { Loading
services/core/java/com/android/server/notification/ZenModeHelper.java +125 −21 File changed.Preview size limit exceeded, changes collapsed. Show changes
services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +6 −1 Original line number Original line Diff line number Diff line Loading @@ -60,6 +60,7 @@ import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.IOException; import java.time.Instant; @SmallTest @SmallTest @RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class) Loading Loading @@ -407,6 +408,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.userModifiedFields = 16; rule.userModifiedFields = 16; rule.iconResName = ICON_RES_NAME; rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain(); rule.writeToParcel(parcel, 0); rule.writeToParcel(parcel, 0); Loading @@ -432,9 +434,10 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.userModifiedFields, parceled.userModifiedFields); assertEquals(rule.userModifiedFields, parceled.userModifiedFields); assertEquals(rule.triggerDescription, parceled.triggerDescription); assertEquals(rule.triggerDescription, parceled.triggerDescription); assertEquals(rule.zenPolicy, parceled.zenPolicy); assertEquals(rule.zenPolicy, parceled.zenPolicy); assertEquals(rule.deletionInstant, parceled.deletionInstant); assertEquals(rule, parceled); assertEquals(rule, parceled); assertEquals(rule.hashCode(), parceled.hashCode()); assertEquals(rule.hashCode(), parceled.hashCode()); } } @Test @Test Loading Loading @@ -510,6 +513,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { rule.userModifiedFields = 4; rule.userModifiedFields = 4; rule.iconResName = ICON_RES_NAME; rule.iconResName = ICON_RES_NAME; rule.triggerDescription = TRIGGER_DESC; rule.triggerDescription = TRIGGER_DESC; rule.deletionInstant = Instant.ofEpochMilli(1701790147000L); ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeRuleXml(rule, baos); writeRuleXml(rule, baos); Loading Loading @@ -539,6 +543,7 @@ public class ZenModeConfigTest extends UiServiceTestCase { assertEquals(rule.userModifiedFields, fromXml.userModifiedFields); assertEquals(rule.userModifiedFields, fromXml.userModifiedFields); assertEquals(rule.triggerDescription, fromXml.triggerDescription); assertEquals(rule.triggerDescription, fromXml.triggerDescription); assertEquals(rule.iconResName, fromXml.iconResName); assertEquals(rule.iconResName, fromXml.iconResName); assertEquals(rule.deletionInstant, fromXml.deletionInstant); } } @Test @Test Loading