diff --git a/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java b/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java index 0c18be1b4a7..b5e5dc7295a 100644 --- a/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java +++ b/src/com/android/settings/notification/modes/AbstractZenModePreferenceController.java @@ -17,6 +17,7 @@ package com.android.settings.notification.modes; import android.app.AutomaticZenRule; +import android.app.Flags; import android.content.Context; import androidx.annotation.NonNull; @@ -55,6 +56,11 @@ abstract class AbstractZenModePreferenceController extends AbstractPreferenceCon return mKey; } + @Override + public boolean isAvailable() { + return Flags.modesUi(); + } + // Called by the parent Fragment onStart, which means it will happen before resume. public void updateZenMode(@NonNull Preference preference, @NonNull ZenMode zenMode) { mZenMode = zenMode; diff --git a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java index dbeaa2cceca..ff5c1116223 100644 --- a/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java +++ b/src/com/android/settings/notification/modes/ZenModesListPreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.notification.modes; import android.app.AutomaticZenRule; import android.app.Flags; import android.content.Context; +import android.content.res.Resources; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -25,9 +26,12 @@ import androidx.fragment.app.Fragment; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; -import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.search.SearchIndexableRaw; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -35,7 +39,7 @@ import java.util.Map; * 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 { +public class ZenModesListPreferenceController extends BasePreferenceController { protected static final String KEY = "zen_modes_list"; @Nullable @@ -44,7 +48,7 @@ public class ZenModesListPreferenceController extends AbstractPreferenceControll public ZenModesListPreferenceController(Context context, @Nullable Fragment parent, @NonNull ZenModesBackend backend) { - super(context); + super(context, KEY); mParent = parent; mBackend = backend; } @@ -55,8 +59,9 @@ public class ZenModesListPreferenceController extends AbstractPreferenceControll } @Override - public boolean isAvailable() { - return Flags.modesUi(); + @AvailabilityStatus + public int getAvailabilityStatus() { + return Flags.modesUi() ? AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE; } @Override @@ -95,4 +100,27 @@ public class ZenModesListPreferenceController extends AbstractPreferenceControll category.removePreferenceRecursively(key); } } + + // Provide search data for the modes, which will allow users to reach the modes page if they + // search for a mode name. + @Override + public void updateDynamicRawDataToIndex(List rawData) { + // Don't add anything if flag is off. In theory this preference controller itself shouldn't + // be available in that case, but we check anyway to be sure. + if (!Flags.modesUi()) { + return; + } + if (mBackend == null) { + return; + } + + final Resources res = mContext.getResources(); + for (ZenMode mode : mBackend.getModes()) { + SearchIndexableRaw data = new SearchIndexableRaw(mContext); + data.key = mode.getId(); + data.title = mode.getRule().getName(); + data.screenTitle = res.getString(R.string.zen_modes_list_title); + rawData.add(data); + } + } } diff --git a/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java new file mode 100644 index 00000000000..0297841a2fe --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/modes/ZenModesListPreferenceControllerTest.java @@ -0,0 +1,154 @@ +/* + * 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 android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.app.AutomaticZenRule; +import android.app.Flags; +import android.content.Context; +import android.net.Uri; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; +import android.service.notification.ZenPolicy; + +import com.android.settingslib.search.SearchIndexableRaw; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +public class ZenModesListPreferenceControllerTest { + private static final String TEST_MODE_ID = "test_mode"; + private static final String TEST_MODE_NAME = "Test Mode"; + private static final ZenMode TEST_MODE = new ZenMode( + TEST_MODE_ID, + new AutomaticZenRule.Builder(TEST_MODE_NAME, Uri.parse("test_uri")) + .setType(AutomaticZenRule.TYPE_BEDTIME) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build()) + .build(), + false); + + private static final ZenMode TEST_MANUAL_MODE = ZenMode.manualDndMode( + new AutomaticZenRule.Builder("Do Not Disturb", Uri.EMPTY) + .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) + .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build()) + .build(), + false); + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule( + SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + + private Context mContext; + + @Mock + private ZenModesBackend mBackend; + + private ZenModesListPreferenceController mPrefController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + + mPrefController = new ZenModesListPreferenceController(mContext, null, mBackend); + } + + @Test + @DisableFlags(Flags.FLAG_MODES_UI) + public void testModesUiOff_notAvailableAndNoSearchData() { + // There exist modes + when(mBackend.getModes()).thenReturn(List.of(TEST_MANUAL_MODE, TEST_MODE)); + + assertThat(mPrefController.isAvailable()).isFalse(); + List data = new ArrayList<>(); + mPrefController.updateDynamicRawDataToIndex(data); + assertThat(data).isEmpty(); // despite existence of modes + } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI) + public void testUpdateDynamicRawDataToIndex_empty() { + // Case of no modes. + when(mBackend.getModes()).thenReturn(new ArrayList<>()); + + List data = new ArrayList<>(); + mPrefController.updateDynamicRawDataToIndex(data); + assertThat(data).isEmpty(); + } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI) + public void testUpdateDynamicRawDataToIndex_oneMode() { + // One mode present, confirm it's the correct one + when(mBackend.getModes()).thenReturn(List.of(TEST_MODE)); + + List data = new ArrayList<>(); + mPrefController.updateDynamicRawDataToIndex(data); + assertThat(data).hasSize(1); + + SearchIndexableRaw item = data.get(0); + assertThat(item.key).isEqualTo(TEST_MODE_ID); + assertThat(item.title).isEqualTo(TEST_MODE_NAME); + + // Changing mode data so there's a different one mode doesn't keep any previous data + // (and setting that state up in the caller) + when(mBackend.getModes()).thenReturn(List.of(TEST_MANUAL_MODE)); + List newData = new ArrayList<>(); + mPrefController.updateDynamicRawDataToIndex(newData); + assertThat(newData).hasSize(1); + + SearchIndexableRaw newItem = newData.get(0); + assertThat(newItem.key).isEqualTo(ZenMode.MANUAL_DND_MODE_ID); + assertThat(newItem.title).isEqualTo("Do Not Disturb"); // set above + } + + @Test + @EnableFlags(Flags.FLAG_MODES_UI) + public void testUpdateDynamicRawDataToIndex_multipleModes() { + when(mBackend.getModes()).thenReturn(List.of(TEST_MANUAL_MODE, TEST_MODE)); + + List data = new ArrayList<>(); + mPrefController.updateDynamicRawDataToIndex(data); + assertThat(data).hasSize(2); + + // Should keep the order presented by getModes() + SearchIndexableRaw item0 = data.get(0); + assertThat(item0.key).isEqualTo(ZenMode.MANUAL_DND_MODE_ID); + assertThat(item0.title).isEqualTo("Do Not Disturb"); // set above + + SearchIndexableRaw item1 = data.get(1); + assertThat(item1.key).isEqualTo(TEST_MODE_ID); + assertThat(item1.title).isEqualTo(TEST_MODE_NAME); + } +}