script-astra/Android/Sdk/sources/android-35/android/service/notification/ZenModeDiff.java

650 lines
25 KiB
Java
Raw Normal View History

2025-01-20 15:15:20 +00:00
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.notification;
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.Flags;
import android.util.ArrayMap;
import android.util.ArraySet;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
import java.util.Set;
/**
* ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their
* subcomponents (automatic and manual ZenRules).
*
* <p>Note that this class is intended to detect <em>meaningful</em> differences, so objects that
* are not identical (as per their {@code equals()} implementation) can still produce an empty diff
* if only "metadata" fields are updated.
*
* @hide
*/
public class ZenModeDiff {
/**
* Enum representing whether the existence of a config or rule has changed (added or removed,
* or "none" meaning there is no change, which may either mean both null, or there exists a
* diff in fields rather than add/remove).
*/
@IntDef(value = {
NONE,
ADDED,
REMOVED,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ExistenceChange{}
public static final int NONE = 0;
public static final int ADDED = 1;
public static final int REMOVED = 2;
/**
* Diff class representing an individual field diff.
* @param <T> The type of the field.
*/
public static class FieldDiff<T> {
private final T mFrom;
private final T mTo;
/**
* Constructor to create a FieldDiff object with the given values.
* @param from from (old) value
* @param to to (new) value
*/
public FieldDiff(@Nullable T from, @Nullable T to) {
mFrom = from;
mTo = to;
}
/**
* Get the "from" value
*/
public T from() {
return mFrom;
}
/**
* Get the "to" value
*/
public T to() {
return mTo;
}
/**
* Get the string representation of this field diff, in the form of "from->to".
*/
@Override
public String toString() {
return mFrom + "->" + mTo;
}
/**
* Returns whether this represents an actual diff.
*/
public boolean hasDiff() {
// note that Objects.equals handles null values gracefully.
return !Objects.equals(mFrom, mTo);
}
}
/**
* Base diff class that contains info about whether something was added, and a set of named
* fields that changed.
* Extend for diffs of specific types of objects.
*/
private abstract static class BaseDiff {
// Whether the diff was added or removed
@ExistenceChange private int mExists = NONE;
// Map from field name to diffs for any standalone fields in the object.
private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>();
// Functions for actually diffing objects and string representations have to be implemented
// by subclasses.
/**
* Return whether this diff represents any changes.
*/
public abstract boolean hasDiff();
/**
* Return a string representation of the diff.
*/
public abstract String toString();
/**
* Constructor that takes the two objects meant to be compared. This constructor sets
* whether there is an existence change (added or removed).
* @param from previous Object
* @param to new Object
*/
BaseDiff(Object from, Object to) {
if (from == null) {
if (to != null) {
mExists = ADDED;
}
// If both are null, there isn't an existence change; callers/inheritors must handle
// the both null case.
} else if (to == null) {
// in this case, we know that from != null
mExists = REMOVED;
}
// Subclasses should implement the actual diffing functionality in their own
// constructors.
}
/**
* Add a diff for a specific field to the map.
* @param name field name
* @param diff FieldDiff object representing the diff
*/
final void addField(String name, FieldDiff diff) {
mFields.put(name, diff);
}
/**
* Returns whether this diff represents a config being newly added.
*/
public final boolean wasAdded() {
return mExists == ADDED;
}
/**
* Returns whether this diff represents a config being removed.
*/
public final boolean wasRemoved() {
return mExists == REMOVED;
}
/**
* Returns whether this diff represents an object being either added or removed.
*/
public final boolean hasExistenceChange() {
return mExists != NONE;
}
/**
* Returns whether there are any individual field diffs.
*/
public final boolean hasFieldDiffs() {
return mFields.size() > 0;
}
/**
* Returns the diff for the specific named field if it exists
*/
public final FieldDiff getDiffForField(String name) {
return mFields.getOrDefault(name, null);
}
/**
* Get the set of all field names with some diff.
*/
public final Set<String> fieldNamesWithDiff() {
return mFields.keySet();
}
}
/**
* Diff class representing a diff between two ZenModeConfigs.
*/
public static class ConfigDiff extends BaseDiff {
// Rules. Automatic rule map is keyed by the rule name.
private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>();
private RuleDiff mManualRuleDiff;
// Field name constants
public static final String FIELD_USER = "user";
public static final String FIELD_ALLOW_ALARMS = "allowAlarms";
public static final String FIELD_ALLOW_MEDIA = "allowMedia";
public static final String FIELD_ALLOW_SYSTEM = "allowSystem";
public static final String FIELD_ALLOW_CALLS = "allowCalls";
public static final String FIELD_ALLOW_REMINDERS = "allowReminders";
public static final String FIELD_ALLOW_EVENTS = "allowEvents";
public static final String FIELD_ALLOW_REPEAT_CALLERS = "allowRepeatCallers";
public static final String FIELD_ALLOW_MESSAGES = "allowMessages";
public static final String FIELD_ALLOW_CONVERSATIONS = "allowConversations";
public static final String FIELD_ALLOW_CALLS_FROM = "allowCallsFrom";
public static final String FIELD_ALLOW_MESSAGES_FROM = "allowMessagesFrom";
public static final String FIELD_ALLOW_CONVERSATIONS_FROM = "allowConversationsFrom";
public static final String FIELD_SUPPRESSED_VISUAL_EFFECTS = "suppressedVisualEffects";
public static final String FIELD_ARE_CHANNELS_BYPASSING_DND = "areChannelsBypassingDnd";
public static final String FIELD_ALLOW_PRIORITY_CHANNELS = "allowPriorityChannels";
private static final Set<String> PEOPLE_TYPE_FIELDS =
Set.of(FIELD_ALLOW_CALLS_FROM, FIELD_ALLOW_MESSAGES_FROM);
/**
* Create a diff that contains diffs between the "from" and "to" ZenModeConfigs.
*
* @param from previous ZenModeConfig
* @param to new ZenModeConfig
*/
public ConfigDiff(ZenModeConfig from, ZenModeConfig to) {
super(from, to);
// If both are null skip
if (from == null && to == null) {
return;
}
if (hasExistenceChange()) {
// either added or removed; return here. otherwise (they're not both null) there's
// field diffs.
return;
}
// Now we compare all the fields, knowing there's a diff and that neither is null
if (from.user != to.user) {
addField(FIELD_USER, new FieldDiff<>(from.user, to.user));
}
if (from.allowAlarms != to.allowAlarms) {
addField(FIELD_ALLOW_ALARMS, new FieldDiff<>(from.allowAlarms, to.allowAlarms));
}
if (from.allowMedia != to.allowMedia) {
addField(FIELD_ALLOW_MEDIA, new FieldDiff<>(from.allowMedia, to.allowMedia));
}
if (from.allowSystem != to.allowSystem) {
addField(FIELD_ALLOW_SYSTEM, new FieldDiff<>(from.allowSystem, to.allowSystem));
}
if (from.allowCalls != to.allowCalls) {
addField(FIELD_ALLOW_CALLS, new FieldDiff<>(from.allowCalls, to.allowCalls));
}
if (from.allowReminders != to.allowReminders) {
addField(FIELD_ALLOW_REMINDERS,
new FieldDiff<>(from.allowReminders, to.allowReminders));
}
if (from.allowEvents != to.allowEvents) {
addField(FIELD_ALLOW_EVENTS, new FieldDiff<>(from.allowEvents, to.allowEvents));
}
if (from.allowRepeatCallers != to.allowRepeatCallers) {
addField(FIELD_ALLOW_REPEAT_CALLERS,
new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers));
}
if (from.allowMessages != to.allowMessages) {
addField(FIELD_ALLOW_MESSAGES,
new FieldDiff<>(from.allowMessages, to.allowMessages));
}
if (from.allowConversations != to.allowConversations) {
addField(FIELD_ALLOW_CONVERSATIONS,
new FieldDiff<>(from.allowConversations, to.allowConversations));
}
if (from.allowCallsFrom != to.allowCallsFrom) {
addField(FIELD_ALLOW_CALLS_FROM,
new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom));
}
if (from.allowMessagesFrom != to.allowMessagesFrom) {
addField(FIELD_ALLOW_MESSAGES_FROM,
new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom));
}
if (from.allowConversationsFrom != to.allowConversationsFrom) {
addField(FIELD_ALLOW_CONVERSATIONS_FROM,
new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom));
}
if (from.suppressedVisualEffects != to.suppressedVisualEffects) {
addField(FIELD_SUPPRESSED_VISUAL_EFFECTS,
new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects));
}
if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) {
addField(FIELD_ARE_CHANNELS_BYPASSING_DND,
new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd));
}
if (Flags.modesApi()) {
if (from.allowPriorityChannels != to.allowPriorityChannels) {
addField(FIELD_ALLOW_PRIORITY_CHANNELS,
new FieldDiff<>(from.allowPriorityChannels, to.allowPriorityChannels));
}
}
// Compare automatic and manual rules
final ArraySet<String> allRules = new ArraySet<>();
addKeys(allRules, from.automaticRules);
addKeys(allRules, to.automaticRules);
final int num = allRules.size();
for (int i = 0; i < num; i++) {
final String rule = allRules.valueAt(i);
final ZenModeConfig.ZenRule
fromRule = from.automaticRules != null ? from.automaticRules.get(rule)
: null;
final ZenModeConfig.ZenRule
toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null;
RuleDiff ruleDiff = new RuleDiff(fromRule, toRule);
if (ruleDiff.hasDiff()) {
mAutomaticRulesDiff.put(rule, ruleDiff);
}
}
// If there's no diff this may turn out to be null, but that's also fine
RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule);
if (manualRuleDiff.hasDiff()) {
mManualRuleDiff = manualRuleDiff;
}
}
private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) {
if (map != null) {
for (int i = 0; i < map.size(); i++) {
set.add(map.keyAt(i));
}
}
}
/**
* Returns whether this diff object contains any diffs in any field.
*/
@Override
public boolean hasDiff() {
return hasExistenceChange()
|| hasFieldDiffs()
|| mManualRuleDiff != null
|| mAutomaticRulesDiff.size() > 0;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Diff[");
if (!hasDiff()) {
sb.append("no changes");
}
// If added or deleted, then that's just the end of it
if (hasExistenceChange()) {
if (wasAdded()) {
sb.append("added");
} else if (wasRemoved()) {
sb.append("removed");
}
}
// Handle top-level field change
boolean first = true;
for (String key : fieldNamesWithDiff()) {
FieldDiff diff = getDiffForField(key);
if (diff == null) {
// this shouldn't happen, but
continue;
}
if (first) {
first = false;
} else {
sb.append(",\n");
}
// Some special handling for people- and conversation-type fields for readability
if (PEOPLE_TYPE_FIELDS.contains(key)) {
sb.append(key);
sb.append(":");
sb.append(ZenModeConfig.sourceToString((int) diff.from()));
sb.append("->");
sb.append(ZenModeConfig.sourceToString((int) diff.to()));
} else if (key.equals(FIELD_ALLOW_CONVERSATIONS_FROM)) {
sb.append(key);
sb.append(":");
sb.append(ZenPolicy.conversationTypeToString((int) diff.from()));
sb.append("->");
sb.append(ZenPolicy.conversationTypeToString((int) diff.to()));
} else {
sb.append(key);
sb.append(":");
sb.append(diff);
}
}
// manual rule
if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) {
if (first) {
first = false;
} else {
sb.append(",\n");
}
sb.append("manualRule:");
sb.append(mManualRuleDiff);
}
// automatic rules
for (String rule : mAutomaticRulesDiff.keySet()) {
RuleDiff diff = mAutomaticRulesDiff.get(rule);
if (diff != null && diff.hasDiff()) {
if (first) {
first = false;
} else {
sb.append(",\n");
}
sb.append("automaticRule[");
sb.append(rule);
sb.append("]:");
sb.append(diff);
}
}
return sb.append(']').toString();
}
/**
* Get the diff in manual rule, if it exists.
*/
public RuleDiff getManualRuleDiff() {
return mManualRuleDiff;
}
/**
* Get the full map of automatic rule diffs, or null if there are no diffs.
*/
public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() {
return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null;
}
}
/**
* Diff class representing a change between two ZenRules.
*/
public static class RuleDiff extends BaseDiff {
public static final String FIELD_ENABLED = "enabled";
public static final String FIELD_SNOOZING = "snoozing";
public static final String FIELD_NAME = "name";
public static final String FIELD_ZEN_MODE = "zenMode";
public static final String FIELD_CONDITION_ID = "conditionId";
public static final String FIELD_CONDITION = "condition";
public static final String FIELD_COMPONENT = "component";
public static final String FIELD_CONFIGURATION_ACTIVITY = "configurationActivity";
public static final String FIELD_ID = "id";
public static final String FIELD_CREATION_TIME = "creationTime";
public static final String FIELD_ENABLER = "enabler";
public static final String FIELD_ZEN_POLICY = "zenPolicy";
public static final String FIELD_ZEN_DEVICE_EFFECTS = "zenDeviceEffects";
public static final String FIELD_MODIFIED = "modified";
public static final String FIELD_PKG = "pkg";
public static final String FIELD_ALLOW_MANUAL = "allowManualInvocation";
public static final String FIELD_ICON_RES = "iconResName";
public static final String FIELD_TRIGGER_DESCRIPTION = "triggerDescription";
public static final String FIELD_TYPE = "type";
// NOTE: new field strings must match the variable names in ZenModeConfig.ZenRule
// Special field to track whether this rule became active or inactive
FieldDiff<Boolean> mActiveDiff;
/**
* Create a RuleDiff representing the difference between two ZenRule objects.
* @param from previous ZenRule
* @param to new ZenRule
* @return The diff between the two given ZenRules
*/
public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) {
super(from, to);
// Short-circuit the both-null case
if (from == null && to == null) {
return;
}
// Even if added or removed, there may be a change in whether or not it was active.
// This only applies to automatic rules.
boolean fromActive = from != null ? from.isAutomaticActive() : false;
boolean toActive = to != null ? to.isAutomaticActive() : false;
if (fromActive != toActive) {
mActiveDiff = new FieldDiff<>(fromActive, toActive);
}
// Return if the diff was added or removed
if (hasExistenceChange()) {
return;
}
if (from.enabled != to.enabled) {
addField(FIELD_ENABLED, new FieldDiff<>(from.enabled, to.enabled));
}
if (from.snoozing != to.snoozing) {
addField(FIELD_SNOOZING, new FieldDiff<>(from.snoozing, to.snoozing));
}
if (!Objects.equals(from.name, to.name)) {
addField(FIELD_NAME, new FieldDiff<>(from.name, to.name));
}
if (from.zenMode != to.zenMode) {
addField(FIELD_ZEN_MODE, new FieldDiff<>(from.zenMode, to.zenMode));
}
if (!Objects.equals(from.conditionId, to.conditionId)) {
addField(FIELD_CONDITION_ID, new FieldDiff<>(from.conditionId,
to.conditionId));
}
if (!Objects.equals(from.condition, to.condition)) {
addField(FIELD_CONDITION, new FieldDiff<>(from.condition, to.condition));
}
if (!Objects.equals(from.component, to.component)) {
addField(FIELD_COMPONENT, new FieldDiff<>(from.component, to.component));
}
if (!Objects.equals(from.configurationActivity, to.configurationActivity)) {
addField(FIELD_CONFIGURATION_ACTIVITY, new FieldDiff<>(
from.configurationActivity, to.configurationActivity));
}
if (!Objects.equals(from.id, to.id)) {
addField(FIELD_ID, new FieldDiff<>(from.id, to.id));
}
if (from.creationTime != to.creationTime) {
addField(FIELD_CREATION_TIME,
new FieldDiff<>(from.creationTime, to.creationTime));
}
if (!Objects.equals(from.enabler, to.enabler)) {
addField(FIELD_ENABLER, new FieldDiff<>(from.enabler, to.enabler));
}
if (!Objects.equals(from.zenPolicy, to.zenPolicy)) {
addField(FIELD_ZEN_POLICY, new FieldDiff<>(from.zenPolicy, to.zenPolicy));
}
if (from.modified != to.modified) {
addField(FIELD_MODIFIED, new FieldDiff<>(from.modified, to.modified));
}
if (!Objects.equals(from.pkg, to.pkg)) {
addField(FIELD_PKG, new FieldDiff<>(from.pkg, to.pkg));
}
if (android.app.Flags.modesApi()) {
if (!Objects.equals(from.zenDeviceEffects, to.zenDeviceEffects)) {
addField(FIELD_ZEN_DEVICE_EFFECTS,
new FieldDiff<>(from.zenDeviceEffects, to.zenDeviceEffects));
}
if (!Objects.equals(from.triggerDescription, to.triggerDescription)) {
addField(FIELD_TRIGGER_DESCRIPTION,
new FieldDiff<>(from.triggerDescription, to.triggerDescription));
}
if (from.type != to.type) {
addField(FIELD_TYPE, new FieldDiff<>(from.type, to.type));
}
if (from.allowManualInvocation != to.allowManualInvocation) {
addField(FIELD_ALLOW_MANUAL,
new FieldDiff<>(from.allowManualInvocation, to.allowManualInvocation));
}
if (!Objects.equals(from.iconResName, to.iconResName)) {
addField(FIELD_ICON_RES, new FieldDiff<>(from.iconResName, to.iconResName));
}
}
}
/**
* Returns whether this object represents an actual diff.
*/
@Override
public boolean hasDiff() {
return hasExistenceChange() || hasFieldDiffs();
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("ZenRuleDiff{");
// If there's no diff, probably we haven't actually let this object continue existing
// but might as well handle this case.
if (!hasDiff()) {
sb.append("no changes");
}
// If added or deleted, then that's just the end of it
if (hasExistenceChange()) {
if (wasAdded()) {
sb.append("added");
} else if (wasRemoved()) {
sb.append("removed");
}
}
// Go through all of the individual fields
boolean first = true;
for (String key : fieldNamesWithDiff()) {
FieldDiff diff = getDiffForField(key);
if (diff == null) {
// this shouldn't happen, but
continue;
}
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(key);
sb.append(":");
sb.append(diff);
}
if (becameActive()) {
if (!first) {
sb.append(", ");
}
sb.append("(->active)");
} else if (becameInactive()) {
if (!first) {
sb.append(", ");
}
sb.append("(->inactive)");
}
return sb.append("}").toString();
}
/**
* Returns whether this diff indicates that this (automatic) rule became active.
*/
public boolean becameActive() {
// if the "to" side is true, then it became active
return mActiveDiff != null && mActiveDiff.to();
}
/**
* Returns whether this diff indicates that this (automatic) rule became inactive.
*/
public boolean becameInactive() {
// if the "to" side is false, then it became inactive
return mActiveDiff != null && !mActiveDiff.to();
}
}
}