diff --git a/res/values/strings.xml b/res/values/strings.xml
index 90cc39c9fad..864719d3949 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -7882,6 +7882,9 @@
Do Not Disturb
+
+ Priority Modes
+
Only get notified by important people and apps
diff --git a/res/xml/modes_list_settings.xml b/res/xml/modes_list_settings.xml
new file mode 100644
index 00000000000..c6b6200bdcb
--- /dev/null
+++ b/res/xml/modes_list_settings.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/modes_rule_settings.xml b/res/xml/modes_rule_settings.xml
new file mode 100644
index 00000000000..d7e26946829
--- /dev/null
+++ b/res/xml/modes_rule_settings.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/com/android/settings/notification/modes/ZenModeFragment.java b/src/com/android/settings/notification/modes/ZenModeFragment.java
new file mode 100644
index 00000000000..616332e0d1f
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeFragment.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 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.settings.notification.modes;
+
+import android.app.AutomaticZenRule;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ZenModeFragment extends ZenModeFragmentBase {
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.modes_rule_settings;
+ }
+
+ @Override
+ protected List createPreferenceControllers(Context context) {
+ // TODO: fill in with all the elements of this page. Each should be an instance of
+ // {@link AbstractZenModePreferenceController}.
+ List prefControllers = new ArrayList<>();
+ return prefControllers;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ // Set title for the entire screen
+ ZenMode mode = getMode();
+ AutomaticZenRule azr = getAZR();
+ if (mode == null || azr == null) {
+ return;
+ }
+ getActivity().setTitle(azr.getName());
+
+ // TODO: b/308819292 - implement the real screen!
+ final PreferenceScreen screen = getPreferenceScreen();
+ if (screen == null) {
+ return;
+ }
+
+ Preference tmpPref = screen.findPreference("zen_mode_test");
+ if (tmpPref == null) {
+ return;
+ }
+ tmpPref.setTitle(azr.getTriggerDescription());
+ tmpPref.setSummary("active?: " + mode.isActive());
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO: b/332937635 - make this the correct metrics category
+ return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModeListPreference.java b/src/com/android/settings/notification/modes/ZenModeListPreference.java
new file mode 100644
index 00000000000..cb0456140bf
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModeListPreference.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2024 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.settings.notification.modes;
+
+import static com.android.settings.notification.modes.ZenModeFragmentBase.MODE_ID;
+
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.settings.core.SubSettingLauncher;
+import com.android.settings.notification.zen.ZenModeSettings;
+import com.android.settingslib.RestrictedPreference;
+
+/**
+ * Preference representing a single mode item on the modes aggregator page. Clicking on this
+ * preference leads to an individual mode's configuration page.
+ */
+public class ZenModeListPreference extends RestrictedPreference {
+ final Context mContext;
+ ZenMode mZenMode;
+
+ ZenModeListPreference(Context context, ZenMode zenMode) {
+ super(context);
+ mContext = context;
+ mZenMode = zenMode;
+ setTitle(mZenMode.getRule().getName());
+ setSummary((mZenMode.isActive() ? "ACTIVE" : "inactive") + ": "
+ + mZenMode.getRule().getTriggerDescription());
+ }
+
+ @Override
+ public void onClick() {
+ // TODO: b/322373473 - This implementation is a hack that just leads to the old DND page
+ // for manual only; remove this in favor of the real implementation.
+ if (mZenMode.isManualDnd()) {
+ new SubSettingLauncher(mContext)
+ .setDestination(ZenModeSettings.class.getName())
+ .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE)
+ .launch();
+ } else {
+ Bundle bundle = new Bundle();
+ bundle.putString(MODE_ID, mZenMode.getId());
+ new SubSettingLauncher(mContext)
+ .setDestination(ZenModeFragment.class.getName())
+ .setArguments(bundle)
+ .setSourceMetricsCategory(SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION)
+ .launch();
+ }
+
+ }
+}
diff --git a/src/com/android/settings/notification/modes/ZenModesListFragment.java b/src/com/android/settings/notification/modes/ZenModesListFragment.java
new file mode 100644
index 00000000000..040621e6d93
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModesListFragment.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 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.settings.notification.modes;
+
+import android.app.NotificationManager;
+import android.app.settings.SettingsEnums;
+import android.content.Context;
+import android.service.notification.ConditionProviderService;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.settings.R;
+import com.android.settings.search.BaseSearchIndexProvider;
+import com.android.settings.utils.ManagedServiceSettings;
+import com.android.settings.utils.ZenServiceListing;
+import com.android.settingslib.core.AbstractPreferenceController;
+import com.android.settingslib.search.SearchIndexable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SearchIndexable
+public class ZenModesListFragment extends ZenModesFragmentBase {
+ protected final ManagedServiceSettings.Config CONFIG = getConditionProviderConfig();
+
+ @Override
+ protected List createPreferenceControllers(Context context) {
+ ZenServiceListing serviceListing = new ZenServiceListing(getContext(), CONFIG);
+ serviceListing.reloadApprovedServices();
+ return buildPreferenceControllers(context, this, serviceListing);
+ }
+
+ private static List buildPreferenceControllers(Context context,
+ @Nullable Fragment parent, @Nullable ZenServiceListing serviceListing) {
+ // We need to redefine ZenModesBackend here even though mBackend exists so that this method
+ // can be static; it must be static to be able to be used in SEARCH_INDEX_DATA_PROVIDER.
+ ZenModesBackend backend = ZenModesBackend.getInstance(context);
+ List controllers = new ArrayList<>();
+ controllers.add(new ZenModesListPreferenceController(
+ context, parent, backend));
+
+ // TODO: b/326442408 - Add controller for "Add Mode" preference/flow, which is what uses
+ // the ZenServiceListing.
+ return controllers;
+ }
+
+ @Override
+ protected void updateZenModeState() {
+ // TODO: b/322373473 -- update any overall description of modes state here if necessary.
+ // Note the preferences linking to individual rules do not need to be updated, as
+ // updateState() is called on all preference controllers whenever the page is resumed.
+ }
+
+ @Override
+ protected int getPreferenceScreenResId() {
+ return R.xml.modes_list_settings;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ // TODO: b/332937635 - add new & set metrics categories correctly
+ return SettingsEnums.NOTIFICATION_ZEN_MODE_AUTOMATION;
+ }
+
+ protected static ManagedServiceSettings.Config getConditionProviderConfig() {
+ return new ManagedServiceSettings.Config.Builder()
+ .setTag(TAG)
+ .setIntentAction(ConditionProviderService.SERVICE_INTERFACE)
+ .setConfigurationIntentAction(NotificationManager.ACTION_AUTOMATIC_ZEN_RULE)
+ .setPermission(android.Manifest.permission.BIND_CONDITION_PROVIDER_SERVICE)
+ .setNoun("condition provider")
+ .build();
+ }
+
+ // TODO: b/322373473 - Add 3-dot options menu with capability to delete modes.
+
+ /**
+ * For Search.
+ */
+ public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider(R.xml.modes_list_settings) {
+
+ @Override
+ public List getNonIndexableKeys(Context context) {
+ final List keys = super.getNonIndexableKeys(context);
+ // TODO: b/332937523 - determine if this should be removed once the preference
+ // controller adds dynamic data to index
+ keys.add(ZenModesListPreferenceController.KEY);
+ return keys;
+ }
+
+ @Override
+ public List createPreferenceControllers(
+ Context context) {
+ return buildPreferenceControllers(context, null, null);
+ }
+ };
+}
diff --git a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
new file mode 100644
index 00000000000..53336c84be3
--- /dev/null
+++ b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 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.settings.notification.modes;
+
+import android.app.Flags;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
+
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * Controller for the PreferenceCategory on the modes aggregator page ({@link ZenModesListFragment})
+ * containing links to each individual mode. This is a central controller that populates and updates
+ * all the preferences that then lead to a mode configuration page.
+ */
+public class ZenModesListPreferenceController extends AbstractPreferenceController {
+ protected static final String KEY = "zen_modes_list";
+
+ @Nullable
+ protected Fragment mParent;
+ protected ZenModesBackend mBackend;
+
+ public ZenModesListPreferenceController(Context context, @Nullable Fragment parent,
+ @NonNull ZenModesBackend backend) {
+ super(context);
+ mParent = parent;
+ mBackend = backend;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return Flags.modesUi();
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ if (mBackend == null) {
+ return;
+ }
+
+ // The preference given us is a PreferenceCategory; create one preference inside the
+ // category for each rule that exists.
+ PreferenceCategory category = (PreferenceCategory) preference;
+
+ // TODO: b/322373473 - This is not the right way to replace these preferences; we should
+ // follow something similar to what
+ // ZenModeAutomaticRulesPreferenceController does to change rules
+ // only as necessary and update them.
+ category.removeAll();
+
+ for (ZenMode mode : mBackend.getModes()) {
+ Preference pref = new ZenModeListPreference(mContext, mode);
+ category.addPreference(pref);
+ }
+ }
+
+}
diff --git a/src/com/android/settings/notification/zen/ZenModePreferenceController.java b/src/com/android/settings/notification/zen/ZenModePreferenceController.java
index 24cf158c966..7c36d80f8ad 100644
--- a/src/com/android/settings/notification/zen/ZenModePreferenceController.java
+++ b/src/com/android/settings/notification/zen/ZenModePreferenceController.java
@@ -16,6 +16,7 @@
package com.android.settings.notification.zen;
+import android.app.Flags;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -27,7 +28,9 @@ import android.provider.Settings;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
+import com.android.settings.notification.modes.ZenModesListFragment;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnPause;
import com.android.settingslib.core.lifecycle.events.OnResume;
@@ -46,7 +49,9 @@ public class ZenModePreferenceController extends BasePreferenceController
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- mSettingObserver = new SettingObserver(screen.findPreference(getPreferenceKey()));
+ Preference preference = screen.findPreference(getPreferenceKey());
+ mSettingObserver = new SettingObserver(preference);
+ maybeSetTitleAndDestination(preference);
}
@Override
@@ -71,11 +76,22 @@ public class ZenModePreferenceController extends BasePreferenceController
@Override
public void updateState(Preference preference) {
super.updateState(preference);
+ maybeSetTitleAndDestination(preference);
if (preference.isEnabled()) {
preference.setSummary(mSummaryBuilder.getSoundSummary());
}
}
+ // Only when modes_ui is active: change title & target fragment.
+ private void maybeSetTitleAndDestination(Preference preference) {
+ if (!Flags.modesUi()) {
+ return;
+ }
+
+ preference.setTitle(R.string.zen_modes_list_title);
+ preference.setFragment(ZenModesListFragment.class.getCanonicalName());
+ }
+
class SettingObserver extends ContentObserver {
private final Uri ZEN_MODE_URI = Settings.Global.getUriFor(Settings.Global.ZEN_MODE);
private final Uri ZEN_MODE_CONFIG_ETAG_URI = Settings.Global.getUriFor(
diff --git a/tests/robotests/src/com/android/settings/notification/zen/ZenModePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/zen/ZenModePreferenceControllerTest.java
index c2abbcdfb40..f611c9b1950 100644
--- a/tests/robotests/src/com/android/settings/notification/zen/ZenModePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/notification/zen/ZenModePreferenceControllerTest.java
@@ -16,10 +16,13 @@
package com.android.settings.notification.zen;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -27,13 +30,20 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.Flags;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
import android.content.Context;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import androidx.preference.Preference;
+import com.android.settings.notification.modes.ZenModesListFragment;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -46,6 +56,9 @@ import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
public class ZenModePreferenceControllerTest {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
@Mock
private Preference mPreference;
@Mock
@@ -96,4 +109,20 @@ public class ZenModePreferenceControllerTest {
verify(mPreference, never()).setSummary(anyString());
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_UI)
+ public void updateState_modesUi_resetsTitleAndFragment() {
+ mController.updateState(mPreference);
+ verify(mPreference).setTitle(anyInt()); // Resource IDs are ints
+ verify(mPreference).setFragment(ZenModesListFragment.class.getCanonicalName());
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_MODES_UI)
+ public void updateState_noModesUi_doesNotSetTitleAndFragment() {
+ mController.updateState(mPreference);
+ verify(mPreference, never()).setTitle(anyInt());
+ verify(mPreference, never()).setFragment(anyString());
+ }
}