1 /*
2  * Copyright (C) 2017 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.systemui.statusbar.phone;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertTrue;
22 import static org.mockito.ArgumentMatchers.any;
23 import static org.mockito.ArgumentMatchers.anyBoolean;
24 import static org.mockito.ArgumentMatchers.anyInt;
25 import static org.mockito.ArgumentMatchers.isNotNull;
26 import static org.mockito.ArgumentMatchers.isNull;
27 import static org.mockito.Mockito.doNothing;
28 import static org.mockito.Mockito.doReturn;
29 import static org.mockito.Mockito.inOrder;
30 import static org.mockito.Mockito.never;
31 import static org.mockito.Mockito.spy;
32 import static org.mockito.Mockito.verify;
33 import static org.mockito.Mockito.when;
34 
35 import android.content.ComponentName;
36 import android.content.ContentResolver;
37 import android.content.Context;
38 import android.content.ContextWrapper;
39 import android.hardware.display.ColorDisplayManager;
40 import android.hardware.display.NightDisplayListener;
41 import android.os.Handler;
42 import android.os.UserHandle;
43 import android.provider.Settings;
44 import android.testing.AndroidTestingRunner;
45 import android.testing.TestableContentResolver;
46 import android.testing.TestableContext;
47 import android.testing.TestableLooper;
48 import android.testing.TestableLooper.RunWithLooper;
49 
50 import androidx.test.filters.SmallTest;
51 
52 import com.android.systemui.R;
53 import com.android.systemui.SysuiTestCase;
54 import com.android.systemui.qs.AutoAddTracker;
55 import com.android.systemui.qs.QSTileHost;
56 import com.android.systemui.qs.SecureSetting;
57 import com.android.systemui.statusbar.phone.AutoTileManagerTest.MyContextWrapper;
58 import com.android.systemui.statusbar.policy.CastController;
59 import com.android.systemui.statusbar.policy.CastController.CastDevice;
60 import com.android.systemui.statusbar.policy.DataSaverController;
61 import com.android.systemui.statusbar.policy.HotspotController;
62 
63 import org.junit.After;
64 import org.junit.Before;
65 import org.junit.Test;
66 import org.junit.runner.RunWith;
67 import org.mockito.Answers;
68 import org.mockito.InOrder;
69 import org.mockito.Mock;
70 import org.mockito.MockitoAnnotations;
71 
72 import java.util.Collections;
73 import java.util.List;
74 
75 @RunWith(AndroidTestingRunner.class)
76 @RunWithLooper
77 @SmallTest
78 public class AutoTileManagerTest extends SysuiTestCase {
79 
80     private static final String TEST_SETTING = "setting";
81     private static final String TEST_SPEC = "spec";
82     private static final String TEST_SETTING_COMPONENT = "setting_component";
83     private static final String TEST_COMPONENT = "test_pkg/test_cls";
84     private static final String TEST_CUSTOM_SPEC = "custom(" + TEST_COMPONENT + ")";
85     private static final String SEPARATOR = AutoTileManager.SETTING_SEPARATOR;
86 
87     private static final int USER = 0;
88 
89     @Mock private QSTileHost mQsTileHost;
90     @Mock private AutoAddTracker mAutoAddTracker;
91     @Mock private CastController mCastController;
92     @Mock private HotspotController mHotspotController;
93     @Mock private DataSaverController mDataSaverController;
94     @Mock private ManagedProfileController mManagedProfileController;
95     @Mock private NightDisplayListener mNightDisplayListener;
96     @Mock(answer = Answers.RETURNS_SELF)
97     private AutoAddTracker.Builder mAutoAddTrackerBuilder;
98     @Mock private Context mUserContext;
99 
100     private AutoTileManager mAutoTileManager;
101 
102     @Before
setUp()103     public void setUp() throws Exception {
104         MockitoAnnotations.initMocks(this);
105 
106         mContext.getOrCreateTestableResources().addOverride(
107                 R.array.config_quickSettingsAutoAdd,
108                 new String[] {
109                         TEST_SETTING + SEPARATOR + TEST_SPEC,
110                         TEST_SETTING_COMPONENT + SEPARATOR + TEST_CUSTOM_SPEC
111                 }
112         );
113 
114         when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker);
115         when(mQsTileHost.getUserContext()).thenReturn(mUserContext);
116         when(mUserContext.getUser()).thenReturn(UserHandle.of(USER));
117 
118         mAutoTileManager = createAutoTileManager(new
119                 MyContextWrapper(mContext));
120     }
121 
122     @After
tearDown()123     public void tearDown() {
124         mAutoTileManager.destroy();
125     }
126 
createAutoTileManager(Context context)127     private AutoTileManager createAutoTileManager(Context context) {
128         return new AutoTileManager(context, mAutoAddTrackerBuilder, mQsTileHost,
129                 Handler.createAsync(TestableLooper.get(this).getLooper()),
130                 mHotspotController,
131                 mDataSaverController,
132                 mManagedProfileController,
133                 mNightDisplayListener,
134                 mCastController);
135     }
136 
137     @Test
testChangeUserCallbacksStoppedAndStarted()138     public void testChangeUserCallbacksStoppedAndStarted() throws Exception {
139         TestableLooper.get(this).runWithLooper(() ->
140                 mAutoTileManager.changeUser(UserHandle.of(USER + 1))
141         );
142 
143         InOrder inOrderHotspot = inOrder(mHotspotController);
144         inOrderHotspot.verify(mHotspotController).removeCallback(any());
145         inOrderHotspot.verify(mHotspotController).addCallback(any());
146 
147         InOrder inOrderDataSaver = inOrder(mDataSaverController);
148         inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
149         inOrderDataSaver.verify(mDataSaverController).addCallback(any());
150 
151         InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
152         inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
153         inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
154 
155         if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
156             InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
157             inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
158             inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
159         }
160 
161         InOrder inOrderCast = inOrder(mCastController);
162         inOrderCast.verify(mCastController).removeCallback(any());
163         inOrderCast.verify(mCastController).addCallback(any());
164 
165         SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
166         assertEquals(USER + 1, setting.getCurrentUser());
167         assertTrue(setting.isListening());
168     }
169 
170     @Test
testChangeUserSomeCallbacksNotAdded()171     public void testChangeUserSomeCallbacksNotAdded() throws Exception {
172         when(mAutoAddTracker.isAdded("hotspot")).thenReturn(true);
173         when(mAutoAddTracker.isAdded("work")).thenReturn(true);
174         when(mAutoAddTracker.isAdded("cast")).thenReturn(true);
175         when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
176 
177         TestableLooper.get(this).runWithLooper(() ->
178                 mAutoTileManager.changeUser(UserHandle.of(USER + 1))
179         );
180 
181         verify(mAutoAddTracker).changeUser(UserHandle.of(USER + 1));
182 
183         InOrder inOrderHotspot = inOrder(mHotspotController);
184         inOrderHotspot.verify(mHotspotController).removeCallback(any());
185         inOrderHotspot.verify(mHotspotController, never()).addCallback(any());
186 
187         InOrder inOrderDataSaver = inOrder(mDataSaverController);
188         inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
189         inOrderDataSaver.verify(mDataSaverController).addCallback(any());
190 
191         InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
192         inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
193         inOrderManagedProfile.verify(mManagedProfileController, never()).addCallback(any());
194 
195         if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
196             InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
197             inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
198             inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
199         }
200 
201         InOrder inOrderCast = inOrder(mCastController);
202         inOrderCast.verify(mCastController).removeCallback(any());
203         inOrderCast.verify(mCastController, never()).addCallback(any());
204 
205         SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
206         assertEquals(USER + 1, setting.getCurrentUser());
207         assertFalse(setting.isListening());
208     }
209 
210     @Test
testGetCurrentUserId()211     public void testGetCurrentUserId() throws Exception {
212         assertEquals(USER, mAutoTileManager.getCurrentUserId());
213 
214         TestableLooper.get(this).runWithLooper(() ->
215                 mAutoTileManager.changeUser(UserHandle.of(USER + 100))
216         );
217 
218         assertEquals(USER + 100, mAutoTileManager.getCurrentUserId());
219     }
220 
221     @Test
nightTileAdded_whenActivated()222     public void nightTileAdded_whenActivated() {
223         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
224             return;
225         }
226         mAutoTileManager.mNightDisplayCallback.onActivated(true);
227         verify(mQsTileHost).addTile("night");
228     }
229 
230     @Test
nightTileNotAdded_whenDeactivated()231     public void nightTileNotAdded_whenDeactivated() {
232         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
233             return;
234         }
235         mAutoTileManager.mNightDisplayCallback.onActivated(false);
236         verify(mQsTileHost, never()).addTile("night");
237     }
238 
239     @Test
nightTileAdded_whenNightModeTwilight()240     public void nightTileAdded_whenNightModeTwilight() {
241         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
242             return;
243         }
244         mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
245                 ColorDisplayManager.AUTO_MODE_TWILIGHT);
246         verify(mQsTileHost).addTile("night");
247     }
248 
249     @Test
nightTileAdded_whenNightModeCustom()250     public void nightTileAdded_whenNightModeCustom() {
251         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
252             return;
253         }
254         mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
255                 ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
256         verify(mQsTileHost).addTile("night");
257     }
258 
259     @Test
nightTileNotAdded_whenNightModeDisabled()260     public void nightTileNotAdded_whenNightModeDisabled() {
261         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
262             return;
263         }
264         mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
265                 ColorDisplayManager.AUTO_MODE_DISABLED);
266         verify(mQsTileHost, never()).addTile("night");
267     }
268 
buildFakeCastDevice(boolean isCasting)269     private static List<CastDevice> buildFakeCastDevice(boolean isCasting) {
270         CastDevice cd = new CastDevice();
271         cd.state = isCasting ? CastDevice.STATE_CONNECTED : CastDevice.STATE_DISCONNECTED;
272         return Collections.singletonList(cd);
273     }
274 
275     @Test
castTileAdded_whenDeviceIsCasting()276     public void castTileAdded_whenDeviceIsCasting() {
277         doReturn(buildFakeCastDevice(true)).when(mCastController).getCastDevices();
278         mAutoTileManager.mCastCallback.onCastDevicesChanged();
279         verify(mQsTileHost).addTile("cast");
280     }
281 
282     @Test
castTileNotAdded_whenDeviceIsNotCasting()283     public void castTileNotAdded_whenDeviceIsNotCasting() {
284         doReturn(buildFakeCastDevice(false)).when(mCastController).getCastDevices();
285         mAutoTileManager.mCastCallback.onCastDevicesChanged();
286         verify(mQsTileHost, never()).addTile("cast");
287     }
288 
289     @Test
testSettingTileAdded_onChanged()290     public void testSettingTileAdded_onChanged() {
291         changeValue(TEST_SETTING, 1);
292         waitForIdleSync();
293         verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
294         verify(mQsTileHost).addTile(TEST_SPEC);
295     }
296 
297     @Test
testSettingTileAddedComponentAtEnd_onChanged()298     public void testSettingTileAddedComponentAtEnd_onChanged() {
299         changeValue(TEST_SETTING_COMPONENT, 1);
300         waitForIdleSync();
301         verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC);
302         verify(mQsTileHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)
303             , /* end */ true);
304     }
305 
306     @Test
testSettingTileAdded_onlyOnce()307     public void testSettingTileAdded_onlyOnce() {
308         changeValue(TEST_SETTING, 1);
309         waitForIdleSync();
310         TestableLooper.get(this).processAllMessages();
311         changeValue(TEST_SETTING, 2);
312         waitForIdleSync();
313         verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
314         verify(mQsTileHost).addTile(TEST_SPEC);
315     }
316 
317     @Test
testSettingTileNotAdded_onChangedTo0()318     public void testSettingTileNotAdded_onChangedTo0() {
319         changeValue(TEST_SETTING, 0);
320         waitForIdleSync();
321         verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
322         verify(mQsTileHost, never()).addTile(TEST_SPEC);
323     }
324 
325     @Test
testSettingTileNotAdded_ifPreviouslyAdded()326     public void testSettingTileNotAdded_ifPreviouslyAdded() {
327         when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
328 
329         changeValue(TEST_SETTING, 1);
330         waitForIdleSync();
331         verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
332         verify(mQsTileHost, never()).addTile(TEST_SPEC);
333     }
334 
335     @Test
testEmptyArray_doesNotCrash()336     public void testEmptyArray_doesNotCrash() {
337         mContext.getOrCreateTestableResources().addOverride(
338                 R.array.config_quickSettingsAutoAdd, new String[0]);
339         createAutoTileManager(mContext).destroy();
340     }
341 
342     @Test
testMissingConfig_doesNotCrash()343     public void testMissingConfig_doesNotCrash() {
344         mContext.getOrCreateTestableResources().addOverride(
345                 R.array.config_quickSettingsAutoAdd, null);
346         createAutoTileManager(mContext).destroy();
347     }
348 
349     // Will only notify if it's listening
changeValue(String key, int value)350     private void changeValue(String key, int value) {
351         SecureSetting s = mAutoTileManager.getSecureSettingForKey(key);
352         Settings.Secure.putInt(mContext.getContentResolver(), key, value);
353         if (s != null && s.isListening()) {
354             s.onChange(false);
355         }
356     }
357 
358     class MyContextWrapper extends ContextWrapper {
359 
360         private TestableContentResolver mSpiedTCR;
361 
MyContextWrapper(TestableContext context)362         MyContextWrapper(TestableContext context) {
363             super(context);
364             mSpiedTCR = spy(context.getContentResolver());
365             doNothing().when(mSpiedTCR).registerContentObserver(any(), anyBoolean(), any(),
366                     anyInt());
367             doNothing().when(mSpiedTCR).unregisterContentObserver(any());
368         }
369 
370         @Override
getContentResolver()371         public ContentResolver getContentResolver() {
372             return mSpiedTCR;
373         }
374     }
375 }
376