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