summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java50
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeSchedules.java45
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java117
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt12
7 files changed, 219 insertions, 21 deletions
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index 6f94c1b2d274..4cbd5beb3a8c 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -3131,6 +3131,7 @@ public class ZenModeConfig implements Parcelable {
* @return null if DND is off or describeForeverCondition is false and
* DND is on forever (until turned off)
*/
+ // TODO: b/368247671 - Delete when inlining MODES_UI
public static String getDescription(Context context, boolean zenOn, ZenModeConfig config,
boolean describeForeverCondition) {
if (!zenOn || config == null) {
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 d5cfe55813ee..460c790174ab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -35,6 +35,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.SuppressLint;
import android.app.AutomaticZenRule;
import android.app.NotificationManager;
+import android.content.ComponentName;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
@@ -117,6 +118,14 @@ public class ZenMode implements Parcelable {
DISABLED_BY_OTHER
}
+ /**
+ * Information about the owner of a {@link ZenMode}. {@link #packageName()} is
+ * {@link SystemZenRules#PACKAGE_ANDROID} if the mode is system-owned; it may also be
+ * {@code null}, but only as an artifact of very old modes.
+ */
+ public record Owner(@Nullable String packageName, @Nullable ComponentName configurationActivity,
+ @Nullable ComponentName conditionProvider) { }
+
private final String mId;
private final AutomaticZenRule mRule;
private final Kind mKind;
@@ -198,7 +207,7 @@ public class ZenMode implements Parcelable {
}
@NonNull
- public AutomaticZenRule getRule() {
+ AutomaticZenRule getRule() {
return mRule;
}
@@ -207,6 +216,10 @@ public class ZenMode implements Parcelable {
return Strings.nullToEmpty(mRule.getName());
}
+ public void setName(@NonNull String name) {
+ mRule.setName(name);
+ }
+
@NonNull
public Kind getKind() {
return mKind;
@@ -217,6 +230,17 @@ public class ZenMode implements Parcelable {
return mStatus;
}
+ @NonNull
+ public Owner getOwner() {
+ return new Owner(mRule.getPackageName(), mRule.getConfigurationActivity(),
+ mRule.getOwner());
+ }
+
+ @Nullable
+ public String getOwnerPackage() {
+ return getOwner().packageName();
+ }
+
@AutomaticZenRule.Type
public int getType() {
return mRule.getType();
@@ -257,6 +281,26 @@ public class ZenMode implements Parcelable {
}
}
+ /**
+ * Returns the resource id of the icon for this mode. Note that this is the <em>stored</em>
+ * resource id, and thus can be different from the value in {@link #getIconKey()} -- in
+ * particular, for modes without a custom icon set, this method returns {@code 0} whereas
+ * {@link #getIconKey()} will return a default icon based on other mode properties.
+ *
+ * <p>Most callers are interested in {@link #getIconKey()}, unless they are editing the icon.
+ */
+ public int getIconResId() {
+ return mRule.getIconResId();
+ }
+
+ /**
+ * Sets the resource id of the icon for this mode.
+ * @see #getIconResId()
+ */
+ public void setIconResId(@DrawableRes int iconResId) {
+ mRule.setIconResId(iconResId);
+ }
+
/** Returns the interruption filter of the mode. */
@NotificationManager.InterruptionFilter
public int getInterruptionFilter() {
@@ -445,6 +489,10 @@ public class ZenMode implements Parcelable {
return mStatus == Status.ENABLED_AND_ACTIVE;
}
+ public boolean isManualInvocationAllowed() {
+ return mRule.isManualInvocationAllowed();
+ }
+
public boolean isSystemOwned() {
return SystemZenRules.PACKAGE_ANDROID.equals(mRule.getPackageName());
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java
index f5776989917e..b67339bace67 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeDescriptions.java
@@ -70,8 +70,7 @@ public final class ZenModeDescriptions {
public String getTriggerDescriptionForAccessibility(@NonNull ZenMode mode) {
// Only one special case: time-based schedules, where we want to use full day names.
if (mode.isSystemOwned() && mode.getType() == TYPE_SCHEDULE_TIME) {
- ZenModeConfig.ScheduleInfo schedule = ZenModeConfig.tryParseScheduleConditionId(
- mode.getRule().getConditionId());
+ ZenModeConfig.ScheduleInfo schedule = ZenModeSchedules.getTimeSchedule(mode);
if (schedule != null) {
String fullDaysSummary = SystemZenRules.getDaysOfWeekFull(mContext, schedule);
if (fullDaysSummary != null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeSchedules.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeSchedules.java
new file mode 100644
index 000000000000..c981652f231d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModeSchedules.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.notification.modes;
+
+import android.service.notification.ZenModeConfig;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class ZenModeSchedules {
+
+ /**
+ * Returns the {@link ZenModeConfig.ScheduleInfo} time schedule corresponding to the mode, or
+ * {@code null} if the mode is not time-schedule-based.
+ */
+ @Nullable
+ public static ZenModeConfig.ScheduleInfo getTimeSchedule(@NonNull ZenMode mode) {
+ return ZenModeConfig.tryParseScheduleConditionId(mode.getRule().getConditionId());
+ }
+
+ /**
+ * Returns the {@link ZenModeConfig.EventInfo} calendar schedule corresponding to the mode, or
+ * {@code null} if the mode is not calendar-schedule-based.
+ */
+ @Nullable
+ public static ZenModeConfig.EventInfo getCalendarSchedule(@NonNull ZenMode mode) {
+ return ZenModeConfig.tryParseEventConditionId(mode.getRule().getConditionId());
+ }
+
+ private ZenModeSchedules() { }
+}
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 6b30f159129e..71ecf5b76296 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
@@ -21,6 +21,7 @@ import static android.app.AutomaticZenRule.TYPE_DRIVING;
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
import static android.app.AutomaticZenRule.TYPE_OTHER;
import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.AutomaticZenRule.TYPE_THEATER;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
@@ -35,6 +36,8 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertThrows;
import android.app.AutomaticZenRule;
+import android.content.ComponentName;
+import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
import android.service.notification.Condition;
@@ -43,13 +46,17 @@ import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
+import androidx.test.core.app.ApplicationProvider;
+
import com.android.internal.R;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@@ -72,6 +79,13 @@ public class ZenModeTest {
.setType(TYPE_OTHER)
.build();
+ private Context mContext;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ }
+
@Test
public void testBasicMethods_mode() {
ZenMode zenMode = new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, true));
@@ -95,7 +109,7 @@ public class ZenModeTest {
assertThat(manualMode.canEditPolicy()).isTrue();
assertThat(manualMode.canBeDeleted()).isFalse();
assertThat(manualMode.isActive()).isFalse();
- assertThat(manualMode.getRule().getPackageName()).isEqualTo(PACKAGE_ANDROID);
+ assertThat(manualMode.getOwnerPackage()).isEqualTo(PACKAGE_ANDROID);
}
@Test
@@ -153,7 +167,7 @@ public class ZenModeTest {
public void isCustomManual_scheduleTime_false() {
AutomaticZenRule rule = new AutomaticZenRule.Builder("Mode", Uri.parse("x"))
.setPackage(SystemZenRules.PACKAGE_ANDROID)
- .setType(AutomaticZenRule.TYPE_SCHEDULE_TIME)
+ .setType(TYPE_SCHEDULE_TIME)
.build();
ZenMode mode = new ZenMode("id", rule, zenConfigRuleFor(rule, false));
@@ -194,6 +208,23 @@ public class ZenModeTest {
}
@Test
+ public void getOwner_returnsOwnerDetails() {
+ AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
+ .setPackage("package")
+ .setConfigurationActivity(new ComponentName("package", "configActivity"))
+ .setOwner(new ComponentName("package", "conditionService"))
+ .build();
+ ZenMode zenMode = new ZenMode("id", azr, zenConfigRuleFor(azr, false));
+
+ ZenMode.Owner owner = zenMode.getOwner();
+ assertThat(owner.packageName()).isEqualTo("package");
+ assertThat(owner.configurationActivity()).isEqualTo(
+ new ComponentName("package", "configActivity"));
+ assertThat(owner.conditionProvider()).isEqualTo(
+ new ComponentName("package", "conditionService"));
+ }
+
+ @Test
public void getPolicy_interruptionFilterPriority_returnsZenPolicy() {
AutomaticZenRule azr = new AutomaticZenRule.Builder("Rule", Uri.EMPTY)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
@@ -245,7 +276,7 @@ public class ZenModeTest {
zenMode.setPolicy(ZEN_POLICY);
- assertThat(zenMode.getRule().getInterruptionFilter()).isEqualTo(
+ assertThat(zenMode.getInterruptionFilter()).isEqualTo(
INTERRUPTION_FILTER_PRIORITY);
assertThat(zenMode.getPolicy()).isEqualTo(ZEN_POLICY);
assertThat(zenMode.getRule().getZenPolicy()).isEqualTo(ZEN_POLICY);
@@ -397,6 +428,7 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isEqualTo("some.package");
assertThat(iconKey.resId()).isEqualTo(123);
+ assertThat(mode.getIconResId()).isEqualTo(123);
}
@Test
@@ -411,6 +443,7 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(123);
+ assertThat(mode.getIconResId()).isEqualTo(123);
}
@Test
@@ -425,15 +458,18 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isEqualTo("some.package");
assertThat(iconKey.resId()).isEqualTo(123);
+ assertThat(mode.getIconResId()).isEqualTo(123);
}
@Test
public void getIconKey_manualDnd_isDndIcon() {
- ZenIcon.Key iconKey = TestModeBuilder.MANUAL_DND.getIconKey();
+ ZenMode mode = TestModeBuilder.MANUAL_DND;
+ ZenIcon.Key iconKey = mode.getIconKey();
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(
com.android.internal.R.drawable.ic_zen_mode_type_special_dnd);
+ assertThat(mode.getIconResId()).isEqualTo(0);
}
@Test
@@ -448,6 +484,7 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(
com.android.internal.R.drawable.ic_zen_mode_type_bedtime);
+ assertThat(mode.getIconResId()).isEqualTo(0);
}
@Test
@@ -462,6 +499,7 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(
com.android.internal.R.drawable.ic_zen_mode_type_schedule_calendar);
+ assertThat(mode.getIconResId()).isEqualTo(0);
}
@Test
@@ -477,6 +515,77 @@ public class ZenModeTest {
assertThat(iconKey.resPackage()).isNull();
assertThat(iconKey.resId()).isEqualTo(
com.android.internal.R.drawable.ic_zen_mode_type_special_dnd);
+ assertThat(mode.getIconResId()).isEqualTo(0);
+ }
+
+ @Test
+ public void setCustomModeConditionId_timeSchedule() {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage(PACKAGE_ANDROID)
+ .build();
+ ZenModeConfig.ScheduleInfo timeSchedule = new ZenModeConfig.ScheduleInfo();
+ timeSchedule.startHour = 9;
+ timeSchedule.endHour = 12;
+ timeSchedule.days = new int[] {Calendar.SATURDAY, Calendar.SUNDAY};
+ Uri scheduleUri = ZenModeConfig.toScheduleConditionId(timeSchedule);
+
+ mode.setCustomModeConditionId(mContext, scheduleUri);
+
+ assertThat(mode.getType()).isEqualTo(TYPE_SCHEDULE_TIME);
+ assertThat(ZenModeSchedules.getTimeSchedule(mode)).isEqualTo(timeSchedule);
+ assertThat(mode.getTriggerDescription()).isEqualTo("Sun, Sat, 9:00 AM - 12:00 PM");
+
+ assertThat(mode.getRule().getConditionId()).isEqualTo(scheduleUri);
+ assertThat(mode.getRule().getOwner()).isEqualTo(
+ ZenModeConfig.getScheduleConditionProvider());
+ }
+
+ @Test
+ public void setCustomModeConditionId_calendarSchedule() {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage(PACKAGE_ANDROID)
+ .build();
+ ZenModeConfig.EventInfo calendarSchedule = new ZenModeConfig.EventInfo();
+ calendarSchedule.calendarId = 1L;
+ calendarSchedule.calName = "My events";
+ Uri scheduleUri = ZenModeConfig.toEventConditionId(calendarSchedule);
+
+ mode.setCustomModeConditionId(mContext, scheduleUri);
+
+ assertThat(mode.getType()).isEqualTo(TYPE_SCHEDULE_CALENDAR);
+ assertThat(ZenModeSchedules.getCalendarSchedule(mode)).isEqualTo(calendarSchedule);
+ assertThat(mode.getTriggerDescription()).isEqualTo("My events");
+
+ assertThat(mode.getRule().getConditionId()).isEqualTo(scheduleUri);
+ assertThat(mode.getRule().getOwner()).isEqualTo(
+ ZenModeConfig.getEventConditionProvider());
+ }
+
+ @Test
+ public void setCustomModeConditionId_manualSchedule() {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage(PACKAGE_ANDROID)
+ .build();
+
+ mode.setCustomModeConditionId(mContext, ZenModeConfig.toCustomManualConditionId());
+
+ assertThat(mode.getType()).isEqualTo(TYPE_OTHER);
+ assertThat(mode.getTriggerDescription()).isEqualTo("");
+
+ assertThat(mode.getRule().getConditionId()).isEqualTo(
+ ZenModeConfig.toCustomManualConditionId());
+ assertThat(mode.getRule().getOwner()).isEqualTo(
+ ZenModeConfig.getCustomManualConditionProvider());
+ }
+
+ @Test
+ public void setCustomModeConditionId_nonSystemRule_throws() {
+ ZenMode mode = new TestModeBuilder()
+ .setPackage("some.other.package")
+ .build();
+
+ assertThrows(IllegalStateException.class,
+ () -> mode.setCustomModeConditionId(mContext, Uri.parse("blah")));
}
private static void assertUnparceledIsEqualToOriginal(String type, ZenMode original) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
index 33ed419afef2..5dbe9b145bc7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
@@ -21,29 +21,25 @@ import com.android.settingslib.notification.modes.ZenMode
import com.android.systemui.qs.QSModesEvent
import javax.inject.Inject
-class ModesDialogEventLogger
-@Inject
-constructor(
- private val uiEventLogger: UiEventLogger,
-) {
+class ModesDialogEventLogger @Inject constructor(private val uiEventLogger: UiEventLogger) {
fun logModeOn(mode: ZenMode) {
val id =
if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_ON else QSModesEvent.QS_MODES_MODE_ON
- uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+ uiEventLogger.log(id, /* uid= */ 0, mode.ownerPackage)
}
fun logModeOff(mode: ZenMode) {
val id =
if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_OFF else QSModesEvent.QS_MODES_MODE_OFF
- uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+ uiEventLogger.log(id, /* uid= */ 0, mode.ownerPackage)
}
fun logModeSettings(mode: ZenMode) {
val id =
if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_SETTINGS
else QSModesEvent.QS_MODES_MODE_SETTINGS
- uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+ uiEventLogger.log(id, /* uid= */ 0, mode.ownerPackage)
}
fun logOpenDurationDialog(mode: ZenMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 07f1c3470c83..dc07202dc486 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -71,10 +71,10 @@ constructor(
mode.id in prevIds -> true
// Mode is enabled -> show if active (so user can toggle off), or if it
// can be manually toggled on
- mode.rule.isEnabled -> mode.isActive || mode.rule.isManualInvocationAllowed
+ mode.isEnabled -> mode.isActive || mode.isManualInvocationAllowed
// Mode was created as disabled, or disabled by the app that owns it ->
// will be shown with a "Not set" text
- !mode.rule.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
+ !mode.isEnabled -> mode.status == ZenMode.Status.DISABLED_BY_OTHER
else -> false
}
}
@@ -97,13 +97,13 @@ constructor(
if (mode.isActive) R.string.zen_mode_on else R.string.zen_mode_off
),
onClick = {
- if (!mode.rule.isEnabled) {
+ if (!mode.isEnabled) {
openSettings(mode)
} else if (mode.isActive) {
dialogEventLogger.logModeOff(mode)
zenModeInteractor.deactivateMode(mode)
} else {
- if (mode.rule.isManualInvocationAllowed) {
+ if (mode.isManualInvocationAllowed) {
if (zenModeInteractor.shouldAskForZenDuration(mode)) {
dialogEventLogger.logOpenDurationDialog(mode)
// NOTE: The dialog handles turning on the mode itself.
@@ -144,10 +144,10 @@ constructor(
* readers, and for the tile subtext will be augmented with the current status of the mode.
*/
private fun getModeDescription(mode: ZenMode, forAccessibility: Boolean): String? {
- if (!mode.rule.isEnabled) {
+ if (!mode.isEnabled) {
return context.resources.getString(R.string.zen_mode_set_up)
}
- if (!mode.rule.isManualInvocationAllowed && !mode.isActive) {
+ if (!mode.isManualInvocationAllowed && !mode.isActive) {
return context.resources.getString(R.string.zen_mode_no_manual_invocation)
}
return if (forAccessibility)