summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java17
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java80
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java90
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java2
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java11
5 files changed, 170 insertions, 30 deletions
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index fc6c2e88779f..57acc71c20ea 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -187,6 +187,13 @@ public class ZenModeConfig implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface ConfigOrigin {}
+ /**
+ * Prefix for the ids of implicit Zen rules. Implicit rules are those created automatically
+ * on behalf of apps that call {@link NotificationManager#setNotificationPolicy} or
+ * {@link NotificationManager#setInterruptionFilter}.
+ */
+ private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name
+
public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY;
public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS;
public static final int SOURCE_STAR = Policy.PRIORITY_SENDERS_STARRED;
@@ -2492,6 +2499,16 @@ public class ZenModeConfig implements Parcelable {
// ==== End built-in system conditions ====
+ /** Generate the rule id for the implicit rule for the specified package. */
+ public static String implicitRuleId(String forPackage) {
+ return IMPLICIT_RULE_ID_PREFIX + forPackage;
+ }
+
+ /** Returns whether the rule id corresponds to an implicit rule. */
+ public static boolean isImplicitRuleId(@NonNull String ruleId) {
+ return ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX);
+ }
+
private static int[] tryParseHourAndMinute(String value) {
if (TextUtils.isEmpty(value)) return null;
final int i = value.indexOf('.');
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 0d4ce5be7b0e..c13b2612d37e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -117,6 +117,21 @@ public class ZenMode implements Parcelable {
.thenComparing(ZenMode::getType, PRIORITIZED_TYPE_COMPARATOR)
.thenComparing(ZenMode::getName);
+ public enum Kind {
+ /** A "normal" mode, created by apps or users via {@code addAutomaticZenRule()}. */
+ NORMAL,
+
+ /** The special, built-in "Do Not Disturb" mode. */
+ MANUAL_DND,
+
+ /**
+ * An implicit mode, automatically created and managed by the system on behalf of apps that
+ * call {@code setInterruptionFilter()} or {@code setNotificationPolicy()} (with some
+ * exceptions).
+ */
+ IMPLICIT,
+ }
+
public enum Status {
ENABLED,
ENABLED_AND_ACTIVE,
@@ -126,8 +141,8 @@ public class ZenMode implements Parcelable {
private final String mId;
private final AutomaticZenRule mRule;
+ private final Kind mKind;
private final Status mStatus;
- private final boolean mIsManualDnd;
/**
* Initializes a {@link ZenMode}, mainly based on the information from the
@@ -137,9 +152,11 @@ public class ZenMode implements Parcelable {
* active, or the reason it was disabled) are read from the {@link ZenModeConfig.ZenRule} --
* see {@link #computeStatus}.
*/
- public ZenMode(String id, @NonNull AutomaticZenRule rule,
+ ZenMode(String id, @NonNull AutomaticZenRule rule,
@NonNull ZenModeConfig.ZenRule zenRuleExtraData) {
- this(id, rule, computeStatus(zenRuleExtraData), false);
+ this(id, rule,
+ ZenModeConfig.isImplicitRuleId(id) ? Kind.IMPLICIT : Kind.NORMAL,
+ computeStatus(zenRuleExtraData));
}
private static Status computeStatus(@NonNull ZenModeConfig.ZenRule zenRuleExtraData) {
@@ -158,13 +175,16 @@ public class ZenMode implements Parcelable {
}
}
- public static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
+ static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
// Manual rule is owned by the system, so we set it here
AutomaticZenRule manualRuleWithPkg = new AutomaticZenRule.Builder(manualRule)
.setPackage(PACKAGE_ANDROID)
.build();
- return new ZenMode(MANUAL_DND_MODE_ID, manualRuleWithPkg,
- isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true);
+ return new ZenMode(
+ MANUAL_DND_MODE_ID,
+ manualRuleWithPkg,
+ Kind.MANUAL_DND,
+ isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED);
}
/**
@@ -183,19 +203,19 @@ public class ZenMode implements Parcelable {
.setIconResId(iconResId)
.setManualInvocationAllowed(true)
.build();
- return new ZenMode(TEMP_NEW_MODE_ID, rule, Status.ENABLED, false);
+ return new ZenMode(TEMP_NEW_MODE_ID, rule, Kind.NORMAL, Status.ENABLED);
}
- private ZenMode(String id, @NonNull AutomaticZenRule rule, Status status, boolean isManualDnd) {
+ private ZenMode(String id, @NonNull AutomaticZenRule rule, Kind kind, Status status) {
mId = id;
mRule = rule;
+ mKind = kind;
mStatus = status;
- mIsManualDnd = isManualDnd;
}
/** Creates a deep copy of this object. */
public ZenMode copy() {
- return new ZenMode(mId, new AutomaticZenRule.Builder(mRule).build(), mStatus, mIsManualDnd);
+ return new ZenMode(mId, new AutomaticZenRule.Builder(mRule).build(), mKind, mStatus);
}
@NonNull
@@ -264,10 +284,32 @@ public class ZenMode implements Parcelable {
return mRule.getType() + ":" + mRule.getPackageName() + ":" + mRule.getIconResId();
}
+ /**
+ * Returns the mode icon -- which can be either app-provided (via {@code addAutomaticZenRule}),
+ * user-chosen (via the icon picker in Settings), the app's launcher icon for implicit rules
+ * (in its monochrome variant, if available), or a default icon based on the mode type.
+ */
@NonNull
public ListenableFuture<Drawable> getIcon(@NonNull Context context,
@NonNull ZenIconLoader iconLoader) {
- if (mIsManualDnd) {
+ if (mKind == Kind.MANUAL_DND) {
+ return Futures.immediateFuture(requireNonNull(
+ context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
+ }
+
+ return iconLoader.getIcon(context, mRule);
+ }
+
+ /**
+ * Returns an alternative mode icon. The difference with {@link #getIcon} is that it's the
+ * basic DND icon not only for Manual DND, but also for <em>implicit rules</em>. As such, it's
+ * suitable for places where showing the launcher icon of an app could be confusing, such as
+ * the status bar or lockscreen.
+ */
+ @NonNull
+ public ListenableFuture<Drawable> getLockscreenIcon(@NonNull Context context,
+ @NonNull ZenIconLoader iconLoader) {
+ if (mKind == Kind.MANUAL_DND || mKind == Kind.IMPLICIT) {
return Futures.immediateFuture(requireNonNull(
context.getDrawable(R.drawable.ic_do_not_disturb_on_24dp)));
}
@@ -373,7 +415,7 @@ public class ZenMode implements Parcelable {
}
public boolean isManualDnd() {
- return mIsManualDnd;
+ return mKind == Kind.MANUAL_DND;
}
/**
@@ -404,18 +446,18 @@ public class ZenMode implements Parcelable {
return obj instanceof ZenMode other
&& mId.equals(other.mId)
&& mRule.equals(other.mRule)
- && mStatus.equals(other.mStatus)
- && mIsManualDnd == other.mIsManualDnd;
+ && mKind.equals(other.mKind)
+ && mStatus.equals(other.mStatus);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mRule, mStatus, mIsManualDnd);
+ return Objects.hash(mId, mRule, mKind, mStatus);
}
@Override
public String toString() {
- return mId + " (" + mStatus + ") -> " + mRule;
+ return mId + " (" + mKind + ", " + mStatus + ") -> " + mRule;
}
@Override
@@ -427,8 +469,8 @@ public class ZenMode implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mId);
dest.writeParcelable(mRule, 0);
+ dest.writeString(mKind.name());
dest.writeString(mStatus.name());
- dest.writeBoolean(mIsManualDnd);
}
public static final Creator<ZenMode> CREATOR = new Creator<ZenMode>() {
@@ -438,8 +480,8 @@ public class ZenMode implements Parcelable {
in.readString(),
checkNotNull(in.readParcelable(AutomaticZenRule.class.getClassLoader(),
AutomaticZenRule.class)),
- Status.valueOf(in.readString()),
- in.readBoolean());
+ Kind.valueOf(in.readString()),
+ Status.valueOf(in.readString()));
}
@Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index bab4bc3b0bf5..f533e77bb33e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -30,7 +30,14 @@ import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
import android.app.AutomaticZenRule;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Parcel;
import android.service.notification.Condition;
@@ -40,9 +47,12 @@ import android.service.notification.ZenPolicy;
import com.android.internal.R;
+import com.google.common.util.concurrent.ListenableFuture;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.List;
@@ -60,6 +70,13 @@ public class ZenModeTest {
.setZenPolicy(ZEN_POLICY)
.build();
+ private static final String IMPLICIT_RULE_ID = ZenModeConfig.implicitRuleId("some.package");
+ private static final AutomaticZenRule IMPLICIT_ZEN_RULE =
+ new AutomaticZenRule.Builder("Implicit", Uri.parse("implicit/some.package"))
+ .setPackage("some.package")
+ .setType(TYPE_OTHER)
+ .build();
+
@Test
public void testBasicMethods() {
ZenMode zenMode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, true));
@@ -265,6 +282,79 @@ public class ZenModeTest {
assertUnparceledIsEqualToOriginal("custom_manual",
ZenMode.newCustomManual("New mode", R.drawable.ic_zen_mode_type_immersive));
+
+ assertUnparceledIsEqualToOriginal("implicit",
+ new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
+ zenConfigRuleFor(IMPLICIT_ZEN_RULE, false)));
+ }
+
+ @Test
+ public void getIcon_normalMode_loadsIconNormally() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+ ZenMode mode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false));
+
+ ListenableFuture<Drawable> unused = mode.getIcon(RuntimeEnvironment.getApplication(),
+ iconLoader);
+
+ verify(iconLoader).getIcon(any(), eq(ZEN_RULE));
+ }
+
+ @Test
+ public void getIcon_manualDnd_returnsFixedIcon() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+
+ ListenableFuture<Drawable> future = TestModeBuilder.MANUAL_DND_INACTIVE.getIcon(
+ RuntimeEnvironment.getApplication(), iconLoader);
+
+ assertThat(future.isDone()).isTrue();
+ verify(iconLoader, never()).getIcon(any(), any());
+ }
+
+ @Test
+ public void getIcon_implicitMode_loadsIconNormally() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+ ZenMode mode = new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
+ zenConfigRuleFor(IMPLICIT_ZEN_RULE, false));
+
+ ListenableFuture<Drawable> unused = mode.getIcon(RuntimeEnvironment.getApplication(),
+ iconLoader);
+
+ verify(iconLoader).getIcon(any(), eq(IMPLICIT_ZEN_RULE));
+ }
+
+ @Test
+ public void getLockscreenIcon_normalMode_loadsIconNormally() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+ ZenMode mode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false));
+
+ ListenableFuture<Drawable> unused = mode.getLockscreenIcon(
+ RuntimeEnvironment.getApplication(), iconLoader);
+
+ verify(iconLoader).getIcon(any(), eq(ZEN_RULE));
+ }
+
+ @Test
+ public void getLockscreenIcon_manualDnd_returnsFixedIcon() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+
+ ListenableFuture<Drawable> future = TestModeBuilder.MANUAL_DND_INACTIVE.getLockscreenIcon(
+ RuntimeEnvironment.getApplication(), iconLoader);
+
+ assertThat(future.isDone()).isTrue();
+ verify(iconLoader, never()).getIcon(any(), any());
+ }
+
+ @Test
+ public void getLockscreenIcon_implicitMode_returnsFixedIcon() {
+ ZenIconLoader iconLoader = mock(ZenIconLoader.class);
+ ZenMode mode = new ZenMode(IMPLICIT_RULE_ID, IMPLICIT_ZEN_RULE,
+ zenConfigRuleFor(IMPLICIT_ZEN_RULE, false));
+
+ ListenableFuture<Drawable> future = mode.getLockscreenIcon(
+ RuntimeEnvironment.getApplication(), iconLoader);
+
+ assertThat(future.isDone()).isTrue();
+ verify(iconLoader, never()).getIcon(any(), any());
}
private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c7c984b40267..ffb2bb6a4528 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5675,7 +5675,7 @@ public class NotificationManagerService extends SystemService {
// a "normal" rule, it must provide a CP/ConfigActivity too.
if (android.app.Flags.modesApi()) {
boolean isImplicitRuleUpdateFromSystem = updateId != null
- && ZenModeHelper.isImplicitRuleId(updateId)
+ && ZenModeConfig.isImplicitRuleId(updateId)
&& isCallerSystemOrSystemUi();
if (!isImplicitRuleUpdateFromSystem
&& rule.getOwner() == null
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 0f50260a55d2..20eeda0c78e5 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -39,6 +39,7 @@ import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_APP;
import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_ACTIVATE;
import static android.service.notification.ZenModeConfig.ZenRule.OVERRIDE_DEACTIVATE;
+import static android.service.notification.ZenModeConfig.implicitRuleId;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -155,8 +156,6 @@ public class ZenModeHelper {
static final int RULE_LIMIT_PER_PACKAGE = 100;
private static final Duration DELETED_RULE_KEPT_FOR = Duration.ofDays(30);
- private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name
-
private static final int MAX_ICON_RESOURCE_NAME_LENGTH = 1000;
/**
@@ -783,14 +782,6 @@ public class ZenModeHelper {
return rule;
}
- private static String implicitRuleId(String forPackage) {
- return IMPLICIT_RULE_ID_PREFIX + forPackage;
- }
-
- static boolean isImplicitRuleId(@NonNull String ruleId) {
- return ruleId.startsWith(IMPLICIT_RULE_ID_PREFIX);
- }
-
boolean removeAutomaticZenRule(String id, @ConfigOrigin int origin, String reason,
int callingUid) {
checkManageRuleOrigin("removeAutomaticZenRule", origin);