Support (non-editable) display of DND with a filter != PRIORITY

Tweak ZenModesBackend to return the current DND interruption filter if manual DND is on. This will be used to show the corresponding policy (e.g. the policy for INTERRUPTION_FILTER_ALAMS) in this situation. The policy will be read-only in the UI since it cannot be customized.

Also clarify some related documentation.

Bug: 361586248
Test: atest ZenModeHelperTest ZenModeTest ZenModesBackendTest NotificationManagerZenTest
Flag: android.app.modes_ui
Change-Id: Ie09032bbf4ded76d02d6c9fcc47c746030536191
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 62b5412..e0a9371 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -49,7 +49,7 @@
 
     /**
      * Rule is of an unknown type. This is the default value if not provided by the owning app,
-     * and the value returned if the true type was added in an API level lower than the calling
+     * and the value returned if the true type was added in an API level higher than the calling
      * app's targetSdk.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
@@ -315,7 +315,8 @@
     }
 
     /**
-     * Gets the zen policy.
+     * Gets the {@link ZenPolicy} applied if {@link #getInterruptionFilter()} is
+     * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY}.
      */
     @Nullable
     public ZenPolicy getZenPolicy() {
@@ -345,6 +346,17 @@
 
     /**
      * Sets the interruption filter that is applied when this rule is active.
+     *
+     * <ul>
+     *     <li>When {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY}, the rule will use
+     *     the {@link ZenPolicy} supplied to {@link #setZenPolicy} (or a default one).
+     *     <li>When {@link NotificationManager#INTERRUPTION_FILTER_ALARMS} or
+     *     {@link NotificationManager#INTERRUPTION_FILTER_NONE}, the rule will use a fixed
+     *     {@link ZenPolicy} matching the filter.
+     *     <li>When {@link NotificationManager#INTERRUPTION_FILTER_ALL}, the rule will not block
+     *     notifications, but can still have {@link ZenDeviceEffects}.
+     * </ul>
+     *
      * @param interruptionFilter The do not disturb mode to enter when this rule is active.
      */
     public void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
@@ -374,7 +386,8 @@
     }
 
     /**
-     * Sets the zen policy.
+     * Sets the {@link ZenPolicy} applied if {@link #getInterruptionFilter()} is
+     * {@link NotificationManager#INTERRUPTION_FILTER_PRIORITY}.
      *
      * <p>When updating an existing rule via {@link NotificationManager#updateAutomaticZenRule},
      * a {@code null} value here means the previous policy is retained.
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 918e591..682a89f 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -2939,7 +2939,7 @@
             }
         }
 
-        // TODO: b/333527800 - Rename to isActive()
+        // TODO: b/363193376 - Rename to isActive()
         public boolean isAutomaticActive() {
             if (Flags.modesApi() && Flags.modesUi()) {
                 if (!enabled || getPkg() == null) {
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 687c728..4d771c0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1364,6 +1364,9 @@
     <!-- Keywords for setting screen for controlling apps that can schedule alarms [CHAR LIMIT=100] -->
     <string name="keywords_alarms_and_reminders">schedule, alarm, reminder, clock</string>
 
+    <!-- Priority Modes: Name of the "manual" Do Not Disturb mode. [CHAR LIMIT=50] -->
+    <string name="zen_mode_do_not_disturb_name">Do Not Disturb</string>
+
     <!-- Sound: Title for the Do not Disturb option and associated settings page. [CHAR LIMIT=50]-->
     <string name="zen_mode_settings_title">Do Not Disturb</string>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index f7492cf..8e0cdf8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.notification.modes;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.service.notification.ZenModeConfig.ORIGIN_UNKNOWN;
 import static android.service.notification.ZenModeConfig.ORIGIN_USER_IN_SYSTEMUI;
 
@@ -24,11 +25,13 @@
 import android.content.ComponentName;
 import android.net.Uri;
 import android.service.notification.Condition;
+import android.service.notification.SystemZenRules;
 import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 
 import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import java.util.Random;
@@ -40,13 +43,27 @@
     private ZenModeConfig.ZenRule mConfigZenRule;
 
     public static final ZenMode EXAMPLE = new TestModeBuilder().build();
-    public static final ZenMode MANUAL_DND_ACTIVE = manualDnd(Uri.EMPTY, true);
-    public static final ZenMode MANUAL_DND_INACTIVE = manualDnd(Uri.EMPTY, false);
 
-    public static ZenMode manualDnd(Uri conditionId, boolean isActive) {
+    public static final ZenMode MANUAL_DND_ACTIVE = manualDnd(Uri.EMPTY,
+            INTERRUPTION_FILTER_PRIORITY, true);
+
+    public static final ZenMode MANUAL_DND_INACTIVE = manualDnd(Uri.EMPTY,
+            INTERRUPTION_FILTER_PRIORITY, false);
+
+    @NonNull
+    public static ZenMode manualDnd(@NotificationManager.InterruptionFilter int filter,
+            boolean isActive) {
+        return manualDnd(Uri.EMPTY, filter, isActive);
+    }
+
+    private static ZenMode manualDnd(Uri conditionId,
+            @NotificationManager.InterruptionFilter int filter, boolean isActive) {
         return ZenMode.manualDndMode(
                 new AutomaticZenRule.Builder("Do Not Disturb", conditionId)
-                        .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+                        .setInterruptionFilter(filter)
+                        .setType(AutomaticZenRule.TYPE_OTHER)
+                        .setManualInvocationAllowed(true)
+                        .setPackage(SystemZenRules.PACKAGE_ANDROID)
                         .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
                         .build(),
                 isActive);
@@ -58,7 +75,7 @@
         mId = "rule_" + id;
         mRule = new AutomaticZenRule.Builder("Test Rule #" + id, Uri.parse("rule://" + id))
                 .setPackage("some_package")
-                .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+                .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                 .setZenPolicy(new ZenPolicy.Builder().disallowAllSounds().build())
                 .build();
         mConfigZenRule = new ZenModeConfig.ZenRule();
@@ -134,7 +151,7 @@
             @NotificationManager.InterruptionFilter int interruptionFilter) {
         mRule.setInterruptionFilter(interruptionFilter);
         mConfigZenRule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
-                interruptionFilter, NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+                interruptionFilter, INTERRUPTION_FILTER_PRIORITY);
         return this;
     }
 
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 36975c7..d0661fa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -18,9 +18,10 @@
 
 import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
 import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
-import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
 import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId;
@@ -69,7 +70,7 @@
     static final String TEMP_NEW_MODE_ID = "temp_new_mode";
 
     // Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
-    private static final ZenPolicy POLICY_INTERRUPTION_FILTER_ALARMS =
+    static final ZenPolicy POLICY_INTERRUPTION_FILTER_ALARMS =
             new ZenPolicy.Builder()
                     .disallowAllSounds()
                     .allowAlarms(true)
@@ -78,7 +79,7 @@
                     .build();
 
     // Must match com.android.server.notification.ZenModeHelper#applyCustomPolicy.
-    private static final ZenPolicy POLICY_INTERRUPTION_FILTER_NONE =
+    static final ZenPolicy POLICY_INTERRUPTION_FILTER_NONE =
             new ZenPolicy.Builder()
                     .disallowAllSounds()
                     .hideAllVisualEffects()
@@ -171,13 +172,9 @@
     }
 
     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,
+                manualRule,
                 Kind.MANUAL_DND,
                 isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED);
     }
@@ -298,6 +295,23 @@
         }
     }
 
+    /** Returns the interruption filter of the mode. */
+    @NotificationManager.InterruptionFilter
+    public int getInterruptionFilter() {
+        return mRule.getInterruptionFilter();
+    }
+
+    /**
+     * Sets the interruption filter of the mode. This is valid for {@link AutomaticZenRule}-backed
+     * modes (and not manual DND).
+     */
+    public void setInterruptionFilter(@NotificationManager.InterruptionFilter int filter) {
+        if (isManualDnd() || !canEditPolicy()) {
+            throw new IllegalStateException("Cannot update interruption filter for mode " + this);
+        }
+        mRule.setInterruptionFilter(filter);
+    }
+
     @NonNull
     public ZenPolicy getPolicy() {
         switch (mRule.getInterruptionFilter()) {
@@ -326,6 +340,10 @@
      */
     @SuppressLint("WrongConstant")
     public void setPolicy(@NonNull ZenPolicy policy) {
+        if (!canEditPolicy()) {
+            throw new IllegalStateException("Cannot update ZenPolicy for mode " + this);
+        }
+
         ZenPolicy currentPolicy = getPolicy();
         if (currentPolicy.equals(policy)) {
             return;
@@ -342,6 +360,12 @@
         mRule.setZenPolicy(policy);
     }
 
+    /**
+     * Returns the {@link ZenDeviceEffects} of the mode.
+     *
+     * <p>This is never {@code null}; if the backing AutomaticZenRule doesn't have effects set then
+     * a default (empty) effects set is returned.
+     */
     @NonNull
     public ZenDeviceEffects getDeviceEffects() {
         return mRule.getDeviceEffects() != null
@@ -349,6 +373,15 @@
                 : new ZenDeviceEffects.Builder().build();
     }
 
+    /** Sets the {@link ZenDeviceEffects} of the mode. */
+    public void setDeviceEffects(@NonNull ZenDeviceEffects effects) {
+        checkNotNull(effects);
+        if (!canEditPolicy()) {
+            throw new IllegalStateException("Cannot update device effects for mode " + this);
+        }
+        mRule.setDeviceEffects(effects);
+    }
+
     public void setCustomModeConditionId(Context context, Uri conditionId) {
         checkState(SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName()),
                 "Trying to change condition of non-system-owned rule %s (to %s)",
@@ -391,6 +424,18 @@
         return !isManualDnd();
     }
 
+    /**
+     * Whether the mode has an editable policy. Calling {@link #setPolicy},
+     * {@link #setDeviceEffects}, or {@link #setInterruptionFilter} is not valid for modes with a
+     * read-only policy.
+     */
+    public boolean canEditPolicy() {
+        // Cannot edit the policy of a temporarily active non-PRIORITY DND mode.
+        // Note that it's fine to edit the policy of an *AutomaticZenRule* with non-PRIORITY filter;
+        // the filter will we set to PRIORITY if you do.
+        return !isManualDndWithSpecialFilter();
+    }
+
     public boolean canBeDeleted() {
         return !isManualDnd();
     }
@@ -399,6 +444,12 @@
         return mKind == Kind.MANUAL_DND;
     }
 
+    private boolean isManualDndWithSpecialFilter() {
+        return isManualDnd()
+                && (mRule.getInterruptionFilter() == INTERRUPTION_FILTER_ALARMS
+                || mRule.getInterruptionFilter() == INTERRUPTION_FILTER_NONE);
+    }
+
     /**
      * A <em>custom manual</em> mode is a mode created by the user, and not yet assigned an
      * automatic trigger condition (neither time schedule nor a calendar).
@@ -414,6 +465,18 @@
         return mRule.isEnabled();
     }
 
+    /**
+     * Enables or disables the mode.
+     *
+     * <p>The DND mode cannot be disabled; trying to do so will fail.
+     */
+    public void setEnabled(boolean enabled) {
+        if (isManualDnd()) {
+            throw new IllegalStateException("Cannot update enabled for manual DND mode " + this);
+        }
+        mRule.setEnabled(enabled);
+    }
+
     public boolean isActive() {
         return mStatus == Status.ENABLED_AND_ACTIVE;
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
index c8a12f4..71e03c1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
@@ -16,6 +16,11 @@
 
 package com.android.settingslib.notification.modes;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.app.NotificationManager.zenModeToInterruptionFilter;
+import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
+
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AutomaticZenRule;
@@ -112,14 +117,22 @@
 
     private ZenMode getManualDndMode(ZenModeConfig config) {
         ZenModeConfig.ZenRule manualRule = config.manualRule;
+
+        // If DND is currently on with an interruption filter other than PRIORITY, construct the
+        // rule with that. DND will be *non-editable* while in this state.
+        int dndInterruptionFilter = config.isManualActive()
+                ? zenModeToInterruptionFilter(manualRule.zenMode)
+                : INTERRUPTION_FILTER_PRIORITY;
+
         AutomaticZenRule manualDndRule = new AutomaticZenRule.Builder(
-                mContext.getString(R.string.zen_mode_settings_title), manualRule.conditionId)
-                .setType(manualRule.type)
+                mContext.getString(R.string.zen_mode_do_not_disturb_name), manualRule.conditionId)
+                .setPackage(PACKAGE_ANDROID)
+                .setType(AutomaticZenRule.TYPE_OTHER)
                 .setZenPolicy(manualRule.zenPolicy)
                 .setDeviceEffects(manualRule.zenDeviceEffects)
-                .setManualInvocationAllowed(manualRule.allowManualInvocation)
+                .setManualInvocationAllowed(true)
                 .setConfigurationActivity(null) // No further settings
-                .setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY)
+                .setInterruptionFilter(dndInterruptionFilter)
                 .build();
 
         return ZenMode.manualDndMode(manualDndRule, config.isManualActive());
@@ -150,7 +163,7 @@
                 durationConditionId = ZenModeConfig.toTimeCondition(mContext,
                         (int) forDuration.toMinutes(), ActivityManager.getCurrentUser(), true).id;
             }
-            mNotificationManager.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+            mNotificationManager.setZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS,
                     durationConditionId, TAG, /* fromUser= */ true);
 
         } else {
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 14b0c25..a30613d 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
@@ -24,6 +24,7 @@
 import static android.app.AutomaticZenRule.TYPE_THEATER;
 import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
@@ -31,11 +32,14 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertThrows;
+
 import android.app.AutomaticZenRule;
 import android.net.Uri;
 import android.os.Parcel;
 import android.service.notification.Condition;
 import android.service.notification.SystemZenRules;
+import android.service.notification.ZenDeviceEffects;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 
@@ -69,20 +73,26 @@
                     .build();
 
     @Test
-    public void testBasicMethods() {
+    public void testBasicMethods_mode() {
         ZenMode zenMode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, true));
 
         assertThat(zenMode.getId()).isEqualTo("id");
         assertThat(zenMode.getRule()).isEqualTo(ZEN_RULE);
         assertThat(zenMode.isManualDnd()).isFalse();
         assertThat(zenMode.canEditNameAndIcon()).isTrue();
+        assertThat(zenMode.canEditPolicy()).isTrue();
         assertThat(zenMode.canBeDeleted()).isTrue();
         assertThat(zenMode.isActive()).isTrue();
+    }
 
-        ZenMode manualMode = ZenMode.manualDndMode(ZEN_RULE, false);
+    @Test
+    public void testBasicMethods_manualDnd() {
+        ZenMode manualMode = TestModeBuilder.MANUAL_DND_INACTIVE;
+
         assertThat(manualMode.getId()).isEqualTo(ZenMode.MANUAL_DND_MODE_ID);
         assertThat(manualMode.isManualDnd()).isTrue();
         assertThat(manualMode.canEditNameAndIcon()).isFalse();
+        assertThat(manualMode.canEditPolicy()).isTrue();
         assertThat(manualMode.canBeDeleted()).isFalse();
         assertThat(manualMode.isActive()).isFalse();
         assertThat(manualMode.getRule().getPackageName()).isEqualTo(PACKAGE_ANDROID);
@@ -243,6 +253,77 @@
     }
 
     @Test
+    public void getInterruptionFilter_returnsFilter() {
+        ZenMode mode = new TestModeBuilder().setInterruptionFilter(
+                INTERRUPTION_FILTER_ALARMS).build();
+
+        assertThat(mode.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
+    }
+
+    @Test
+    public void setInterruptionFilter_setsFilter() {
+        ZenMode mode = new TestModeBuilder().setInterruptionFilter(
+                INTERRUPTION_FILTER_ALARMS).build();
+
+        mode.setInterruptionFilter(INTERRUPTION_FILTER_ALL);
+
+        assertThat(mode.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
+    }
+
+    @Test
+    public void setInterruptionFilter_manualDnd_throws() {
+        ZenMode manualDnd = TestModeBuilder.MANUAL_DND_INACTIVE;
+
+        assertThrows(IllegalStateException.class,
+                () -> manualDnd.setInterruptionFilter(INTERRUPTION_FILTER_ALL));
+    }
+
+    @Test
+    public void canEditPolicy_onlyFalseForSpecialDnd() {
+        assertThat(TestModeBuilder.EXAMPLE.canEditPolicy()).isTrue();
+        assertThat(TestModeBuilder.MANUAL_DND_ACTIVE.canEditPolicy()).isTrue();
+        assertThat(TestModeBuilder.MANUAL_DND_INACTIVE.canEditPolicy()).isTrue();
+
+        ZenMode dndWithAlarms = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_ALARMS, true);
+        assertThat(dndWithAlarms.canEditPolicy()).isFalse();
+        ZenMode dndWithNone = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, true);
+        assertThat(dndWithNone.canEditPolicy()).isFalse();
+
+        // Note: Backend will never return an inactive manual mode with custom filter.
+        ZenMode badDndWithAlarms = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_ALARMS, false);
+        assertThat(badDndWithAlarms.canEditPolicy()).isFalse();
+        ZenMode badDndWithNone = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_NONE, false);
+        assertThat(badDndWithNone.canEditPolicy()).isFalse();
+    }
+
+    @Test
+    public void canEditPolicy_whenTrue_allowsSettingPolicyAndEffects() {
+        ZenMode normalDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_PRIORITY, true);
+
+        assertThat(normalDnd.canEditPolicy()).isTrue();
+
+        ZenPolicy somePolicy = new ZenPolicy.Builder().showBadges(true).build();
+        normalDnd.setPolicy(somePolicy);
+        assertThat(normalDnd.getPolicy()).isEqualTo(somePolicy);
+
+        ZenDeviceEffects someEffects = new ZenDeviceEffects.Builder()
+                .setShouldUseNightMode(true).build();
+        normalDnd.setDeviceEffects(someEffects);
+        assertThat(normalDnd.getDeviceEffects()).isEqualTo(someEffects);
+    }
+
+    @Test
+    public void canEditPolicy_whenFalse_preventsSettingFilterPolicyOrEffects() {
+        ZenMode specialDnd = TestModeBuilder.manualDnd(INTERRUPTION_FILTER_ALARMS, true);
+
+        assertThat(specialDnd.canEditPolicy()).isFalse();
+        assertThrows(IllegalStateException.class,
+                () -> specialDnd.setPolicy(ZEN_POLICY));
+        assertThrows(IllegalStateException.class,
+                () -> specialDnd.setDeviceEffects(new ZenDeviceEffects.Builder().build()));
+    }
+
+    @Test
     public void comparator_prioritizes() {
         ZenMode manualDnd = TestModeBuilder.MANUAL_DND_INACTIVE;
         ZenMode driving1 = new TestModeBuilder().setName("b1").setType(TYPE_DRIVING).build();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java
index 539519b..ff028dd 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModesBackendTest.java
@@ -16,12 +16,14 @@
 
 package com.android.settingslib.notification.modes;
 
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 import static android.service.notification.Condition.SOURCE_UNKNOWN;
 import static android.service.notification.Condition.STATE_FALSE;
 import static android.service.notification.Condition.STATE_TRUE;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 import static android.service.notification.ZenAdapters.notificationPolicyToZenPolicy;
 import static android.service.notification.ZenPolicy.STATE_ALLOW;
 
@@ -47,8 +49,6 @@
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
 
-import com.android.settingslib.R;
-
 import com.google.common.collect.ImmutableMap;
 
 import org.junit.Before;
@@ -172,9 +172,8 @@
         // all modes exist, but none of them are currently active
         assertThat(modes).containsExactly(
                         ZenMode.manualDndMode(
-                                new AutomaticZenRule.Builder(
-                                        mContext.getString(R.string.zen_mode_settings_title),
-                                        Uri.EMPTY)
+                                new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
+                                        .setPackage(PACKAGE_ANDROID)
                                         .setType(AutomaticZenRule.TYPE_OTHER)
                                         .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                                         .setZenPolicy(notificationPolicyToZenPolicy(dndPolicy))
@@ -196,15 +195,52 @@
 
         ZenMode mode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID);
 
+        assertThat(mode).isNotNull();
         assertThat(mode).isEqualTo(
                 ZenMode.manualDndMode(
-                        new AutomaticZenRule.Builder(
-                                mContext.getString(R.string.zen_mode_settings_title), Uri.EMPTY)
+                        new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
+                                .setPackage(PACKAGE_ANDROID)
                                 .setType(AutomaticZenRule.TYPE_OTHER)
                                 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                                 .setZenPolicy(notificationPolicyToZenPolicy(dndPolicy))
                                 .setManualInvocationAllowed(true)
                                 .build(), false));
+
+        assertThat(mode.isManualDnd()).isTrue();
+        assertThat(mode.isEnabled()).isTrue();
+        assertThat(mode.isActive()).isFalse();
+        assertThat(mode.canEditPolicy()).isTrue();
+        assertThat(mode.getPolicy()).isEqualTo(notificationPolicyToZenPolicy(dndPolicy));
+    }
+
+    @Test
+    public void getMode_dndWithOtherInterruptionFilter_returnsSpecialDndMode() {
+        ZenModeConfig config = configWithManualRule(new ZenModeConfig(), true);
+        config.manualRule.zenMode = Settings.Global.ZEN_MODE_ALARMS;
+        Policy dndPolicyForPriority = new Policy(Policy.PRIORITY_CATEGORY_ALARMS,
+                Policy.PRIORITY_SENDERS_CONTACTS, Policy.PRIORITY_SENDERS_CONTACTS);
+        config.applyNotificationPolicy(dndPolicyForPriority);
+        when(mNm.getZenModeConfig()).thenReturn(config);
+
+        ZenMode mode = mBackend.getMode(ZenMode.MANUAL_DND_MODE_ID);
+
+        assertThat(mode).isNotNull();
+        assertThat(mode).isEqualTo(
+                ZenMode.manualDndMode(
+                        new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY)
+                                .setPackage(PACKAGE_ANDROID)
+                                .setType(AutomaticZenRule.TYPE_OTHER)
+                                .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+                                .setZenPolicy(notificationPolicyToZenPolicy(dndPolicyForPriority))
+                                .setManualInvocationAllowed(true)
+                                .build(), true));
+
+        assertThat(mode.isManualDnd()).isTrue();
+        assertThat(mode.isEnabled()).isTrue();
+        assertThat(mode.isActive()).isTrue();
+        // Mode itself has a special fixed policy, different to the rule.
+        assertThat(mode.canEditPolicy()).isFalse();
+        assertThat(mode.getPolicy()).isEqualTo(ZenMode.POLICY_INTERRUPTION_FILTER_ALARMS);
     }
 
     @Test