Add new state to AutomaticZenRule
Test: ZenModeConfigTest
Test: (cts) NotificationManagerZenTest
Test: (cts) AutomaticZenRuleTest
Fixes: 308672543
Fixes: 308672010
Fixes: 308673538
Bug: 308674037
Change-Id: I66be27cce62a144289c5bd45c18bf6064ca2fe51
diff --git a/core/api/current.txt b/core/api/current.txt
index e8a6ac9..124f043 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5315,11 +5315,15 @@
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 @@
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 7bfb1b5..919e084 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 @@
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 @@
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 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 @@
* 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 @@
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 @@
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 @@
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 @@
.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 @@
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 @@
&& 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 @@
* 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 @@
}
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 828c062..ff4dfc7 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -28,6 +28,8 @@
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 @@
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 @@
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 @@
}
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 @@
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 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 @@
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 @@
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 @@
.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 @@
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 @@
&& 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 a4f129e..eb55e40 100644
--- a/core/java/android/service/notification/ZenModeDiff.java
+++ b/core/java/android/service/notification/ZenModeDiff.java
@@ -454,6 +454,11 @@
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 @@
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 282fdad..ba2ea88 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -125,6 +125,9 @@
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 @@
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 a14f3fe..4e49c6e 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -199,6 +199,7 @@
"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 71562dc..9802adf 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 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 @@
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 @@
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 3ba9400..261b5d3 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.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.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 @@
@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 @@
}
@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 @@
}
@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 bcd807a..fd3d5e9b 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.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 @@
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 e22c104..0349ad9 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.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.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.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 @@
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 @@
@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 @@
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;