Merge "Adds source to Notification Condition" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 5f7b0c2..f9f8d78 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -40545,19 +40545,26 @@
public final class Condition implements android.os.Parcelable {
ctor public Condition(android.net.Uri, String, int);
+ ctor @FlaggedApi("android.app.modes_api") public Condition(@Nullable android.net.Uri, @Nullable String, int, int);
ctor public Condition(android.net.Uri, String, String, String, int, int, int);
+ ctor @FlaggedApi("android.app.modes_api") public Condition(@Nullable android.net.Uri, @Nullable String, @Nullable String, @Nullable String, int, int, int, int);
ctor public Condition(android.os.Parcel);
method public android.service.notification.Condition copy();
method public int describeContents();
method public static boolean isValidId(android.net.Uri, String);
method public static android.net.Uri.Builder newId(android.content.Context);
method public static String relevanceToString(int);
+ method @FlaggedApi("android.app.modes_api") @NonNull public static String sourceToString(int);
method public static String stateToString(int);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.service.notification.Condition> CREATOR;
field public static final int FLAG_RELEVANT_ALWAYS = 2; // 0x2
field public static final int FLAG_RELEVANT_NOW = 1; // 0x1
field public static final String SCHEME = "condition";
+ field @FlaggedApi("android.app.modes_api") public static final int SOURCE_CONTEXT = 3; // 0x3
+ field @FlaggedApi("android.app.modes_api") public static final int SOURCE_SCHEDULE = 2; // 0x2
+ field @FlaggedApi("android.app.modes_api") public static final int SOURCE_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("android.app.modes_api") public static final int SOURCE_USER_ACTION = 1; // 0x1
field public static final int STATE_ERROR = 3; // 0x3
field public static final int STATE_FALSE = 0; // 0x0
field public static final int STATE_TRUE = 1; // 0x1
@@ -40567,6 +40574,7 @@
field public final android.net.Uri id;
field public final String line1;
field public final String line2;
+ field @FlaggedApi("android.app.modes_api") public final int source;
field public final int state;
field public final String summary;
}
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 4d33bfd..d76fa5b 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -16,8 +16,11 @@
package android.service.notification;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.Flags;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
@@ -57,7 +60,6 @@
* Indicates that Do Not Disturb should be turned on.
*/
public static final int STATE_TRUE = 1;
-
public static final int STATE_UNKNOWN = 2;
public static final int STATE_ERROR = 3;
@@ -90,6 +92,33 @@
public final int flags;
public final int icon;
+ /** @hide */
+ @IntDef(prefix = { "SOURCE_" }, value = {
+ SOURCE_UNKNOWN,
+ SOURCE_USER_ACTION,
+ SOURCE_SCHEDULE,
+ SOURCE_CONTEXT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Source {}
+
+ /** The state is changing due to an unknown reason. */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int SOURCE_UNKNOWN = 0;
+ /** The state is changing due to an explicit user action. */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int SOURCE_USER_ACTION = 1;
+ /** The state is changing due to an automatic schedule (alarm, set time, etc). */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int SOURCE_SCHEDULE = 2;
+ /** The state is changing due to a change in context (such as detected driving or sleeping). */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static final int SOURCE_CONTEXT = 3;
+
+ /** The source of, or reason for, the state change represented by this Condition. **/
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public final @Source int source;
+
/**
* The maximum string length for any string contained in this condition.
* @hide
@@ -99,14 +128,48 @@
/**
* An object representing the current state of a {@link android.app.AutomaticZenRule}.
* @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule
- * @param summary a user visible description of the rule state.
+ * @param summary a user visible description of the rule state
+ * @param state whether the mode should be activated or deactivated
*/
+ // TODO: b/310208502 - Deprecate this in favor of constructor which specifies source.
public Condition(Uri id, String summary, int state) {
- this(id, summary, "", "", -1, state, FLAG_RELEVANT_ALWAYS);
+ this(id, summary, "", "", -1, state, SOURCE_UNKNOWN, FLAG_RELEVANT_ALWAYS);
}
+ /**
+ * An object representing the current state of a {@link android.app.AutomaticZenRule}.
+ * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule
+ * @param summary a user visible description of the rule state
+ * @param state whether the mode should be activated or deactivated
+ * @param source the source of, or reason for, the state change represented by this Condition
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public Condition(@Nullable Uri id, @Nullable String summary, @State int state,
+ @Source int source) {
+ this(id, summary, "", "", -1, state, source, FLAG_RELEVANT_ALWAYS);
+ }
+
+ // TODO: b/310208502 - Deprecate this in favor of constructor which specifies source.
public Condition(Uri id, String summary, String line1, String line2, int icon,
int state, int flags) {
+ this(id, summary, line1, line2, icon, state, SOURCE_UNKNOWN, flags);
+ }
+
+ /**
+ * An object representing the current state of a {@link android.app.AutomaticZenRule}.
+ * @param id the {@link android.app.AutomaticZenRule#getConditionId()} of the zen rule
+ * @param summary a user visible description of the rule state
+ * @param line1 a user-visible description of when the rule will end
+ * @param line2 a continuation of the user-visible description of when the rule will end
+ * @param icon an icon representing this condition
+ * @param state whether the mode should be activated or deactivated
+ * @param source the source of, or reason for, the state change represented by this Condition
+ * @param flags flags on this condition
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public Condition(@Nullable Uri id, @Nullable String summary, @Nullable String line1,
+ @Nullable String line2, int icon, @State int state, @Source int source,
+ int flags) {
if (id == null) throw new IllegalArgumentException("id is required");
if (summary == null) throw new IllegalArgumentException("summary is required");
if (!isValidState(state)) throw new IllegalArgumentException("state is invalid: " + state);
@@ -116,6 +179,7 @@
this.line2 = getTrimmedString(line2);
this.icon = icon;
this.state = state;
+ this.source = source;
this.flags = flags;
}
@@ -129,6 +193,7 @@
source.readString(),
source.readInt(),
source.readInt(),
+ Flags.modesApi() ? source.readInt() : SOURCE_UNKNOWN,
source.readInt());
}
@@ -144,20 +209,27 @@
dest.writeString(line2);
dest.writeInt(icon);
dest.writeInt(state);
+ if (Flags.modesApi()) {
+ dest.writeInt(this.source);
+ }
dest.writeInt(this.flags);
}
@Override
public String toString() {
- return new StringBuilder(Condition.class.getSimpleName()).append('[')
+ StringBuilder sb = new StringBuilder(Condition.class.getSimpleName()).append('[')
.append("state=").append(stateToString(state))
.append(",id=").append(id)
.append(",summary=").append(summary)
.append(",line1=").append(line1)
.append(",line2=").append(line2)
- .append(",icon=").append(icon)
- .append(",flags=").append(flags)
+ .append(",icon=").append(icon);
+ if (Flags.modesApi()) {
+ sb.append(",source=").append(sourceToString(source));
+ }
+ return sb.append(",flags=").append(flags)
.append(']').toString();
+
}
/** @hide */
@@ -171,6 +243,7 @@
proto.write(ConditionProto.LINE_2, line2);
proto.write(ConditionProto.ICON, icon);
proto.write(ConditionProto.STATE, state);
+ // TODO: b/310644464 - Add source to dump.
proto.write(ConditionProto.FLAGS, flags);
proto.end(token);
@@ -184,6 +257,16 @@
throw new IllegalArgumentException("state is invalid: " + state);
}
+ /** Provides a human-readable string version of the Source enum. */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ public static @NonNull String sourceToString(@Source int source) {
+ if (source == SOURCE_UNKNOWN) return "SOURCE_UNKNOWN";
+ if (source == SOURCE_USER_ACTION) return "SOURCE_USER_ACTION";
+ if (source == SOURCE_SCHEDULE) return "SOURCE_SCHEDULE";
+ if (source == SOURCE_CONTEXT) return "SOURCE_CONTEXT";
+ throw new IllegalArgumentException("source is invalid: " + source);
+ }
+
public static String relevanceToString(int flags) {
final boolean now = (flags & FLAG_RELEVANT_NOW) != 0;
final boolean always = (flags & FLAG_RELEVANT_ALWAYS) != 0;
@@ -197,17 +280,24 @@
if (!(o instanceof Condition)) return false;
if (o == this) return true;
final Condition other = (Condition) o;
- return Objects.equals(other.id, id)
+ boolean finalEquals = Objects.equals(other.id, id)
&& Objects.equals(other.summary, summary)
&& Objects.equals(other.line1, line1)
&& Objects.equals(other.line2, line2)
&& other.icon == icon
&& other.state == state
&& other.flags == flags;
+ if (Flags.modesApi()) {
+ return finalEquals && other.source == source;
+ }
+ return finalEquals;
}
@Override
public int hashCode() {
+ if (Flags.modesApi()) {
+ return Objects.hash(id, summary, line1, line2, icon, state, source, flags);
+ }
return Objects.hash(id, summary, line1, line2, icon, state, flags);
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 305b751..fedad89 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -160,6 +160,7 @@
private static final String CONDITION_ATT_LINE2 = "line2";
private static final String CONDITION_ATT_ICON = "icon";
private static final String CONDITION_ATT_STATE = "state";
+ private static final String CONDITION_ATT_SOURCE = "source";
private static final String CONDITION_ATT_FLAGS = "flags";
private static final String ZEN_POLICY_TAG = "zen_policy";
@@ -687,7 +688,12 @@
final int state = safeInt(parser, CONDITION_ATT_STATE, -1);
final int flags = safeInt(parser, CONDITION_ATT_FLAGS, -1);
try {
- return new Condition(id, summary, line1, line2, icon, state, flags);
+ if (Flags.modesApi()) {
+ final int source = safeInt(parser, CONDITION_ATT_SOURCE, Condition.SOURCE_UNKNOWN);
+ return new Condition(id, summary, line1, line2, icon, state, source, flags);
+ } else {
+ return new Condition(id, summary, line1, line2, icon, state, flags);
+ }
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unable to read condition xml", e);
return null;
@@ -701,6 +707,9 @@
out.attribute(null, CONDITION_ATT_LINE2, c.line2);
out.attributeInt(null, CONDITION_ATT_ICON, c.icon);
out.attributeInt(null, CONDITION_ATT_STATE, c.state);
+ if (Flags.modesApi()) {
+ out.attributeInt(null, CONDITION_ATT_SOURCE, c.source);
+ }
out.attributeInt(null, CONDITION_ATT_FLAGS, c.flags);
}
diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java
index 42629ba..612562e 100644
--- a/core/tests/coretests/src/android/service/notification/ConditionTest.java
+++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java
@@ -16,17 +16,22 @@
package android.service.notification;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;
+import android.app.Flags;
import android.net.Uri;
import android.os.Parcel;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.google.common.base.Strings;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,8 +42,11 @@
public class ConditionTest {
private static final String CLASS = "android.service.notification.Condition";
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
@Test
- public void testLongFields_inConstructors() {
+ public void testLongFields_inConstructors_classic() {
String longString = Strings.repeat("A", 65536);
Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
@@ -59,7 +67,7 @@
}
@Test
- public void testLongFields_viaParcel() {
+ public void testLongFields_viaParcel_classic() {
// Set fields via reflection to force them to be long, then parcel and unparcel to make sure
// it gets truncated upon unparcelling.
Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder",
@@ -98,4 +106,92 @@
assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line1.length());
assertEquals(Condition.MAX_STRING_LENGTH, fromParcel.line2.length());
}
+
+ @Test
+ public void testLongFields_inConstructors() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+ String longString = Strings.repeat("A", 65536);
+ Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
+
+ // Confirm strings are truncated via short constructor
+ Condition cond1 = new Condition(longUri, longString, Condition.STATE_TRUE,
+ Condition.SOURCE_CONTEXT);
+
+ assertThat(cond1.id.toString()).hasLength(Condition.MAX_STRING_LENGTH);
+ assertThat(cond1.summary).hasLength(Condition.MAX_STRING_LENGTH);
+
+ // Confirm strings are truncated via long constructor
+ Condition cond2 = new Condition(longUri, longString, longString, longString,
+ -1, Condition.STATE_TRUE, Condition.SOURCE_CONTEXT, Condition.FLAG_RELEVANT_ALWAYS);
+
+ assertThat(cond2.id.toString()).hasLength(Condition.MAX_STRING_LENGTH);
+ assertThat(cond2.summary).hasLength(Condition.MAX_STRING_LENGTH);
+ assertThat(cond2.line1).hasLength(Condition.MAX_STRING_LENGTH);
+ assertThat(cond2.line2).hasLength(Condition.MAX_STRING_LENGTH);
+ }
+
+ @Test
+ public void testLongFields_viaParcel() throws Exception {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+ // Set fields via reflection to force them to be long, then parcel and unparcel to make sure
+ // it gets truncated upon unparcelling.
+ Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+ Condition.STATE_TRUE, Condition.SOURCE_CONTEXT);
+
+ String longString = Strings.repeat("A", 65536);
+ Uri longUri = Uri.parse("uri://" + Strings.repeat("A", 65530));
+ Field id = Class.forName(CLASS).getDeclaredField("id");
+ id.setAccessible(true);
+ id.set(cond, longUri);
+ Field summary = Class.forName(CLASS).getDeclaredField("summary");
+ summary.setAccessible(true);
+ summary.set(cond, longString);
+ Field line1 = Class.forName(CLASS).getDeclaredField("line1");
+ line1.setAccessible(true);
+ line1.set(cond, longString);
+ Field line2 = Class.forName(CLASS).getDeclaredField("line2");
+ line2.setAccessible(true);
+ line2.set(cond, longString);
+
+ Parcel parcel = Parcel.obtain();
+ cond.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ Condition fromParcel = new Condition(parcel);
+ assertThat(fromParcel.id.toString()).hasLength(Condition.MAX_STRING_LENGTH);
+ assertThat(fromParcel.summary).hasLength(Condition.MAX_STRING_LENGTH);
+ assertThat(fromParcel.line1).hasLength(Condition.MAX_STRING_LENGTH);
+ assertThat(fromParcel.line2).hasLength(Condition.MAX_STRING_LENGTH);
+ }
+
+ @Test
+ public void testEquals() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+ Condition cond1 = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+ Condition.STATE_TRUE, Condition.SOURCE_USER_ACTION);
+ Condition cond2 = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+ "", "", -1,
+ Condition.STATE_TRUE, Condition.SOURCE_SCHEDULE, Condition.FLAG_RELEVANT_ALWAYS);
+
+ assertThat(cond1).isNotEqualTo(cond2);
+ Condition cond3 = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+ Condition.STATE_TRUE, Condition.SOURCE_SCHEDULE);
+ assertThat(cond3).isEqualTo(cond2);
+ }
+
+ @Test
+ public void testParcelConstructor() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_MODES_API);
+
+ Condition cond = new Condition(Uri.parse("uri://placeholder"), "placeholder",
+ Condition.STATE_TRUE, Condition.SOURCE_USER_ACTION);
+
+ Parcel parcel = Parcel.obtain();
+ cond.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ Condition fromParcel = new Condition(parcel);
+ assertThat(fromParcel).isEqualTo(cond);
+ }
}