From eff863e5a3e0ff3b372b254f208fbc12fdd52f72 Mon Sep 17 00:00:00 2001 From: Yuri Lin Date: Thu, 11 Apr 2024 17:12:24 -0400 Subject: [PATCH] Redirect to new modes page when modes_ui is on. The new modes page has essentially no content yet, but this is a rough proof of concept for the existing abstract classes and hopefully serves as a building block for future changes. It lists modes and those preferences lead to contentless pages that will be fleshed out later.. Flag: android.app.modes_ui Bug: 327260745 Test: ZenModePreferenceControllerTest Test: manual; individual pages only have skeleton functionality and should be unit tested in future CLs Change-Id: I12f48b48f761e3c9ff1a173445b15f7536d34edb --- res/values/strings.xml | 3 + res/xml/modes_list_settings.xml | 28 +++++ res/xml/modes_rule_settings.xml | 25 ++++ .../notification/modes/ZenModeFragment.java | 78 ++++++++++++ .../modes/ZenModeListPreference.java | 65 ++++++++++ .../modes/ZenModesListFragment.java | 113 ++++++++++++++++++ .../ZenModesListPreferenceController.java | 80 +++++++++++++ .../zen/ZenModePreferenceController.java | 18 ++- .../zen/ZenModePreferenceControllerTest.java | 29 +++++ 9 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 res/xml/modes_list_settings.xml create mode 100644 res/xml/modes_rule_settings.xml create mode 100644 src/com/android/settings/notification/modes/ZenModeFragment.java create mode 100644 src/com/android/settings/notification/modes/ZenModeListPreference.java create mode 100644 src/com/android/settings/notification/modes/ZenModesListFragment.java create mode 100644 src/com/android/settings/notification/modes/ZenModesListPreferenceController.java 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()); + } }