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