diff options
| author | 2023-11-01 17:11:26 -0400 | |
|---|---|---|
| committer | 2023-11-07 20:11:25 +0000 | |
| commit | 2cd67d44cc2cea224c8a1414a7b8761a0bef5219 (patch) | |
| tree | b6493f50bb0cb974e3a130ba872606f12a95432c | |
| parent | c9ef0de2a3fdc8e177da5c6aa5ac2c8c35095b68 (diff) | |
Add new state to AutomaticZenRule
Test: ZenModeConfigTest
Test: (cts) NotificationManagerZenTest
Test: (cts) AutomaticZenRuleTest
Fixes: 308672543
Fixes: 308672010
Fixes: 308673538
Bug: 308674037
Change-Id: I66be27cce62a144289c5bd45c18bf6064ca2fe51
10 files changed, 706 insertions, 18 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index e8a6ac944076..124f04345f6d 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5315,11 +5315,15 @@ package android.app { method public android.net.Uri getConditionId(); method @Nullable public android.content.ComponentName getConfigurationActivity(); method public long getCreationTime(); + method @FlaggedApi("android.app.modes_api") @DrawableRes public int getIconResId(); method public int getInterruptionFilter(); method public String getName(); method public android.content.ComponentName getOwner(); + method @FlaggedApi("android.app.modes_api") @Nullable public String getTriggerDescription(); + method @FlaggedApi("android.app.modes_api") public int getType(); method public android.service.notification.ZenPolicy getZenPolicy(); method public boolean isEnabled(); + method @FlaggedApi("android.app.modes_api") public boolean isManualInvocationAllowed(); method public void setConditionId(android.net.Uri); method public void setConfigurationActivity(@Nullable android.content.ComponentName); method public void setEnabled(boolean); @@ -5328,6 +5332,32 @@ package android.app { method public void setZenPolicy(android.service.notification.ZenPolicy); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.app.AutomaticZenRule> CREATOR; + field @FlaggedApi("android.app.modes_api") public static final int TYPE_BEDTIME = 3; // 0x3 + field @FlaggedApi("android.app.modes_api") public static final int TYPE_DRIVING = 4; // 0x4 + field @FlaggedApi("android.app.modes_api") public static final int TYPE_IMMERSIVE = 5; // 0x5 + field @FlaggedApi("android.app.modes_api") public static final int TYPE_MANAGED = 7; // 0x7 + field @FlaggedApi("android.app.modes_api") public static final int TYPE_OTHER = 0; // 0x0 + field @FlaggedApi("android.app.modes_api") public static final int TYPE_SCHEDULE_CALENDAR = 2; // 0x2 + field @FlaggedApi("android.app.modes_api") public static final int TYPE_SCHEDULE_TIME = 1; // 0x1 + field @FlaggedApi("android.app.modes_api") public static final int TYPE_THEATER = 6; // 0x6 + field @FlaggedApi("android.app.modes_api") public static final int TYPE_UNKNOWN = -1; // 0xffffffff + } + + @FlaggedApi("android.app.modes_api") public static final class AutomaticZenRule.Builder { + ctor public AutomaticZenRule.Builder(@NonNull android.app.AutomaticZenRule); + ctor public AutomaticZenRule.Builder(@NonNull String, @NonNull android.net.Uri); + method @NonNull public android.app.AutomaticZenRule build(); + method @NonNull public android.app.AutomaticZenRule.Builder setConditionId(@NonNull android.net.Uri); + method @NonNull public android.app.AutomaticZenRule.Builder setConfigurationActivity(@Nullable android.content.ComponentName); + method @NonNull public android.app.AutomaticZenRule.Builder setEnabled(boolean); + method @NonNull public android.app.AutomaticZenRule.Builder setIconResId(@DrawableRes int); + method @NonNull public android.app.AutomaticZenRule.Builder setInterruptionFilter(int); + method @NonNull public android.app.AutomaticZenRule.Builder setManualInvocationAllowed(boolean); + method @NonNull public android.app.AutomaticZenRule.Builder setName(@NonNull String); + method @NonNull public android.app.AutomaticZenRule.Builder setOwner(@Nullable android.content.ComponentName); + method @NonNull public android.app.AutomaticZenRule.Builder setTriggerDescription(@Nullable String); + method @NonNull public android.app.AutomaticZenRule.Builder setType(int); + method @NonNull public android.app.AutomaticZenRule.Builder setZenPolicy(@Nullable android.service.notification.ZenPolicy); } public final class BackgroundServiceStartNotAllowedException extends android.app.ServiceStartNotAllowedException implements android.os.Parcelable { diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java index 7bfb1b5c1ba6..919e084002ea 100644 --- a/core/java/android/app/AutomaticZenRule.java +++ b/core/java/android/app/AutomaticZenRule.java @@ -16,16 +16,23 @@ package android.app; +import android.annotation.DrawableRes; +import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.NotificationManager.InterruptionFilter; +import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.service.notification.Condition; import android.service.notification.ZenPolicy; +import android.view.WindowInsetsController; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Objects; /** @@ -36,7 +43,67 @@ public final class AutomaticZenRule implements Parcelable { private static final int ENABLED = 1; /* @hide */ private static final int DISABLED = 0; - private boolean enabled = false; + + /** + * Rule is of an unknown type. This is the default value if not provided by the owning app. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int TYPE_UNKNOWN = -1; + /** + * Rule is of a known type, but not one of the specific types. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int TYPE_OTHER = 0; + /** + * The type for rules triggered according to a time-based schedule. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int TYPE_SCHEDULE_TIME = 1; + /** + * The type for rules triggered by calendar events. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int TYPE_SCHEDULE_CALENDAR = 2; + /** + * The type for rules triggered by bedtime/sleeping, like time of day, or snore detection. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int TYPE_BEDTIME = 3; + /** + * The type for rules triggered by driving detection, like Bluetooth connections or vehicle + * sounds. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int TYPE_DRIVING = 4; + /** + * The type for rules triggered by the user entering an immersive activity, like opening an app + * using {@link WindowInsetsController#hide(int)}. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int TYPE_IMMERSIVE = 5; + /** + * The type for rules that have a {@link ZenPolicy} that implies that the + * device should not make sound and potentially hide some visual effects; may be triggered + * when entering a location where silence is requested, like a theater. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int TYPE_THEATER = 6; + /** + * The type for rules created and managed by a device owner. These rules may not be fully + * editable by the device user. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public static final int TYPE_MANAGED = 7; + + /** @hide */ + @IntDef(prefix = { "TYPE_" }, value = { + TYPE_UNKNOWN, TYPE_OTHER, TYPE_SCHEDULE_TIME, TYPE_SCHEDULE_CALENDAR, TYPE_BEDTIME, + TYPE_DRIVING, TYPE_IMMERSIVE, TYPE_THEATER, TYPE_MANAGED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + private boolean enabled; private String name; private @InterruptionFilter int interruptionFilter; private Uri conditionId; @@ -46,6 +113,10 @@ public final class AutomaticZenRule implements Parcelable { private ZenPolicy mZenPolicy; private boolean mModified = false; private String mPkg; + private int mType = TYPE_UNKNOWN; + private int mIconResId; + private String mTriggerDescription; + private boolean mAllowManualInvocation; /** * The maximum string length for any string contained in this automatic zen rule. This pertains @@ -55,6 +126,12 @@ public final class AutomaticZenRule implements Parcelable { public static final int MAX_STRING_LENGTH = 1000; /** + * The maximum string length for the trigger description rule, given UI constraints. + * @hide + */ + public static final int MAX_DESC_LENGTH = 150; + + /** * Creates an automatic zen rule. * * @param name The name of the rule. @@ -97,6 +174,7 @@ public final class AutomaticZenRule implements Parcelable { * action ({@link Condition#STATE_TRUE}). * @param enabled Whether the rule is enabled. */ + // TODO (b/309088420): deprecate this constructor in favor of the builder public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner, @Nullable ComponentName configurationActivity, @NonNull Uri conditionId, @Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) { @@ -134,6 +212,12 @@ public final class AutomaticZenRule implements Parcelable { mZenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); mModified = source.readInt() == ENABLED; mPkg = source.readString(); + if (Flags.modesApi()) { + mAllowManualInvocation = source.readBoolean(); + mIconResId = source.readInt(); + mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH); + mType = source.readInt(); + } } /** @@ -269,6 +353,81 @@ public final class AutomaticZenRule implements Parcelable { return mPkg; } + /** + * Gets the type of the rule. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public int getType() { + return mType; + } + + /** + * Sets the type of the rule. + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public void setType(@Type int type) { + mType = type; + } + + /** + * Gets the user visible description of when this rule is active + * (see {@link Condition#STATE_TRUE}). + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public @Nullable String getTriggerDescription() { + return mTriggerDescription; + } + + /** + * Sets a user visible description of when this rule will be active + * (see {@link Condition#STATE_TRUE}). + * + * A description should be a (localized) string like "Mon-Fri, 9pm-7am" or + * "When connected to [Car Name]". + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public void setTriggerDescription(@Nullable String triggerDescription) { + mTriggerDescription = triggerDescription; + } + + /** + * Gets the resource id of the drawable icon for this rule. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public @DrawableRes int getIconResId() { + return mIconResId; + } + + /** + * Sets a resource id of a tintable vector drawable representing the rule in image form. + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public void setIconResId(int iconResId) { + mIconResId = iconResId; + } + + /** + * Gets whether this rule can be manually activated by the user even when the triggering + * condition for the rule is not met. + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public boolean isManualInvocationAllowed() { + return mAllowManualInvocation; + } + + /** + * Sets whether this rule can be manually activated by the user even when the triggering + * condition for the rule is not met. + * @hide + */ + @FlaggedApi(Flags.FLAG_MODES_API) + public void setManualInvocationAllowed(boolean allowManualInvocation) { + mAllowManualInvocation = allowManualInvocation; + } + @Override public int describeContents() { return 0; @@ -291,11 +450,17 @@ public final class AutomaticZenRule implements Parcelable { dest.writeParcelable(mZenPolicy, 0); dest.writeInt(mModified ? ENABLED : DISABLED); dest.writeString(mPkg); + if (Flags.modesApi()) { + dest.writeBoolean(mAllowManualInvocation); + dest.writeInt(mIconResId); + dest.writeString(mTriggerDescription); + dest.writeInt(mType); + } } @Override public String toString() { - return new StringBuilder(AutomaticZenRule.class.getSimpleName()).append('[') + StringBuilder sb = new StringBuilder(AutomaticZenRule.class.getSimpleName()).append('[') .append("enabled=").append(enabled) .append(",name=").append(name) .append(",interruptionFilter=").append(interruptionFilter) @@ -304,8 +469,16 @@ public final class AutomaticZenRule implements Parcelable { .append(",owner=").append(owner) .append(",configActivity=").append(configurationActivity) .append(",creationTime=").append(creationTime) - .append(",mZenPolicy=").append(mZenPolicy) - .append(']').toString(); + .append(",mZenPolicy=").append(mZenPolicy); + + if (Flags.modesApi()) { + sb.append(",allowManualInvocation=").append(mAllowManualInvocation) + .append(",iconResId=").append(mIconResId) + .append(",triggerDescription=").append(mTriggerDescription) + .append(",type=").append(mType); + } + + return sb.append(']').toString(); } @Override @@ -313,7 +486,7 @@ public final class AutomaticZenRule implements Parcelable { if (!(o instanceof AutomaticZenRule)) return false; if (o == this) return true; final AutomaticZenRule other = (AutomaticZenRule) o; - return other.enabled == enabled + boolean finalEquals = other.enabled == enabled && other.mModified == mModified && Objects.equals(other.name, name) && other.interruptionFilter == interruptionFilter @@ -323,10 +496,23 @@ public final class AutomaticZenRule implements Parcelable { && Objects.equals(other.configurationActivity, configurationActivity) && Objects.equals(other.mPkg, mPkg) && other.creationTime == creationTime; + if (Flags.modesApi()) { + return finalEquals + && other.mAllowManualInvocation == mAllowManualInvocation + && other.mIconResId == mIconResId + && Objects.equals(other.mTriggerDescription, mTriggerDescription) + && other.mType == mType; + } + return finalEquals; } @Override public int hashCode() { + if (Flags.modesApi()) { + return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, + configurationActivity, mZenPolicy, mModified, creationTime, mPkg, + mAllowManualInvocation, mIconResId, mTriggerDescription, mType); + } return Objects.hash(enabled, name, interruptionFilter, conditionId, owner, configurationActivity, mZenPolicy, mModified, creationTime, mPkg); } @@ -357,8 +543,12 @@ public final class AutomaticZenRule implements Parcelable { * Returns a truncated copy of the string if the string is longer than MAX_STRING_LENGTH. */ private static String getTrimmedString(String input) { - if (input != null && input.length() > MAX_STRING_LENGTH) { - return input.substring(0, MAX_STRING_LENGTH); + return getTrimmedString(input, MAX_STRING_LENGTH); + } + + private static String getTrimmedString(String input, int length) { + if (input != null && input.length() > length) { + return input.substring(0, length); } return input; } @@ -373,4 +563,138 @@ public final class AutomaticZenRule implements Parcelable { } return input; } + + @FlaggedApi(Flags.FLAG_MODES_API) + public static final class Builder { + private String mName; + private ComponentName mOwner; + private Uri mConditionId; + private int mInterruptionFilter; + private boolean mEnabled; + private ComponentName mConfigurationActivity = null; + private ZenPolicy mPolicy = null; + private int mType; + private String mDescription; + private int mIconResId; + private boolean mAllowManualInvocation; + private long mCreationTime; + private String mPkg; + + public Builder(@NonNull AutomaticZenRule rule) { + mName = rule.getName(); + mOwner = rule.getOwner(); + mConditionId = rule.getConditionId(); + mInterruptionFilter = rule.getInterruptionFilter(); + mEnabled = rule.isEnabled(); + mConfigurationActivity = rule.getConfigurationActivity(); + mPolicy = rule.getZenPolicy(); + mType = rule.getType(); + mDescription = rule.getTriggerDescription(); + mIconResId = rule.getIconResId(); + mAllowManualInvocation = rule.isManualInvocationAllowed(); + mCreationTime = rule.getCreationTime(); + mPkg = rule.getPackageName(); + } + + public Builder(@NonNull String name, @NonNull Uri conditionId) { + mName = name; + mConditionId = conditionId; + } + + public @NonNull Builder setName(@NonNull String name) { + mName = name; + return this; + } + + public @NonNull Builder setOwner(@Nullable ComponentName owner) { + mOwner = owner; + return this; + } + + public @NonNull Builder setConditionId(@NonNull Uri conditionId) { + mConditionId = conditionId; + return this; + } + + public @NonNull Builder setInterruptionFilter( + @InterruptionFilter int interruptionFilter) { + mInterruptionFilter = interruptionFilter; + return this; + } + + public @NonNull Builder setEnabled(boolean enabled) { + mEnabled = enabled; + return this; + } + + public @NonNull Builder setConfigurationActivity( + @Nullable ComponentName configurationActivity) { + mConfigurationActivity = configurationActivity; + return this; + } + + public @NonNull Builder setZenPolicy(@Nullable ZenPolicy policy) { + mPolicy = policy; + return this; + } + + /** + * Sets the type of the rule + */ + public @NonNull Builder setType(@Type int type) { + mType = type; + return this; + } + + /** + * Sets a user visible description of when this rule will be active + * (see {@link Condition#STATE_TRUE}). + * + * A description should be a (localized) string like "Mon-Fri, 9pm-7am" or + * "When connected to [Car Name]". + */ + public @NonNull Builder setTriggerDescription(@Nullable String description) { + mDescription = description; + return this; + } + + /** + * Sets a resource id of a tintable vector drawable representing the rule in image form. + */ + public @NonNull Builder setIconResId(@DrawableRes int iconResId) { + mIconResId = iconResId; + return this; + } + + /** + * Sets whether this rule can be manually activated by the user even when the triggering + * condition for the rule is not met. + */ + public @NonNull Builder setManualInvocationAllowed(boolean allowManualInvocation) { + mAllowManualInvocation = allowManualInvocation; + return this; + } + + /** + * Sets the time at which this rule was created, in milliseconds since epoch + * @hide + */ + public @NonNull Builder setCreationTime(long creationTime) { + mCreationTime = creationTime; + return this; + } + + public @NonNull AutomaticZenRule build() { + AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity, + mConditionId, mPolicy, mInterruptionFilter, mEnabled); + rule.creationTime = mCreationTime; + rule.mType = mType; + rule.mTriggerDescription = mDescription; + rule.mIconResId = mIconResId; + rule.mAllowManualInvocation = mAllowManualInvocation; + rule.setPackageName(mPkg); + + return rule; + } + } } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 828c062d955d..ff4dfc7cc079 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -28,6 +28,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OF import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlarmManager; +import android.app.AutomaticZenRule; +import android.app.Flags; import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.compat.annotation.UnsupportedAppUsage; @@ -177,6 +179,10 @@ public class ZenModeConfig implements Parcelable { private static final String RULE_ATT_CREATION_TIME = "creationTime"; private static final String RULE_ATT_ENABLER = "enabler"; private static final String RULE_ATT_MODIFIED = "modified"; + private static final String RULE_ATT_ALLOW_MANUAL = "userInvokable"; + private static final String RULE_ATT_TYPE = "type"; + private static final String RULE_ATT_ICON = "rule_icon"; + private static final String RULE_ATT_TRIGGER_DESC = "triggerDesc"; @UnsupportedAppUsage public boolean allowAlarms = DEFAULT_ALLOW_ALARMS; @@ -212,7 +218,7 @@ public class ZenModeConfig implements Parcelable { allowCallsFrom = source.readInt(); allowMessagesFrom = source.readInt(); user = source.readInt(); - manualRule = source.readParcelable(null, android.service.notification.ZenModeConfig.ZenRule.class); + manualRule = source.readParcelable(null, ZenRule.class); final int len = source.readInt(); if (len > 0) { final String[] ids = new String[len]; @@ -622,6 +628,12 @@ public class ZenModeConfig implements Parcelable { } rt.modified = safeBoolean(parser, RULE_ATT_MODIFIED, false); rt.zenPolicy = readZenPolicyXml(parser); + if (Flags.modesApi()) { + rt.allowManualInvocation = safeBoolean(parser, RULE_ATT_ALLOW_MANUAL, false); + rt.iconResId = safeInt(parser, RULE_ATT_ICON, 0); + rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC); + rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN); + } return rt; } @@ -655,6 +667,14 @@ public class ZenModeConfig implements Parcelable { writeZenPolicyXml(rule.zenPolicy, out); } out.attributeBoolean(null, RULE_ATT_MODIFIED, rule.modified); + if (Flags.modesApi()) { + out.attributeBoolean(null, RULE_ATT_ALLOW_MANUAL, rule.allowManualInvocation); + out.attributeInt(null, RULE_ATT_ICON, rule.iconResId); + if (rule.triggerDescription != null) { + out.attribute(null, RULE_ATT_TRIGGER_DESC, rule.triggerDescription); + } + out.attributeInt(null, RULE_ATT_TYPE, rule.type); + } } public static Condition readConditionXml(TypedXmlPullParser parser) { @@ -1726,6 +1746,11 @@ public class ZenModeConfig implements Parcelable { public ZenPolicy zenPolicy; public boolean modified; // rule has been modified from initial creation public String pkg; + public int type = AutomaticZenRule.TYPE_UNKNOWN; + public String triggerDescription; + // TODO (b/308672670): switch to string res name + public int iconResId; + public boolean allowManualInvocation; public ZenRule() { } @@ -1750,6 +1775,12 @@ public class ZenModeConfig implements Parcelable { zenPolicy = source.readParcelable(null, android.service.notification.ZenPolicy.class); modified = source.readInt() == 1; pkg = source.readString(); + if (Flags.modesApi()) { + allowManualInvocation = source.readBoolean(); + iconResId = source.readInt(); + triggerDescription = source.readString(); + type = source.readInt(); + } } @Override @@ -1788,11 +1819,17 @@ public class ZenModeConfig implements Parcelable { dest.writeParcelable(zenPolicy, 0); dest.writeInt(modified ? 1 : 0); dest.writeString(pkg); + if (Flags.modesApi()) { + dest.writeBoolean(allowManualInvocation); + dest.writeInt(iconResId); + dest.writeString(triggerDescription); + dest.writeInt(type); + } } @Override public String toString() { - return new StringBuilder(ZenRule.class.getSimpleName()).append('[') + StringBuilder sb = new StringBuilder(ZenRule.class.getSimpleName()).append('[') .append("id=").append(id) .append(",state=").append(condition == null ? "STATE_FALSE" : Condition.stateToString(condition.state)) @@ -1808,8 +1845,16 @@ public class ZenModeConfig implements Parcelable { .append(",enabler=").append(enabler) .append(",zenPolicy=").append(zenPolicy) .append(",modified=").append(modified) - .append(",condition=").append(condition) - .append(']').toString(); + .append(",condition=").append(condition); + + if (Flags.modesApi()) { + sb.append(",allowManualInvocation=").append(allowManualInvocation) + .append(",iconResId=").append(iconResId) + .append(",triggerDescription=").append(triggerDescription) + .append(",type=").append(type); + } + + return sb.append(']').toString(); } /** @hide */ @@ -1845,7 +1890,7 @@ public class ZenModeConfig implements Parcelable { if (!(o instanceof ZenRule)) return false; if (o == this) return true; final ZenRule other = (ZenRule) o; - return other.enabled == enabled + boolean finalEquals = other.enabled == enabled && other.snoozing == snoozing && Objects.equals(other.name, name) && other.zenMode == zenMode @@ -1858,10 +1903,25 @@ public class ZenModeConfig implements Parcelable { && Objects.equals(other.zenPolicy, zenPolicy) && Objects.equals(other.pkg, pkg) && other.modified == modified; + + if (Flags.modesApi()) { + return finalEquals + && other.allowManualInvocation == allowManualInvocation + && other.iconResId == iconResId + && Objects.equals(other.triggerDescription, triggerDescription) + && other.type == type; + } + + return finalEquals; } @Override public int hashCode() { + if (Flags.modesApi()) { + return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, + component, configurationActivity, pkg, id, enabler, zenPolicy, modified, + allowManualInvocation, iconResId, triggerDescription, type); + } return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition, component, configurationActivity, pkg, id, enabler, zenPolicy, modified); } diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java index a4f129ec247e..eb55e40f2c7b 100644 --- a/core/java/android/service/notification/ZenModeDiff.java +++ b/core/java/android/service/notification/ZenModeDiff.java @@ -454,6 +454,11 @@ public class ZenModeDiff { public static final String FIELD_ZEN_POLICY = "zenPolicy"; 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 = "iconResId"; + public static final String FIELD_TRIGGER = "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; @@ -529,6 +534,20 @@ public class ZenModeDiff { if (!Objects.equals(from.pkg, to.pkg)) { addField(FIELD_PKG, new FieldDiff<>(from.pkg, to.pkg)); } + if (!Objects.equals(from.triggerDescription, to.triggerDescription)) { + addField(FIELD_TRIGGER, + 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.iconResId, to.iconResId)) { + addField(FIELD_ICON_RES, new FieldDiff(from.iconResId, to.iconResId)); + } } /** diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java index 282fdad294eb..ba2ea88e8e01 100644 --- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java +++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java @@ -125,6 +125,9 @@ public class AutomaticZenRuleTest { Field configActivity = Class.forName(CLASS).getDeclaredField("configurationActivity"); configActivity.setAccessible(true); configActivity.set(rule, new ComponentName(longString, longString)); + Field trigger = Class.forName(CLASS).getDeclaredField("mTriggerDescription"); + trigger.setAccessible(true); + trigger.set(rule, longString); } catch (NoSuchFieldException e) { fail(e.toString()); } catch (ClassNotFoundException e) { @@ -149,5 +152,6 @@ public class AutomaticZenRuleTest { fromParcel.getOwner().getPackageName().length()); assertEquals(AutomaticZenRule.MAX_STRING_LENGTH, fromParcel.getOwner().getClassName().length()); + assertEquals(AutomaticZenRule.MAX_DESC_LENGTH, rule.getTriggerDescription().length()); } } diff --git a/services/core/Android.bp b/services/core/Android.bp index a14f3fee5303..4e49c6e4e7de 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -199,6 +199,7 @@ java_library_static { "biometrics_flags_lib", "am_flags_lib", "com_android_wm_shell_flags_lib", + "android.app.flags-aconfig-java" ], javac_shard_size: 50, javacflags: [ diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 71562dc1ed86..9802adf302d1 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -26,6 +26,7 @@ import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import android.app.AppOpsManager; import android.app.AutomaticZenRule; +import android.app.Flags; import android.app.Notification; import android.app.NotificationManager; import android.app.NotificationManager.Policy; @@ -670,14 +671,37 @@ public class ZenModeHelper { if (rule.enabled != automaticZenRule.isEnabled()) { rule.snoozing = false; } + if (Flags.modesApi()) { + rule.allowManualInvocation = automaticZenRule.isManualInvocationAllowed(); + rule.iconResId = automaticZenRule.getIconResId(); + rule.triggerDescription = automaticZenRule.getTriggerDescription(); + rule.type = automaticZenRule.getType(); + } } protected AutomaticZenRule createAutomaticZenRule(ZenRule rule) { - AutomaticZenRule azr = new AutomaticZenRule(rule.name, rule.component, - rule.configurationActivity, - rule.conditionId, rule.zenPolicy, - NotificationManager.zenModeToInterruptionFilter(rule.zenMode), - rule.enabled, rule.creationTime); + AutomaticZenRule azr; + if (Flags.modesApi()) { + azr = new AutomaticZenRule.Builder(rule.name, rule.conditionId) + .setManualInvocationAllowed(rule.allowManualInvocation) + .setCreationTime(rule.creationTime) + .setIconResId(rule.iconResId) + .setType(rule.type) + .setZenPolicy(rule.zenPolicy) + .setEnabled(rule.enabled) + .setInterruptionFilter( + NotificationManager.zenModeToInterruptionFilter(rule.zenMode)) + .setOwner(rule.component) + .setConfigurationActivity(rule.configurationActivity) + .setTriggerDescription(rule.triggerDescription) + .build(); + } else { + azr = new AutomaticZenRule(rule.name, rule.component, + rule.configurationActivity, + rule.conditionId, rule.zenPolicy, + NotificationManager.zenModeToInterruptionFilter(rule.zenMode), + rule.enabled, rule.creationTime); + } azr.setPackageName(rule.pkg); return azr; } @@ -713,6 +737,9 @@ public class ZenModeHelper { newRule.zenMode = zenMode; newRule.conditionId = conditionId; newRule.enabler = caller; + if (Flags.modesApi()) { + newRule.allowManualInvocation = true; + } newConfig.manualRule = newRule; } setConfigLocked(newConfig, reason, null, setRingerMode, callingUid, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java index 3ba94000d4a5..261b5d33b635 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import static android.app.AutomaticZenRule.TYPE_BEDTIME; import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; import static junit.framework.TestCase.assertEquals; @@ -24,9 +25,12 @@ import static junit.framework.TestCase.assertNotNull; import static junit.framework.TestCase.assertNull; import static junit.framework.TestCase.assertTrue; +import android.app.Flags; import android.app.NotificationManager.Policy; import android.content.ComponentName; import android.net.Uri; +import android.os.Parcel; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.service.notification.Condition; import android.service.notification.ZenModeConfig; @@ -41,6 +45,7 @@ import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; import com.android.server.UiServiceTestCase; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.xmlpull.v1.XmlPullParserException; @@ -55,6 +60,28 @@ import java.io.IOException; @RunWith(AndroidJUnit4.class) public class ZenModeConfigTest extends UiServiceTestCase { + private final String NAME = "name"; + private final ComponentName OWNER = new ComponentName("pkg", "cls"); + private final ComponentName CONFIG_ACTIVITY = new ComponentName("pkg", "act"); + private final ZenPolicy POLICY = new ZenPolicy.Builder().allowAlarms(true).build(); + private final Uri CONDITION_ID = new Uri.Builder().scheme("scheme") + .authority("authority") + .appendPath("path") + .appendPath("test") + .build(); + + private final Condition CONDITION = new Condition(CONDITION_ID, "", Condition.STATE_TRUE); + private final String TRIGGER_DESC = "Every Night, 10pm to 6am"; + private final int TYPE = TYPE_BEDTIME; + private final boolean ALLOW_MANUAL = true; + private final int ICON_RES_ID = 1234; + private final int INTERRUPTION_FILTER = Settings.Global.ZEN_MODE_ALARMS; + private final boolean ENABLED = true; + private final int CREATION_TIME = 123; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Test public void testPriorityOnlyMutingAllNotifications() { ZenModeConfig config = getMutedRingerConfig(); @@ -202,7 +229,59 @@ public class ZenModeConfigTest extends UiServiceTestCase { } @Test - public void testRuleXml() throws Exception { + public void testWriteToParcel() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = CONFIG_ACTIVITY; + rule.component = OWNER; + rule.conditionId = CONDITION_ID; + rule.condition = CONDITION; + rule.enabled = ENABLED; + rule.creationTime = 123; + rule.id = "id"; + rule.zenMode = INTERRUPTION_FILTER; + rule.modified = true; + rule.name = NAME; + rule.snoozing = true; + rule.pkg = OWNER.getPackageName(); + rule.zenPolicy = POLICY; + + rule.allowManualInvocation = ALLOW_MANUAL; + rule.type = TYPE; + rule.iconResId = ICON_RES_ID; + rule.triggerDescription = TRIGGER_DESC; + + Parcel parcel = Parcel.obtain(); + rule.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + ZenModeConfig.ZenRule parceled = new ZenModeConfig.ZenRule(parcel); + + assertEquals(rule.pkg, parceled.pkg); + assertEquals(rule.snoozing, parceled.snoozing); + assertEquals(rule.enabler, parceled.enabler); + assertEquals(rule.component, parceled.component); + assertEquals(rule.configurationActivity, parceled.configurationActivity); + assertEquals(rule.condition, parceled.condition); + assertEquals(rule.enabled, parceled.enabled); + assertEquals(rule.creationTime, parceled.creationTime); + assertEquals(rule.modified, parceled.modified); + assertEquals(rule.conditionId, parceled.conditionId); + assertEquals(rule.name, parceled.name); + assertEquals(rule.zenMode, parceled.zenMode); + + assertEquals(rule.allowManualInvocation, parceled.allowManualInvocation); + assertEquals(rule.iconResId, parceled.iconResId); + assertEquals(rule.type, parceled.type); + assertEquals(rule.triggerDescription, parceled.triggerDescription); + assertEquals(rule.zenPolicy, parceled.zenPolicy); + assertEquals(rule, parceled); + assertEquals(rule.hashCode(), parceled.hashCode()); + + } + + @Test + public void testRuleXml_classic() throws Exception { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.configurationActivity = new ComponentName("a", "a"); rule.component = new ComponentName("b", "b"); @@ -239,6 +318,58 @@ public class ZenModeConfigTest extends UiServiceTestCase { } @Test + public void testRuleXml() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = CONFIG_ACTIVITY; + rule.component = OWNER; + rule.conditionId = CONDITION_ID; + rule.condition = CONDITION; + rule.enabled = ENABLED; + rule.creationTime = 123; + rule.id = "id"; + rule.zenMode = INTERRUPTION_FILTER; + rule.modified = true; + rule.name = NAME; + rule.snoozing = true; + rule.pkg = OWNER.getPackageName(); + rule.zenPolicy = POLICY; + rule.creationTime = CREATION_TIME; + + rule.allowManualInvocation = ALLOW_MANUAL; + rule.type = TYPE; + rule.iconResId = ICON_RES_ID; + rule.triggerDescription = TRIGGER_DESC; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writeRuleXml(rule, baos); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + ZenModeConfig.ZenRule fromXml = readRuleXml(bais); + + assertEquals(rule.pkg, fromXml.pkg); + // always resets on reboot + assertFalse(fromXml.snoozing); + //should all match original + assertEquals(rule.component, fromXml.component); + assertEquals(rule.configurationActivity, fromXml.configurationActivity); + assertNull(fromXml.enabler); + assertEquals(rule.condition, fromXml.condition); + assertEquals(rule.enabled, fromXml.enabled); + assertEquals(rule.creationTime, fromXml.creationTime); + assertEquals(rule.modified, fromXml.modified); + assertEquals(rule.conditionId, fromXml.conditionId); + assertEquals(rule.name, fromXml.name); + assertEquals(rule.zenMode, fromXml.zenMode); + assertEquals(rule.creationTime, fromXml.creationTime); + + assertEquals(rule.allowManualInvocation, fromXml.allowManualInvocation); + assertEquals(rule.type, fromXml.type); + assertEquals(rule.triggerDescription, fromXml.triggerDescription); + assertEquals(rule.iconResId, fromXml.iconResId); + } + + @Test public void testRuleXml_pkg_component() throws Exception { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); rule.configurationActivity = new ComponentName("a", "a"); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java index bcd807ab6d2f..fd3d5e9bf863 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -23,6 +23,7 @@ import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; +import android.app.AutomaticZenRule; import android.content.ComponentName; import android.net.Uri; import android.provider.Settings; @@ -229,6 +230,10 @@ public class ZenModeDiffTest extends UiServiceTestCase { rule.name = "name"; rule.snoozing = true; rule.pkg = "a"; + rule.allowManualInvocation = true; + rule.type = AutomaticZenRule.TYPE_SCHEDULE_TIME; + rule.iconResId = 123; + rule.triggerDescription = "At night"; return rule; } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index e22c10489d4d..0349ad9e30c7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -16,6 +16,7 @@ package com.android.server.notification; +import static android.app.AutomaticZenRule.TYPE_BEDTIME; import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS; import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS; @@ -71,6 +72,7 @@ import android.annotation.SuppressLint; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AutomaticZenRule; +import android.app.Flags; import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.content.ComponentName; @@ -88,6 +90,7 @@ import android.media.VolumePolicy; import android.net.Uri; import android.os.Process; import android.os.UserHandle; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.provider.Settings.Global; import android.service.notification.Condition; @@ -120,6 +123,7 @@ import com.google.common.collect.ImmutableList; import com.google.protobuf.InvalidProtocolBufferException; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -150,6 +154,28 @@ public class ZenModeHelperTest extends UiServiceTestCase { private static final int CUSTOM_PKG_UID = 1; private static final String CUSTOM_RULE_ID = "custom_rule"; + private final String NAME = "name"; + private final ComponentName OWNER = new ComponentName("pkg", "cls"); + private final ComponentName CONFIG_ACTIVITY = new ComponentName("pkg", "act"); + private final ZenPolicy POLICY = new ZenPolicy.Builder().allowAlarms(true).build(); + private final Uri CONDITION_ID = new Uri.Builder().scheme("scheme") + .authority("authority") + .appendPath("path") + .appendPath("test") + .build(); + + private final Condition CONDITION = new Condition(CONDITION_ID, "", Condition.STATE_TRUE); + private final String TRIGGER_DESC = "Every Night, 10pm to 6am"; + private final int TYPE = TYPE_BEDTIME; + private final boolean ALLOW_MANUAL = true; + private final int ICON_RES_ID = 1234; + private final int INTERRUPTION_FILTER = Settings.Global.ZEN_MODE_ALARMS; + private final boolean ENABLED = true; + private final int CREATION_TIME = 123; + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + ConditionProviders mConditionProviders; @Mock NotificationManager mNotificationManager; @Mock PackageManager mPackageManager; @@ -1961,6 +1987,26 @@ public class ZenModeHelperTest extends UiServiceTestCase { @Test public void testSetManualZenMode() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + setupZenConfig(); + + // note that caller=null because that's how it comes in from NMS.setZenMode + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", + Process.SYSTEM_UID, true); + + // confirm that setting zen mode via setManualZenMode changed the zen mode correctly + assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode); + assertEquals(true, mZenModeHelper.mConfig.manualRule.allowManualInvocation); + + // and also that it works to turn it back off again + mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", + Process.SYSTEM_UID, true); + + assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode); + } + + @Test + public void testSetManualZenMode_legacy() { setupZenConfig(); // note that caller=null because that's how it comes in from NMS.setZenMode @@ -2607,6 +2653,47 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertFalse(mZenModeHelper.mConsolidatedPolicy.showPeeking()); // custom stricter } + @Test + public void testCreateAutomaticZenRule_allFields() { + mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API); + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = CONFIG_ACTIVITY; + rule.component = OWNER; + rule.conditionId = CONDITION_ID; + rule.condition = CONDITION; + rule.enabled = ENABLED; + rule.creationTime = 123; + rule.id = "id"; + rule.zenMode = INTERRUPTION_FILTER; + rule.modified = true; + rule.name = NAME; + rule.snoozing = true; + rule.pkg = OWNER.getPackageName(); + rule.zenPolicy = POLICY; + + rule.allowManualInvocation = ALLOW_MANUAL; + rule.type = TYPE; + rule.iconResId = ICON_RES_ID; + rule.triggerDescription = TRIGGER_DESC; + + AutomaticZenRule actual = mZenModeHelper.createAutomaticZenRule(rule); + + assertEquals(NAME, actual.getName()); + assertEquals(OWNER, actual.getOwner()); + assertEquals(CONDITION_ID, actual.getConditionId()); + assertEquals(NotificationManager.INTERRUPTION_FILTER_ALARMS, + actual.getInterruptionFilter()); + assertEquals(ENABLED, actual.isEnabled()); + assertEquals(POLICY, actual.getZenPolicy()); + assertEquals(CONFIG_ACTIVITY, actual.getConfigurationActivity()); + assertEquals(TYPE, actual.getType()); + assertEquals(ALLOW_MANUAL, actual.isManualInvocationAllowed()); + assertEquals(CREATION_TIME, actual.getCreationTime()); + assertEquals(OWNER.getPackageName(), actual.getPackageName()); + assertEquals(ICON_RES_ID, actual.getIconResId()); + assertEquals(TRIGGER_DESC, actual.getTriggerDescription()); + } + private void setupZenConfig() { mZenModeHelper.mZenMode = Global.ZEN_MODE_OFF; mZenModeHelper.mConfig.allowAlarms = false; |