1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.notification.modes; 18 19 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY; 20 21 import static com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND_INACTIVE; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.mockito.Mockito.when; 26 27 import android.app.AutomaticZenRule; 28 import android.app.Flags; 29 import android.content.Context; 30 import android.net.Uri; 31 import android.platform.test.annotations.DisableFlags; 32 import android.platform.test.annotations.EnableFlags; 33 import android.platform.test.flag.junit.SetFlagsRule; 34 import android.service.notification.ZenPolicy; 35 36 import androidx.preference.Preference; 37 import androidx.preference.PreferenceCategory; 38 import androidx.preference.PreferenceGroup; 39 import androidx.preference.PreferenceManager; 40 import androidx.preference.PreferenceScreen; 41 42 import com.android.settingslib.notification.modes.TestModeBuilder; 43 import com.android.settingslib.notification.modes.ZenIconLoader; 44 import com.android.settingslib.notification.modes.ZenMode; 45 import com.android.settingslib.notification.modes.ZenModesBackend; 46 import com.android.settingslib.search.SearchIndexableRaw; 47 48 import com.google.common.collect.ImmutableList; 49 import com.google.common.util.concurrent.MoreExecutors; 50 51 import org.junit.Before; 52 import org.junit.Rule; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 import org.mockito.Mock; 56 import org.mockito.MockitoAnnotations; 57 import org.robolectric.RobolectricTestRunner; 58 import org.robolectric.RuntimeEnvironment; 59 60 import java.util.ArrayList; 61 import java.util.Comparator; 62 import java.util.List; 63 64 @RunWith(RobolectricTestRunner.class) 65 public class ZenModesListPreferenceControllerTest { 66 private static final String TEST_MODE_ID = "test_mode"; 67 private static final String TEST_MODE_NAME = "Test Mode"; 68 69 private static final ZenMode TEST_MODE = new TestModeBuilder() 70 .setId(TEST_MODE_ID) 71 .setAzr(new AutomaticZenRule.Builder(TEST_MODE_NAME, Uri.parse("test_uri")) 72 .setType(AutomaticZenRule.TYPE_BEDTIME) 73 .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY) 74 .setZenPolicy(new ZenPolicy.Builder().allowAllSounds().build()) 75 .build()) 76 .build(); 77 78 @Rule 79 public final SetFlagsRule mSetFlagsRule = new SetFlagsRule( 80 SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); 81 82 private Context mContext; 83 84 @Mock 85 private ZenModesBackend mBackend; 86 87 private ZenModesListPreferenceController mPrefController; 88 private PreferenceCategory mPreference; 89 90 @Before setup()91 public void setup() { 92 MockitoAnnotations.initMocks(this); 93 mContext = RuntimeEnvironment.application; 94 95 mPreference = new PreferenceCategory(mContext); 96 PreferenceManager preferenceManager = new PreferenceManager(mContext); 97 PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext); 98 preferenceScreen.addPreference(mPreference); 99 100 mPrefController = new ZenModesListPreferenceController(mContext, mBackend, 101 new ZenIconLoader(MoreExecutors.newDirectExecutorService())); 102 } 103 104 @Test 105 @EnableFlags(Flags.FLAG_MODES_UI) updateState_addsPreferences()106 public void updateState_addsPreferences() { 107 ImmutableList<ZenMode> modes = ImmutableList.of(newMode("One"), newMode("Two"), 108 newMode("Three"), newMode("Four"), newMode("Five")); 109 when(mBackend.getModes()).thenReturn(modes); 110 111 mPrefController.updateState(mPreference); 112 113 assertThat(mPreference.getPreferenceCount()).isEqualTo(5); 114 List<ZenModesListItemPreference> itemPreferences = getModeListItems(mPreference); 115 assertThat(itemPreferences.stream().map(ZenModesListItemPreference::getZenMode).toList()) 116 .containsExactlyElementsIn(modes) 117 .inOrder(); 118 } 119 120 @Test 121 @EnableFlags(Flags.FLAG_MODES_UI) updateState_secondTime_updatesPreferences()122 public void updateState_secondTime_updatesPreferences() { 123 ImmutableList<ZenMode> modes = ImmutableList.of(newMode("One"), newMode("Two"), 124 newMode("Three"), newMode("Four"), newMode("Five")); 125 when(mBackend.getModes()).thenReturn(modes); 126 mPrefController.updateState(mPreference); 127 128 assertThat(mPreference.getPreferenceCount()).isEqualTo(5); 129 List<ZenModesListItemPreference> oldPreferences = getModeListItems(mPreference); 130 131 ImmutableList<ZenMode> updatedModes = ImmutableList.of(modes.get(0), modes.get(1), 132 newMode("Two.1"), newMode("Two.2"), modes.get(2), /* deleted "Four" */ 133 modes.get(4)); 134 when(mBackend.getModes()).thenReturn(updatedModes); 135 mPrefController.updateState(mPreference); 136 137 List<ZenModesListItemPreference> newPreferences = getModeListItems(mPreference); 138 assertThat(newPreferences.stream().map(ZenModesListItemPreference::getZenMode).toList()) 139 .containsExactlyElementsIn(updatedModes) 140 .inOrder(); 141 142 // Verify that the old preference controllers were reused instead of creating new ones. 143 assertThat(newPreferences.get(0)).isSameInstanceAs(oldPreferences.get(0)); 144 assertThat(newPreferences.get(1)).isSameInstanceAs(oldPreferences.get(1)); 145 assertThat(newPreferences.get(4)).isSameInstanceAs(oldPreferences.get(2)); 146 assertThat(newPreferences.get(5)).isSameInstanceAs(oldPreferences.get(4)); 147 } 148 149 @Test 150 @DisableFlags(Flags.FLAG_MODES_UI) testModesUiOff_notAvailableAndNoSearchData()151 public void testModesUiOff_notAvailableAndNoSearchData() { 152 // There exist modes 153 when(mBackend.getModes()).thenReturn(List.of(MANUAL_DND_INACTIVE, TEST_MODE)); 154 155 assertThat(mPrefController.isAvailable()).isFalse(); 156 List<SearchIndexableRaw> data = new ArrayList<>(); 157 mPrefController.updateDynamicRawDataToIndex(data); 158 assertThat(data).isEmpty(); // despite existence of modes 159 } 160 161 @Test 162 @EnableFlags(Flags.FLAG_MODES_UI) testUpdateDynamicRawDataToIndex_empty()163 public void testUpdateDynamicRawDataToIndex_empty() { 164 // Case of no modes. 165 when(mBackend.getModes()).thenReturn(new ArrayList<>()); 166 167 List<SearchIndexableRaw> data = new ArrayList<>(); 168 mPrefController.updateDynamicRawDataToIndex(data); 169 assertThat(data).isEmpty(); 170 } 171 172 @Test 173 @EnableFlags(Flags.FLAG_MODES_UI) testUpdateDynamicRawDataToIndex_oneMode()174 public void testUpdateDynamicRawDataToIndex_oneMode() { 175 // One mode present, confirm it's the correct one 176 when(mBackend.getModes()).thenReturn(List.of(TEST_MODE)); 177 178 List<SearchIndexableRaw> data = new ArrayList<>(); 179 mPrefController.updateDynamicRawDataToIndex(data); 180 assertThat(data).hasSize(1); 181 182 SearchIndexableRaw item = data.get(0); 183 assertThat(item.key).isEqualTo(TEST_MODE_ID); 184 assertThat(item.title).isEqualTo(TEST_MODE_NAME); 185 186 // Changing mode data so there's a different one mode doesn't keep any previous data 187 // (and setting that state up in the caller) 188 when(mBackend.getModes()).thenReturn(List.of(MANUAL_DND_INACTIVE)); 189 List<SearchIndexableRaw> newData = new ArrayList<>(); 190 mPrefController.updateDynamicRawDataToIndex(newData); 191 assertThat(newData).hasSize(1); 192 193 SearchIndexableRaw newItem = newData.get(0); 194 assertThat(newItem.key).isEqualTo(MANUAL_DND_INACTIVE.getId()); 195 assertThat(newItem.title).isEqualTo("Do Not Disturb"); // set above 196 } 197 198 @Test 199 @EnableFlags(Flags.FLAG_MODES_UI) testUpdateDynamicRawDataToIndex_multipleModes()200 public void testUpdateDynamicRawDataToIndex_multipleModes() { 201 when(mBackend.getModes()).thenReturn(List.of(MANUAL_DND_INACTIVE, TEST_MODE)); 202 203 List<SearchIndexableRaw> data = new ArrayList<>(); 204 mPrefController.updateDynamicRawDataToIndex(data); 205 assertThat(data).hasSize(2); 206 207 // Should keep the order presented by getModes() 208 SearchIndexableRaw item0 = data.get(0); 209 assertThat(item0.key).isEqualTo(MANUAL_DND_INACTIVE.getId()); 210 assertThat(item0.title).isEqualTo("Do Not Disturb"); // set above 211 212 SearchIndexableRaw item1 = data.get(1); 213 assertThat(item1.key).isEqualTo(TEST_MODE_ID); 214 assertThat(item1.title).isEqualTo(TEST_MODE_NAME); 215 } 216 newMode(String id)217 private static ZenMode newMode(String id) { 218 return new TestModeBuilder().setId(id).setName("Mode " + id).build(); 219 } 220 221 /** 222 * Returns the child preferences of the {@code group}, sorted by their 223 * {@link Preference#getOrder} value (which is the order they will be sorted by and displayed 224 * in the UI). 225 */ getModeListItems(PreferenceGroup group)226 private List<ZenModesListItemPreference> getModeListItems(PreferenceGroup group) { 227 ArrayList<ZenModesListItemPreference> items = new ArrayList<>(); 228 for (int i = 0; i < group.getPreferenceCount(); i++) { 229 items.add((ZenModesListItemPreference) group.getPreference(i)); 230 } 231 items.sort(Comparator.comparing(Preference::getOrder)); 232 return items; 233 } 234 } 235