• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.service.notification;
18 
19 import android.annotation.IntDef;
20 import android.annotation.Nullable;
21 import android.app.Flags;
22 import android.util.ArrayMap;
23 import android.util.ArraySet;
24 
25 import java.lang.annotation.Retention;
26 import java.lang.annotation.RetentionPolicy;
27 import java.util.LinkedHashMap;
28 import java.util.Objects;
29 import java.util.Set;
30 
31 /**
32  * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
33  * subcomponents (automatic and manual ZenRules).
34  *
35  * <p>Note that this class is intended to detect <em>meaningful</em> differences, so objects that
36  * are not identical (as per their {@code equals()} implementation) can still produce an empty diff
37  * if only "metadata" fields are updated.
38  *
39  * @hide
40  */
41 public class ZenModeDiff {
42     /**
43      * Enum representing whether the existence of a config or rule has changed (added or removed,
44      * or "none" meaning there is no change, which may either mean both null, or there exists a
45      * diff in fields rather than add/remove).
46      */
47     @IntDef(value = {
48             NONE,
49             ADDED,
50             REMOVED,
51     })
52     @Retention(RetentionPolicy.SOURCE)
53     public @interface ExistenceChange{}
54 
55     public static final int NONE = 0;
56     public static final int ADDED = 1;
57     public static final int REMOVED = 2;
58 
59     /**
60      * Diff class representing an individual field diff.
61      * @param <T> The type of the field.
62      */
63     public static class FieldDiff<T> {
64         private final T mFrom;
65         private final T mTo;
66         private final BaseDiff mDetailedDiff;
67 
68         /**
69          * Constructor to create a FieldDiff object with the given values.
70          * @param from from (old) value
71          * @param to to (new) value
72          */
FieldDiff(@ullable T from, @Nullable T to)73         public FieldDiff(@Nullable T from, @Nullable T to) {
74             mFrom = from;
75             mTo = to;
76             mDetailedDiff = null;
77         }
78 
79         /**
80          * Constructor to create a FieldDiff object with the given values, and that has a
81          * detailed BaseDiff.
82          * @param from from (old) value
83          * @param to to (new) value
84          */
FieldDiff(@ullable T from, @Nullable T to, @Nullable BaseDiff detailedDiff)85         public FieldDiff(@Nullable T from, @Nullable T to, @Nullable BaseDiff detailedDiff) {
86             mFrom = from;
87             mTo = to;
88             mDetailedDiff = detailedDiff;
89         }
90 
91         /**
92          * Get the "from" value
93          */
from()94         public T from() {
95             return mFrom;
96         }
97 
98         /**
99          * Get the "to" value
100          */
to()101         public T to() {
102             return mTo;
103         }
104 
105         /**
106          * Get the string representation of this field diff, in the form of "from->to".
107          */
108         @Override
toString()109         public String toString() {
110             if (mDetailedDiff != null) {
111                 return mDetailedDiff.toString();
112             }
113             return mFrom + "->" + mTo;
114         }
115 
116         /**
117          * Returns whether this represents an actual diff.
118          */
hasDiff()119         public boolean hasDiff() {
120             if (mDetailedDiff != null) {
121                 return mDetailedDiff.hasDiff();
122             }
123             // note that Objects.equals handles null values gracefully.
124             return !Objects.equals(mFrom, mTo);
125         }
126     }
127 
128     /**
129      * Base diff class that contains info about whether something was added, and a set of named
130      * fields that changed.
131      * Extend for diffs of specific types of objects.
132      */
133     private abstract static class BaseDiff {
134         // Whether the diff was added or removed
135         @ExistenceChange private int mExists = NONE;
136 
137         // Map from field name to diffs for any standalone fields in the object.
138         // LinkedHashMap is specifically chosen here to show insertion order when keys are fetched.
139         private LinkedHashMap<String, FieldDiff> mFields = new LinkedHashMap<>();
140 
141         // Functions for actually diffing objects and string representations have to be implemented
142         // by subclasses.
143 
144         /**
145          * Return whether this diff represents any changes.
146          */
hasDiff()147         public abstract boolean hasDiff();
148 
149         /**
150          * Return a string representation of the diff.
151          */
toString()152         public abstract String toString();
153 
154         /**
155          * Constructor that takes the two objects meant to be compared. This constructor sets
156          * whether there is an existence change (added or removed).
157          * @param from previous Object
158          * @param to new Object
159          */
BaseDiff(Object from, Object to)160         BaseDiff(Object from, Object to) {
161             if (from == null) {
162                 if (to != null) {
163                     mExists = ADDED;
164                 }
165                 // If both are null, there isn't an existence change; callers/inheritors must handle
166                 // the both null case.
167             } else if (to == null) {
168                 // in this case, we know that from != null
169                 mExists = REMOVED;
170             }
171 
172             // Subclasses should implement the actual diffing functionality in their own
173             // constructors.
174         }
175 
176         /**
177          * Add a diff for a specific field to the map.
178          * @param name field name
179          * @param diff FieldDiff object representing the diff
180          */
addField(String name, FieldDiff diff)181         final void addField(String name, FieldDiff diff) {
182             mFields.put(name, diff);
183         }
184 
185         /**
186          * Returns whether this diff represents a config being newly added.
187          */
wasAdded()188         public final boolean wasAdded() {
189             return mExists == ADDED;
190         }
191 
192         /**
193          * Returns whether this diff represents a config being removed.
194          */
wasRemoved()195         public final boolean wasRemoved() {
196             return mExists == REMOVED;
197         }
198 
199         /**
200          * Returns whether this diff represents an object being either added or removed.
201          */
hasExistenceChange()202         public final boolean hasExistenceChange() {
203             return mExists != NONE;
204         }
205 
206         /**
207          * Returns whether there are any individual field diffs.
208          */
hasFieldDiffs()209         public final boolean hasFieldDiffs() {
210             return mFields.size() > 0;
211         }
212 
213         /**
214          * Returns the diff for the specific named field if it exists
215          */
getDiffForField(String name)216         public final FieldDiff getDiffForField(String name) {
217             return mFields.getOrDefault(name, null);
218         }
219 
220         /**
221          * Get the set of all field names with some diff.
222          */
fieldNamesWithDiff()223         public final Set<String> fieldNamesWithDiff() {
224             return mFields.keySet();
225         }
226     }
227 
228     /**
229      * Diff class representing a diff between two ZenModeConfigs.
230      */
231     public static class ConfigDiff extends BaseDiff {
232         // Rules. Automatic rule map is keyed by the rule name.
233         private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>();
234         private RuleDiff mManualRuleDiff;
235 
236         // Field name constants
237         public static final String FIELD_USER = "user";
238         public static final String FIELD_ALLOW_ALARMS = "allowAlarms";
239         public static final String FIELD_ALLOW_MEDIA = "allowMedia";
240         public static final String FIELD_ALLOW_SYSTEM = "allowSystem";
241         public static final String FIELD_ALLOW_CALLS = "allowCalls";
242         public static final String FIELD_ALLOW_REMINDERS = "allowReminders";
243         public static final String FIELD_ALLOW_EVENTS = "allowEvents";
244         public static final String FIELD_ALLOW_REPEAT_CALLERS = "allowRepeatCallers";
245         public static final String FIELD_ALLOW_MESSAGES = "allowMessages";
246         public static final String FIELD_ALLOW_CONVERSATIONS = "allowConversations";
247         public static final String FIELD_ALLOW_CALLS_FROM = "allowCallsFrom";
248         public static final String FIELD_ALLOW_MESSAGES_FROM = "allowMessagesFrom";
249         public static final String FIELD_ALLOW_CONVERSATIONS_FROM = "allowConversationsFrom";
250         public static final String FIELD_SUPPRESSED_VISUAL_EFFECTS = "suppressedVisualEffects";
251         public static final String FIELD_HAS_PRIORITY_CHANNELS = "hasPriorityChannels";
252         public static final String FIELD_ALLOW_PRIORITY_CHANNELS = "allowPriorityChannels";
253         private static final Set<String> PEOPLE_TYPE_FIELDS =
254                 Set.of(FIELD_ALLOW_CALLS_FROM, FIELD_ALLOW_MESSAGES_FROM);
255 
256         /**
257          * Create a diff that contains diffs between the "from" and "to" ZenModeConfigs.
258          *
259          * @param from previous ZenModeConfig
260          * @param to   new ZenModeConfig
261          */
ConfigDiff(ZenModeConfig from, ZenModeConfig to)262         public ConfigDiff(ZenModeConfig from, ZenModeConfig to) {
263             super(from, to);
264             // If both are null skip
265             if (from == null && to == null) {
266                 return;
267             }
268             if (hasExistenceChange()) {
269                 // either added or removed; return here. otherwise (they're not both null) there's
270                 // field diffs.
271                 return;
272             }
273 
274             // Now we compare all the fields, knowing there's a diff and that neither is null
275             if (from.user != to.user) {
276                 addField(FIELD_USER, new FieldDiff<>(from.user, to.user));
277             }
278             if (from.allowAlarms != to.allowAlarms) {
279                 addField(FIELD_ALLOW_ALARMS, new FieldDiff<>(from.allowAlarms, to.allowAlarms));
280             }
281             if (from.allowMedia != to.allowMedia) {
282                 addField(FIELD_ALLOW_MEDIA, new FieldDiff<>(from.allowMedia, to.allowMedia));
283             }
284             if (from.allowSystem != to.allowSystem) {
285                 addField(FIELD_ALLOW_SYSTEM, new FieldDiff<>(from.allowSystem, to.allowSystem));
286             }
287             if (from.allowCalls != to.allowCalls) {
288                 addField(FIELD_ALLOW_CALLS, new FieldDiff<>(from.allowCalls, to.allowCalls));
289             }
290             if (from.allowReminders != to.allowReminders) {
291                 addField(FIELD_ALLOW_REMINDERS,
292                         new FieldDiff<>(from.allowReminders, to.allowReminders));
293             }
294             if (from.allowEvents != to.allowEvents) {
295                 addField(FIELD_ALLOW_EVENTS, new FieldDiff<>(from.allowEvents, to.allowEvents));
296             }
297             if (from.allowRepeatCallers != to.allowRepeatCallers) {
298                 addField(FIELD_ALLOW_REPEAT_CALLERS,
299                         new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers));
300             }
301             if (from.allowMessages != to.allowMessages) {
302                 addField(FIELD_ALLOW_MESSAGES,
303                         new FieldDiff<>(from.allowMessages, to.allowMessages));
304             }
305             if (from.allowConversations != to.allowConversations) {
306                 addField(FIELD_ALLOW_CONVERSATIONS,
307                         new FieldDiff<>(from.allowConversations, to.allowConversations));
308             }
309             if (from.allowCallsFrom != to.allowCallsFrom) {
310                 addField(FIELD_ALLOW_CALLS_FROM,
311                         new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom));
312             }
313             if (from.allowMessagesFrom != to.allowMessagesFrom) {
314                 addField(FIELD_ALLOW_MESSAGES_FROM,
315                         new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom));
316             }
317             if (from.allowConversationsFrom != to.allowConversationsFrom) {
318                 addField(FIELD_ALLOW_CONVERSATIONS_FROM,
319                         new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom));
320             }
321             if (from.suppressedVisualEffects != to.suppressedVisualEffects) {
322                 addField(FIELD_SUPPRESSED_VISUAL_EFFECTS,
323                         new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects));
324             }
325             if (from.hasPriorityChannels != to.hasPriorityChannels) {
326                 addField(FIELD_HAS_PRIORITY_CHANNELS,
327                         new FieldDiff<>(from.hasPriorityChannels, to.hasPriorityChannels));
328             }
329             if (from.allowPriorityChannels != to.allowPriorityChannels) {
330                 addField(FIELD_ALLOW_PRIORITY_CHANNELS,
331                         new FieldDiff<>(from.allowPriorityChannels, to.allowPriorityChannels));
332             }
333 
334             // Compare automatic and manual rules
335             final ArraySet<String> allRules = new ArraySet<>();
336             addKeys(allRules, from.automaticRules);
337             addKeys(allRules, to.automaticRules);
338             final int num = allRules.size();
339             for (int i = 0; i < num; i++) {
340                 final String rule = allRules.valueAt(i);
341                 final ZenModeConfig.ZenRule
342                         fromRule = from.automaticRules != null ? from.automaticRules.get(rule)
343                         : null;
344                 final ZenModeConfig.ZenRule
345                         toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
346                 RuleDiff ruleDiff = new RuleDiff(fromRule, toRule);
347                 if (ruleDiff.hasDiff()) {
348                     mAutomaticRulesDiff.put(rule, ruleDiff);
349                 }
350             }
351             // If there's no diff this may turn out to be null, but that's also fine
352             RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule);
353             if (manualRuleDiff.hasDiff()) {
354                 mManualRuleDiff = manualRuleDiff;
355             }
356         }
357 
addKeys(ArraySet<T> set, ArrayMap<T, ?> map)358         private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
359             if (map != null) {
360                 for (int i = 0; i < map.size(); i++) {
361                     set.add(map.keyAt(i));
362                 }
363             }
364         }
365 
366         /**
367          * Returns whether this diff object contains any diffs in any field.
368          */
369         @Override
hasDiff()370         public boolean hasDiff() {
371             return hasExistenceChange()
372                     || hasFieldDiffs()
373                     || mManualRuleDiff != null
374                     || mAutomaticRulesDiff.size() > 0;
375         }
376 
377         @Override
toString()378         public String toString() {
379             final StringBuilder sb = new StringBuilder("Diff[");
380             if (!hasDiff()) {
381                 sb.append("no changes");
382             }
383 
384             // If added or deleted, then that's just the end of it
385             if (hasExistenceChange()) {
386                 if (wasAdded()) {
387                     sb.append("added");
388                 } else if (wasRemoved()) {
389                     sb.append("removed");
390                 }
391             }
392 
393             // Handle top-level field change
394             boolean first = true;
395             for (String key : fieldNamesWithDiff()) {
396                 FieldDiff diff = getDiffForField(key);
397                 if (diff == null) {
398                     // this shouldn't happen, but
399                     continue;
400                 }
401                 if (first) {
402                     first = false;
403                 } else {
404                     sb.append(",\n");
405                 }
406 
407                 // Some special handling for people- and conversation-type fields for readability
408                 if (PEOPLE_TYPE_FIELDS.contains(key)) {
409                     sb.append(key);
410                     sb.append(":");
411                     sb.append(ZenModeConfig.sourceToString((int) diff.from()));
412                     sb.append("->");
413                     sb.append(ZenModeConfig.sourceToString((int) diff.to()));
414                 } else if (key.equals(FIELD_ALLOW_CONVERSATIONS_FROM)) {
415                     sb.append(key);
416                     sb.append(":");
417                     sb.append(ZenPolicy.conversationTypeToString((int) diff.from()));
418                     sb.append("->");
419                     sb.append(ZenPolicy.conversationTypeToString((int) diff.to()));
420                 } else {
421                     sb.append(key);
422                     sb.append(":");
423                     sb.append(diff);
424                 }
425             }
426 
427             // manual rule
428             if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) {
429                 if (first) {
430                     first = false;
431                 } else {
432                     sb.append(",\n");
433                 }
434                 sb.append("manualRule:");
435                 sb.append(mManualRuleDiff);
436             }
437 
438             // automatic rules
439             for (String rule : mAutomaticRulesDiff.keySet()) {
440                 RuleDiff diff = mAutomaticRulesDiff.get(rule);
441                 if (diff != null && diff.hasDiff()) {
442                     if (first) {
443                         first = false;
444                     } else {
445                         sb.append(",\n");
446                     }
447                     sb.append("automaticRule[");
448                     sb.append(rule);
449                     sb.append("]:");
450                     sb.append(diff);
451                 }
452             }
453 
454             return sb.append(']').toString();
455         }
456 
457         /**
458          * Get the diff in manual rule, if it exists.
459          */
getManualRuleDiff()460         public RuleDiff getManualRuleDiff() {
461             return mManualRuleDiff;
462         }
463 
464         /**
465          * Get the full map of automatic rule diffs, or null if there are no diffs.
466          */
getAllAutomaticRuleDiffs()467         public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() {
468             return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null;
469         }
470     }
471 
472     /**
473      * Diff class representing a change between two ZenRules.
474      */
475     public static class RuleDiff extends BaseDiff {
476         public static final String FIELD_ENABLED = "enabled";
477         public static final String FIELD_CONDITION_OVERRIDE = "conditionOverride";
478         @Deprecated
479         public static final String FIELD_SNOOZING = "snoozing";
480         public static final String FIELD_NAME = "name";
481         public static final String FIELD_ZEN_MODE = "zenMode";
482         public static final String FIELD_CONDITION_ID = "conditionId";
483         public static final String FIELD_CONDITION = "condition";
484         public static final String FIELD_COMPONENT = "component";
485         public static final String FIELD_CONFIGURATION_ACTIVITY = "configurationActivity";
486         public static final String FIELD_ID = "id";
487         public static final String FIELD_CREATION_TIME = "creationTime";
488         public static final String FIELD_ENABLER = "enabler";
489         public static final String FIELD_ZEN_POLICY = "zenPolicy";
490         public static final String FIELD_ZEN_DEVICE_EFFECTS = "zenDeviceEffects";
491         public static final String FIELD_PKG = "pkg";
492         public static final String FIELD_ALLOW_MANUAL = "allowManualInvocation";
493         public static final String FIELD_ICON_RES = "iconResName";
494         public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription";
495         public static final String FIELD_TYPE = "type";
496         public static final String FIELD_LEGACY_SUPPRESSED_EFFECTS = "legacySuppressedEffects";
497         // NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule
498 
499         // Special field to track whether this rule became active or inactive
500         FieldDiff<Boolean> mActiveDiff;
501 
502         /**
503          * Create a RuleDiff representing the difference between two ZenRule objects.
504          * @param from previous ZenRule
505          * @param to new ZenRule
506          * @return The diff between the two given ZenRules
507          */
RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to)508         public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) {
509             super(from, to);
510             // Short-circuit the both-null case
511             if (from == null && to == null) {
512                 return;
513             }
514 
515             // Even if added or removed, there may be a change in whether or not it was active.
516             // This only applies to automatic rules.
517             boolean fromActive = from != null ? from.isActive() : false;
518             boolean toActive = to != null ? to.isActive() : false;
519             if (fromActive != toActive) {
520                 mActiveDiff = new FieldDiff<>(fromActive, toActive);
521             }
522 
523             // Return if the diff was added or removed
524             if (hasExistenceChange()) {
525                 return;
526             }
527 
528             if (from.enabled != to.enabled) {
529                 addField(FIELD_ENABLED, new FieldDiff<>(from.enabled, to.enabled));
530             }
531             if (Flags.modesUi()) {
532                 if (from.conditionOverride != to.conditionOverride) {
533                     addField(FIELD_CONDITION_OVERRIDE,
534                             new FieldDiff<>(from.conditionOverride, to.conditionOverride));
535                 }
536             } else {
537                 if (from.snoozing != to.snoozing) {
538                     addField(FIELD_SNOOZING, new FieldDiff<>(from.snoozing, to.snoozing));
539                 }
540             }
541             if (!Objects.equals(from.name, to.name)) {
542                 addField(FIELD_NAME, new FieldDiff<>(from.name, to.name));
543             }
544             if (from.zenMode != to.zenMode) {
545                 addField(FIELD_ZEN_MODE, new FieldDiff<>(from.zenMode, to.zenMode));
546             }
547             if (!Objects.equals(from.conditionId, to.conditionId)) {
548                 addField(FIELD_CONDITION_ID, new FieldDiff<>(from.conditionId,
549                         to.conditionId));
550             }
551             if (!Objects.equals(from.condition, to.condition)) {
552                 addField(FIELD_CONDITION, new FieldDiff<>(from.condition, to.condition));
553             }
554             if (!Objects.equals(from.component, to.component)) {
555                 addField(FIELD_COMPONENT, new FieldDiff<>(from.component, to.component));
556             }
557             if (!Objects.equals(from.configurationActivity, to.configurationActivity)) {
558                 addField(FIELD_CONFIGURATION_ACTIVITY, new FieldDiff<>(
559                         from.configurationActivity, to.configurationActivity));
560             }
561             if (!Objects.equals(from.id, to.id)) {
562                 addField(FIELD_ID, new FieldDiff<>(from.id, to.id));
563             }
564             if (from.creationTime != to.creationTime) {
565                 addField(FIELD_CREATION_TIME,
566                         new FieldDiff<>(from.creationTime, to.creationTime));
567             }
568             if (!Objects.equals(from.enabler, to.enabler)) {
569                 addField(FIELD_ENABLER, new FieldDiff<>(from.enabler, to.enabler));
570             }
571             PolicyDiff policyDiff = new PolicyDiff(from.zenPolicy, to.zenPolicy);
572             if (policyDiff.hasDiff()) {
573                 addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy,
574                         policyDiff));
575             }
576             if (!Objects.equals(from.pkg, to.pkg)) {
577                 addField(FIELD_PKG, new FieldDiff<>(from.pkg, to.pkg));
578             }
579             DeviceEffectsDiff deviceEffectsDiff = new DeviceEffectsDiff(from.zenDeviceEffects,
580                     to.zenDeviceEffects);
581             if (deviceEffectsDiff.hasDiff()) {
582                 addField(FIELD_ZEN_DEVICE_EFFECTS,
583                         new FieldDiff<>(from.zenDeviceEffects, to.zenDeviceEffects,
584                                 deviceEffectsDiff));
585             }
586             if (!Objects.equals(from.triggerDescription, to.triggerDescription)) {
587                 addField(FIELD_TRIGGER_DESCRIPTION,
588                         new FieldDiff<>(from.triggerDescription, to.triggerDescription));
589             }
590             if (from.type != to.type) {
591                 addField(FIELD_TYPE, new FieldDiff<>(from.type, to.type));
592             }
593             if (from.allowManualInvocation != to.allowManualInvocation) {
594                 addField(FIELD_ALLOW_MANUAL,
595                         new FieldDiff<>(from.allowManualInvocation, to.allowManualInvocation));
596             }
597             if (!Objects.equals(from.iconResName, to.iconResName)) {
598                 addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResName, to.iconResName));
599             }
600             if (android.app.Flags.modesUi()) {
601                 if (from.legacySuppressedEffects != to.legacySuppressedEffects) {
602                     addField(FIELD_LEGACY_SUPPRESSED_EFFECTS,
603                             new FieldDiff<>(from.legacySuppressedEffects,
604                                     to.legacySuppressedEffects));
605                 }
606             }
607         }
608 
609         /**
610          * Returns whether this object represents an actual diff.
611          */
612         @Override
hasDiff()613         public boolean hasDiff() {
614             return hasExistenceChange() || hasFieldDiffs();
615         }
616 
617         @Override
toString()618         public String toString() {
619             final StringBuilder sb = new StringBuilder("ZenRuleDiff{");
620             // If there's no diff, probably we haven't actually let this object continue existing
621             // but might as well handle this case.
622             if (!hasDiff()) {
623                 sb.append("no changes");
624             }
625 
626             // If added or deleted, then that's just the end of it
627             if (hasExistenceChange()) {
628                 if (wasAdded()) {
629                     sb.append("added");
630                 } else if (wasRemoved()) {
631                     sb.append("removed");
632                 }
633             }
634 
635             // Go through all of the individual fields
636             boolean first = true;
637             for (String key : fieldNamesWithDiff()) {
638                 FieldDiff diff = getDiffForField(key);
639                 if (diff == null) {
640                     // this shouldn't happen, but
641                     continue;
642                 }
643                 if (first) {
644                     first = false;
645                 } else {
646                     sb.append(", ");
647                 }
648 
649                 sb.append(key);
650                 sb.append(":");
651                 sb.append(diff.toString());
652             }
653 
654             if (becameActive()) {
655                 if (!first) {
656                     sb.append(", ");
657                 }
658                 sb.append("(->active)");
659             } else if (becameInactive()) {
660                 if (!first) {
661                     sb.append(", ");
662                 }
663                 sb.append("(->inactive)");
664             }
665 
666             return sb.append("}").toString();
667         }
668 
669         /**
670          * Returns whether this diff indicates that this (automatic) rule became active.
671          */
becameActive()672         public boolean becameActive() {
673             // if the "to" side is true, then it became active
674             return mActiveDiff != null && mActiveDiff.to();
675         }
676 
677         /**
678          * Returns whether this diff indicates that this (automatic) rule became inactive.
679          */
becameInactive()680         public boolean becameInactive() {
681             // if the "to" side is false, then it became inactive
682             return mActiveDiff != null && !mActiveDiff.to();
683         }
684     }
685 
686     /**
687      * Diff class representing a change between two
688      * {@link android.service.notification.ZenDeviceEffects}.
689      */
690     public static class DeviceEffectsDiff extends BaseDiff {
691         public static final String FIELD_GRAYSCALE = "mGrayscale";
692         public static final String FIELD_SUPPRESS_AMBIENT_DISPLAY = "mSuppressAmbientDisplay";
693         public static final String FIELD_DIM_WALLPAPER = "mDimWallpaper";
694         public static final String FIELD_NIGHT_MODE = "mNightMode";
695         public static final String FIELD_DISABLE_AUTO_BRIGHTNESS = "mDisableAutoBrightness";
696         public static final String FIELD_DISABLE_TAP_TO_WAKE = "mDisableTapToWake";
697         public static final String FIELD_DISABLE_TILT_TO_WAKE = "mDisableTiltToWake";
698         public static final String FIELD_DISABLE_TOUCH = "mDisableTouch";
699         public static final String FIELD_MINIMIZE_RADIO_USAGE = "mMinimizeRadioUsage";
700         public static final String FIELD_MAXIMIZE_DOZE = "mMaximizeDoze";
701         public static final String FIELD_NIGHT_LIGHT = "mNightLight";
702         public static final String FIELD_EXTRA_EFFECTS = "mExtraEffects";
703         // NOTE: new field strings must match the variable names in ZenDeviceEffects
704 
705         /**
706          * Create a DeviceEffectsDiff representing the difference between two ZenDeviceEffects
707          * objects.
708          * @param from previous ZenDeviceEffects
709          * @param to new ZenDeviceEffects
710          * @return The diff between the two given ZenDeviceEffects
711          */
DeviceEffectsDiff(ZenDeviceEffects from, ZenDeviceEffects to)712         public DeviceEffectsDiff(ZenDeviceEffects from, ZenDeviceEffects to) {
713             super(from, to);
714             // Short-circuit the both-null case
715             if (from == null && to == null) {
716                 return;
717             }
718             if (hasExistenceChange()) {
719                 // either added or removed; return here. otherwise (they're not both null) there's
720                 // field diffs.
721                 return;
722             }
723 
724             // Compare all fields, knowing there's some diff and that neither is null.
725             if (from.shouldDisplayGrayscale() != to.shouldDisplayGrayscale()) {
726                 addField(FIELD_GRAYSCALE, new FieldDiff<>(from.shouldDisplayGrayscale(),
727                         to.shouldDisplayGrayscale()));
728             }
729             if (from.shouldSuppressAmbientDisplay() != to.shouldSuppressAmbientDisplay()) {
730                 addField(FIELD_SUPPRESS_AMBIENT_DISPLAY,
731                         new FieldDiff<>(from.shouldSuppressAmbientDisplay(),
732                                 to.shouldSuppressAmbientDisplay()));
733             }
734             if (from.shouldDimWallpaper() != to.shouldDimWallpaper()) {
735                 addField(FIELD_DIM_WALLPAPER, new FieldDiff<>(from.shouldDimWallpaper(),
736                         to.shouldDimWallpaper()));
737             }
738             if (from.shouldUseNightMode() != to.shouldUseNightMode()) {
739                 addField(FIELD_NIGHT_MODE, new FieldDiff<>(from.shouldUseNightMode(),
740                         to.shouldUseNightMode()));
741             }
742             if (from.shouldDisableAutoBrightness() != to.shouldDisableAutoBrightness()) {
743                 addField(FIELD_DISABLE_AUTO_BRIGHTNESS,
744                         new FieldDiff<>(from.shouldDisableAutoBrightness(),
745                                 to.shouldDisableAutoBrightness()));
746             }
747             if (from.shouldDisableTapToWake() != to.shouldDisableTapToWake()) {
748                 addField(FIELD_DISABLE_TAP_TO_WAKE, new FieldDiff<>(from.shouldDisableTapToWake(),
749                         to.shouldDisableTapToWake()));
750             }
751             if (from.shouldDisableTiltToWake() != to.shouldDisableTiltToWake()) {
752                 addField(FIELD_DISABLE_TILT_TO_WAKE,
753                         new FieldDiff<>(from.shouldDisableTiltToWake(),
754                                 to.shouldDisableTiltToWake()));
755             }
756             if (from.shouldDisableTouch() != to.shouldDisableTouch()) {
757                 addField(FIELD_DISABLE_TOUCH, new FieldDiff<>(from.shouldDisableTouch(),
758                         to.shouldDisableTouch()));
759             }
760             if (from.shouldMinimizeRadioUsage() != to.shouldMinimizeRadioUsage()) {
761                 addField(FIELD_MINIMIZE_RADIO_USAGE,
762                         new FieldDiff<>(from.shouldMinimizeRadioUsage(),
763                                 to.shouldMinimizeRadioUsage()));
764             }
765             if (from.shouldMaximizeDoze() != to.shouldMaximizeDoze()) {
766                 addField(FIELD_MAXIMIZE_DOZE, new FieldDiff<>(from.shouldMaximizeDoze(),
767                         to.shouldMaximizeDoze()));
768             }
769             if (from.shouldUseNightLight() != to.shouldUseNightLight()) {
770                 addField(
771                         FIELD_NIGHT_LIGHT,
772                         new FieldDiff<>(from.shouldUseNightLight(), to.shouldUseNightLight()));
773             }
774             if (!Objects.equals(from.getExtraEffects(), to.getExtraEffects())) {
775                 addField(FIELD_EXTRA_EFFECTS, new FieldDiff<>(from.getExtraEffects(),
776                         to.getExtraEffects()));
777             }
778         }
779 
780         /**
781          * Returns whether this object represents an actual diff.
782          */
783         @Override
hasDiff()784         public boolean hasDiff() {
785             return hasExistenceChange() || hasFieldDiffs();
786         }
787 
788         @Override
toString()789         public String toString() {
790             final StringBuilder sb = new StringBuilder("ZenDeviceEffectsDiff{");
791             if (!hasDiff()) {
792                 sb.append("no changes");
793             }
794 
795             // If added or deleted, we just append that.
796             if (hasExistenceChange()) {
797                 if (wasAdded()) {
798                     sb.append("added");
799                 } else if (wasRemoved()) {
800                     sb.append("removed");
801                 }
802             }
803 
804             // Append all of the individual field diffs
805             boolean first = true;
806             for (String key : fieldNamesWithDiff()) {
807                 FieldDiff diff = getDiffForField(key);
808                 if (diff == null) {
809                     // The diff should not have null diffs added, but we add this to be defensive.
810                     continue;
811                 }
812                 if (first) {
813                     first = false;
814                 } else {
815                     sb.append(", ");
816                 }
817 
818                 sb.append(key);
819                 sb.append(":");
820                 sb.append(diff);
821             }
822 
823             return sb.append("}").toString();
824         }
825     }
826 
827     /**
828      * Diff class representing a change between two {@link android.service.notification.ZenPolicy}.
829      */
830     public static class PolicyDiff extends BaseDiff {
831         public static final String FIELD_PRIORITY_CATEGORY_REMINDERS =
832                 "mPriorityCategories_Reminders";
833         public static final String FIELD_PRIORITY_CATEGORY_EVENTS = "mPriorityCategories_Events";
834         public static final String FIELD_PRIORITY_CATEGORY_MESSAGES =
835                 "mPriorityCategories_Messages";
836         public static final String FIELD_PRIORITY_CATEGORY_CALLS = "mPriorityCategories_Calls";
837         public static final String FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS =
838                 "mPriorityCategories_RepeatCallers";
839         public static final String FIELD_PRIORITY_CATEGORY_ALARMS = "mPriorityCategories_Alarms";
840         public static final String FIELD_PRIORITY_CATEGORY_MEDIA = "mPriorityCategories_Media";
841         public static final String FIELD_PRIORITY_CATEGORY_SYSTEM = "mPriorityCategories_System";
842         public static final String FIELD_PRIORITY_CATEGORY_CONVERSATIONS =
843                 "mPriorityCategories_Conversations";
844 
845         public static final String FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT =
846                 "mVisualEffects_FullScreenIntent";
847         public static final String FIELD_VISUAL_EFFECT_LIGHTS = "mVisualEffects_Lights";
848         public static final String FIELD_VISUAL_EFFECT_PEEK = "mVisualEffects_Peek";
849         public static final String FIELD_VISUAL_EFFECT_STATUS_BAR = "mVisualEffects_StatusBar";
850         public static final String FIELD_VISUAL_EFFECT_BADGE = "mVisualEffects_Badge";
851         public static final String FIELD_VISUAL_EFFECT_AMBIENT = "mVisualEffects_Ambient";
852         public static final String FIELD_VISUAL_EFFECT_NOTIFICATION_LIST =
853                 "mVisualEffects_NotificationList";
854 
855         public static final String FIELD_PRIORITY_MESSAGES = "mPriorityMessages";
856         public static final String FIELD_PRIORITY_CALLS = "mPriorityCalls";
857         public static final String FIELD_CONVERSATION_SENDERS = "mConversationSenders";
858         public static final String FIELD_ALLOW_CHANNELS = "mAllowChannels";
859 
860         /**
861          * Create a PolicyDiff representing the difference between two ZenPolicy objects.
862          *
863          * @param from previous ZenPolicy
864          * @param to   new ZenPolicy
865          * @return The diff between the two given ZenPolicy
866          */
PolicyDiff(ZenPolicy from, ZenPolicy to)867         public PolicyDiff(ZenPolicy from, ZenPolicy to) {
868             super(from, to);
869             // Short-circuit the both-null case
870             if (from == null && to == null) {
871                 return;
872             }
873             if (hasExistenceChange()) {
874                 // either added or removed; return here. otherwise (they're not both null) there's
875                 // field diffs.
876                 return;
877             }
878 
879             // Compare all fields, knowing there's some diff and that neither is null.
880             if (from.getPriorityCategoryReminders() != to.getPriorityCategoryReminders()) {
881                 addField(FIELD_PRIORITY_CATEGORY_REMINDERS,
882                         new FieldDiff<>(from.getPriorityCategoryReminders(),
883                                 to.getPriorityCategoryReminders()));
884             }
885             if (from.getPriorityCategoryEvents() != to.getPriorityCategoryEvents()) {
886                 addField(FIELD_PRIORITY_CATEGORY_EVENTS,
887                         new FieldDiff<>(from.getPriorityCategoryEvents(),
888                                 to.getPriorityCategoryEvents()));
889             }
890             if (from.getPriorityCategoryMessages() != to.getPriorityCategoryMessages()) {
891                 addField(FIELD_PRIORITY_CATEGORY_MESSAGES,
892                         new FieldDiff<>(from.getPriorityCategoryMessages(),
893                                 to.getPriorityCategoryMessages()));
894             }
895             if (from.getPriorityCategoryCalls() != to.getPriorityCategoryCalls()) {
896                 addField(FIELD_PRIORITY_CATEGORY_CALLS,
897                         new FieldDiff<>(from.getPriorityCategoryCalls(),
898                                 to.getPriorityCategoryCalls()));
899             }
900             if (from.getPriorityCategoryRepeatCallers() != to.getPriorityCategoryRepeatCallers()) {
901                 addField(FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS,
902                         new FieldDiff<>(from.getPriorityCategoryRepeatCallers(),
903                                 to.getPriorityCategoryRepeatCallers()));
904             }
905             if (from.getPriorityCategoryAlarms() != to.getPriorityCategoryAlarms()) {
906                 addField(FIELD_PRIORITY_CATEGORY_ALARMS,
907                         new FieldDiff<>(from.getPriorityCategoryAlarms(),
908                                 to.getPriorityCategoryAlarms()));
909             }
910             if (from.getPriorityCategoryMedia() != to.getPriorityCategoryMedia()) {
911                 addField(FIELD_PRIORITY_CATEGORY_MEDIA,
912                         new FieldDiff<>(from.getPriorityCategoryMedia(),
913                                 to.getPriorityCategoryMedia()));
914             }
915             if (from.getPriorityCategorySystem() != to.getPriorityCategorySystem()) {
916                 addField(FIELD_PRIORITY_CATEGORY_SYSTEM,
917                         new FieldDiff<>(from.getPriorityCategorySystem(),
918                                 to.getPriorityCategorySystem()));
919             }
920             if (from.getPriorityCategoryConversations() != to.getPriorityCategoryConversations()) {
921                 addField(FIELD_PRIORITY_CATEGORY_CONVERSATIONS,
922                         new FieldDiff<>(from.getPriorityCategoryConversations(),
923                                 to.getPriorityCategoryConversations()));
924             }
925             if (from.getVisualEffectFullScreenIntent() != to.getVisualEffectFullScreenIntent()) {
926                 addField(FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT,
927                         new FieldDiff<>(from.getVisualEffectFullScreenIntent(),
928                                 to.getVisualEffectFullScreenIntent()));
929             }
930             if (from.getVisualEffectLights() != to.getVisualEffectLights()) {
931                 addField(FIELD_VISUAL_EFFECT_LIGHTS,
932                         new FieldDiff<>(from.getVisualEffectLights(), to.getVisualEffectLights()));
933             }
934             if (from.getVisualEffectPeek() != to.getVisualEffectPeek()) {
935                 addField(FIELD_VISUAL_EFFECT_PEEK, new FieldDiff<>(from.getVisualEffectPeek(),
936                         to.getVisualEffectPeek()));
937             }
938             if (from.getVisualEffectStatusBar() != to.getVisualEffectStatusBar()) {
939                 addField(FIELD_VISUAL_EFFECT_STATUS_BAR,
940                         new FieldDiff<>(from.getVisualEffectStatusBar(),
941                                 to.getVisualEffectStatusBar()));
942             }
943             if (from.getVisualEffectBadge() != to.getVisualEffectBadge()) {
944                 addField(FIELD_VISUAL_EFFECT_BADGE, new FieldDiff<>(from.getVisualEffectBadge(),
945                         to.getVisualEffectBadge()));
946             }
947             if (from.getVisualEffectAmbient() != to.getVisualEffectAmbient()) {
948                 addField(FIELD_VISUAL_EFFECT_AMBIENT, new FieldDiff<>(from.getVisualEffectAmbient(),
949                         to.getVisualEffectAmbient()));
950             }
951             if (from.getVisualEffectNotificationList() != to.getVisualEffectNotificationList()) {
952                 addField(FIELD_VISUAL_EFFECT_NOTIFICATION_LIST,
953                         new FieldDiff<>(from.getVisualEffectNotificationList(),
954                                 to.getVisualEffectNotificationList()));
955             }
956             if (from.getPriorityMessageSenders() != to.getPriorityMessageSenders()) {
957                 addField(FIELD_PRIORITY_MESSAGES, new FieldDiff<>(from.getPriorityMessageSenders(),
958                         to.getPriorityMessageSenders()));
959             }
960             if (from.getPriorityCallSenders() != to.getPriorityCallSenders()) {
961                 addField(FIELD_PRIORITY_CALLS, new FieldDiff<>(from.getPriorityCallSenders(),
962                         to.getPriorityCallSenders()));
963             }
964             if (from.getPriorityConversationSenders() != to.getPriorityConversationSenders()) {
965                 addField(FIELD_CONVERSATION_SENDERS,
966                         new FieldDiff<>(from.getPriorityConversationSenders(),
967                                 to.getPriorityConversationSenders()));
968             }
969             if (from.getPriorityChannelsAllowed() != to.getPriorityChannelsAllowed()) {
970                 addField(FIELD_ALLOW_CHANNELS, new FieldDiff<>(from.getPriorityChannelsAllowed(),
971                         to.getPriorityChannelsAllowed()));
972             }
973         }
974 
975         /**
976          * Returns whether this object represents an actual diff.
977          */
978         @Override
hasDiff()979         public boolean hasDiff() {
980             return hasExistenceChange() || hasFieldDiffs();
981         }
982 
983         @Override
toString()984         public String toString() {
985             final StringBuilder sb = new StringBuilder("ZenPolicyDiff{");
986             // The diff should not have null diffs added, but we add this to be defensive.
987             if (!hasDiff()) {
988                 sb.append("no changes");
989             }
990 
991             // If added or deleted, we just append that.
992             if (hasExistenceChange()) {
993                 if (wasAdded()) {
994                     sb.append("added");
995                 } else if (wasRemoved()) {
996                     sb.append("removed");
997                 }
998             }
999 
1000             // Go through all of the individual fields
1001             boolean first = true;
1002             for (String key : fieldNamesWithDiff()) {
1003                 FieldDiff diff = getDiffForField(key);
1004                 if (diff == null) {
1005                     // this shouldn't happen...
1006                     continue;
1007                 }
1008                 if (first) {
1009                     first = false;
1010                 } else {
1011                     sb.append(", ");
1012                 }
1013 
1014                 sb.append(key);
1015                 sb.append(":");
1016                 sb.append(diff);
1017             }
1018 
1019             return sb.append("}").toString();
1020         }
1021     }
1022 
1023 }
1024