diff options
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); |