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 com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertNull;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 import static org.mockito.ArgumentMatchers.any;
27 import static org.mockito.ArgumentMatchers.isNotNull;
28 import static org.mockito.ArgumentMatchers.isNull;
29 import static org.mockito.Mockito.doReturn;
30 import static org.mockito.Mockito.inOrder;
31 import static org.mockito.Mockito.mock;
32 import static org.mockito.Mockito.never;
33 import static org.mockito.Mockito.times;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
36 
37 import android.content.ComponentName;
38 import android.content.Context;
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.testing.AndroidTestingRunner;
44 import android.testing.TestableLooper;
45 import android.testing.TestableLooper.RunWithLooper;
46 
47 import androidx.test.filters.SmallTest;
48 
49 import com.android.systemui.R;
50 import com.android.systemui.SysuiTestCase;
51 import com.android.systemui.qs.AutoAddTracker;
52 import com.android.systemui.qs.QSTileHost;
53 import com.android.systemui.qs.ReduceBrightColorsController;
54 import com.android.systemui.qs.SecureSetting;
55 import com.android.systemui.statusbar.policy.CastController;
56 import com.android.systemui.statusbar.policy.CastController.CastDevice;
57 import com.android.systemui.statusbar.policy.DataSaverController;
58 import com.android.systemui.statusbar.policy.DeviceControlsController;
59 import com.android.systemui.statusbar.policy.HotspotController;
60 import com.android.systemui.statusbar.policy.WalletController;
61 import com.android.systemui.util.settings.FakeSettings;
62 import com.android.systemui.util.settings.SecureSettings;
63 
64 import org.junit.After;
65 import org.junit.Before;
66 import org.junit.Test;
67 import org.junit.runner.RunWith;
68 import org.mockito.Answers;
69 import org.mockito.InOrder;
70 import org.mockito.Mock;
71 import org.mockito.MockitoAnnotations;
72 
73 import java.util.Collections;
74 import java.util.List;
75 
76 import javax.inject.Named;
77 
78 @RunWith(AndroidTestingRunner.class)
79 @RunWithLooper
80 @SmallTest
81 public class AutoTileManagerTest extends SysuiTestCase {
82 
83     private static final String TEST_SETTING = "setting";
84     private static final String TEST_SPEC = "spec";
85     private static final String TEST_SETTING_COMPONENT = "setting_component";
86     private static final String TEST_COMPONENT = "test_pkg/test_cls";
87     private static final String TEST_CUSTOM_SPEC = "custom(" + TEST_COMPONENT + ")";
88     private static final String SEPARATOR = AutoTileManager.SETTING_SEPARATOR;
89 
90     private static final int USER = 0;
91 
92     @Mock private QSTileHost mQsTileHost;
93     @Mock private AutoAddTracker mAutoAddTracker;
94     @Mock private CastController mCastController;
95     @Mock private HotspotController mHotspotController;
96     @Mock private DataSaverController mDataSaverController;
97     @Mock private ManagedProfileController mManagedProfileController;
98     @Mock private NightDisplayListener mNightDisplayListener;
99     @Mock private ReduceBrightColorsController mReduceBrightColorsController;
100     @Mock private DeviceControlsController mDeviceControlsController;
101     @Mock private WalletController mWalletController;
102     @Mock(answer = Answers.RETURNS_SELF)
103     private AutoAddTracker.Builder mAutoAddTrackerBuilder;
104     @Mock private Context mUserContext;
105     private final boolean mIsReduceBrightColorsAvailable = true;
106 
107     private AutoTileManager mAutoTileManager;
108     private SecureSettings mSecureSettings;
109 
110     @Before
setUp()111     public void setUp() throws Exception {
112         MockitoAnnotations.initMocks(this);
113         mSecureSettings = new FakeSettings();
114 
115         mContext.getOrCreateTestableResources().addOverride(
116                 R.array.config_quickSettingsAutoAdd,
117                 new String[] {
118                         TEST_SETTING + SEPARATOR + TEST_SPEC,
119                         TEST_SETTING_COMPONENT + SEPARATOR + TEST_CUSTOM_SPEC
120                 }
121         );
122         mContext.getOrCreateTestableResources().addOverride(
123                 com.android.internal.R.bool.config_nightDisplayAvailable, true);
124 
125         when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker);
126         when(mQsTileHost.getUserContext()).thenReturn(mUserContext);
127         when(mUserContext.getUser()).thenReturn(UserHandle.of(USER));
128 
129         mAutoTileManager = createAutoTileManager(mContext);
130         mAutoTileManager.init();
131     }
132 
133     @After
tearDown()134     public void tearDown() {
135         mAutoTileManager.destroy();
136     }
137 
createAutoTileManager( Context context, AutoAddTracker.Builder autoAddTrackerBuilder, HotspotController hotspotController, DataSaverController dataSaverController, ManagedProfileController managedProfileController, NightDisplayListener nightDisplayListener, CastController castController, ReduceBrightColorsController reduceBrightColorsController, DeviceControlsController deviceControlsController, WalletController walletController, @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable)138     private AutoTileManager createAutoTileManager(
139             Context context,
140             AutoAddTracker.Builder autoAddTrackerBuilder,
141             HotspotController hotspotController,
142             DataSaverController dataSaverController,
143             ManagedProfileController managedProfileController,
144             NightDisplayListener nightDisplayListener,
145             CastController castController,
146             ReduceBrightColorsController reduceBrightColorsController,
147             DeviceControlsController deviceControlsController,
148             WalletController walletController,
149             @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
150         return new AutoTileManager(context, autoAddTrackerBuilder, mQsTileHost,
151                 Handler.createAsync(TestableLooper.get(this).getLooper()),
152                 mSecureSettings,
153                 hotspotController,
154                 dataSaverController,
155                 managedProfileController,
156                 nightDisplayListener,
157                 castController,
158                 reduceBrightColorsController,
159                 deviceControlsController,
160                 walletController,
161                 isReduceBrightColorsAvailable);
162     }
163 
createAutoTileManager(Context context)164     private AutoTileManager createAutoTileManager(Context context) {
165         return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController,
166                 mDataSaverController, mManagedProfileController, mNightDisplayListener,
167                 mCastController, mReduceBrightColorsController, mDeviceControlsController,
168                 mWalletController, mIsReduceBrightColorsAvailable);
169     }
170 
171     @Test
testCreatedAutoTileManagerIsNotInitialized()172     public void testCreatedAutoTileManagerIsNotInitialized() {
173         AutoAddTracker.Builder builder = mock(AutoAddTracker.Builder.class, Answers.RETURNS_SELF);
174         AutoAddTracker tracker = mock(AutoAddTracker.class);
175         when(builder.build()).thenReturn(tracker);
176         HotspotController hC = mock(HotspotController.class);
177         DataSaverController dSC = mock(DataSaverController.class);
178         ManagedProfileController mPC = mock(ManagedProfileController.class);
179         NightDisplayListener nDS = mock(NightDisplayListener.class);
180         CastController cC = mock(CastController.class);
181         ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class);
182         DeviceControlsController dCC = mock(DeviceControlsController.class);
183         WalletController wC = mock(WalletController.class);
184 
185         AutoTileManager manager =
186                 createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDS, cC, rBC,
187                         dCC, wC, true);
188 
189         verify(tracker, never()).initialize();
190         verify(hC, never()).addCallback(any());
191         verify(dSC, never()).addCallback(any());
192         verify(mPC, never()).addCallback(any());
193         verify(nDS, never()).setCallback(any());
194         verify(cC, never()).addCallback(any());
195         verify(rBC, never()).addCallback(any());
196         verify(dCC, never()).setCallback(any());
197         verify(wC, never()).getWalletPosition();
198         assertNull(manager.getSecureSettingForKey(TEST_SETTING));
199         assertNull(manager.getSecureSettingForKey(TEST_SETTING_COMPONENT));
200     }
201 
202     @Test
testChangeUserWhenNotInitializedThrows()203     public void testChangeUserWhenNotInitializedThrows() {
204         AutoTileManager manager = createAutoTileManager(mock(Context.class));
205 
206         try {
207             manager.changeUser(UserHandle.of(USER + 1));
208             fail();
209         } catch (Exception e) {
210             // This should throw and take this path
211         }
212     }
213 
214     @Test
testChangeUserCallbacksStoppedAndStarted()215     public void testChangeUserCallbacksStoppedAndStarted() throws Exception {
216         TestableLooper.get(this).runWithLooper(() ->
217                 mAutoTileManager.changeUser(UserHandle.of(USER + 1))
218         );
219 
220         InOrder inOrderHotspot = inOrder(mHotspotController);
221         inOrderHotspot.verify(mHotspotController).removeCallback(any());
222         inOrderHotspot.verify(mHotspotController).addCallback(any());
223 
224         InOrder inOrderDataSaver = inOrder(mDataSaverController);
225         inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
226         inOrderDataSaver.verify(mDataSaverController).addCallback(any());
227 
228         InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
229         inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
230         inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
231 
232         if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
233             InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
234             inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
235             inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
236         }
237 
238         InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController);
239         inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any());
240         inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any());
241 
242         InOrder inOrderCast = inOrder(mCastController);
243         inOrderCast.verify(mCastController).removeCallback(any());
244         inOrderCast.verify(mCastController).addCallback(any());
245 
246         InOrder inOrderDevices = inOrder(mDeviceControlsController);
247         inOrderDevices.verify(mDeviceControlsController).removeCallback();
248         inOrderDevices.verify(mDeviceControlsController).setCallback(any());
249 
250         verify(mWalletController, times(2)).getWalletPosition();
251 
252         SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
253         assertEquals(USER + 1, setting.getCurrentUser());
254         assertTrue(setting.isListening());
255     }
256 
257     @Test
testChangeUserSomeCallbacksNotAdded()258     public void testChangeUserSomeCallbacksNotAdded() throws Exception {
259         when(mAutoAddTracker.isAdded("hotspot")).thenReturn(true);
260         when(mAutoAddTracker.isAdded("work")).thenReturn(true);
261         when(mAutoAddTracker.isAdded("cast")).thenReturn(true);
262         when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
263 
264         TestableLooper.get(this).runWithLooper(() ->
265                 mAutoTileManager.changeUser(UserHandle.of(USER + 1))
266         );
267 
268         verify(mAutoAddTracker).changeUser(UserHandle.of(USER + 1));
269 
270         InOrder inOrderHotspot = inOrder(mHotspotController);
271         inOrderHotspot.verify(mHotspotController).removeCallback(any());
272         inOrderHotspot.verify(mHotspotController, never()).addCallback(any());
273 
274         InOrder inOrderDataSaver = inOrder(mDataSaverController);
275         inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
276         inOrderDataSaver.verify(mDataSaverController).addCallback(any());
277 
278         InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
279         inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
280         inOrderManagedProfile.verify(mManagedProfileController, never()).addCallback(any());
281 
282         if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
283             InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
284             inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
285             inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
286         }
287 
288         InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController);
289         inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any());
290         inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any());
291 
292         InOrder inOrderCast = inOrder(mCastController);
293         inOrderCast.verify(mCastController).removeCallback(any());
294         inOrderCast.verify(mCastController, never()).addCallback(any());
295 
296         InOrder inOrderDevices = inOrder(mDeviceControlsController);
297         inOrderDevices.verify(mDeviceControlsController).removeCallback();
298         inOrderDevices.verify(mDeviceControlsController).setCallback(any());
299 
300         verify(mWalletController, times(2)).getWalletPosition();
301 
302         SecureSetting setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
303         assertEquals(USER + 1, setting.getCurrentUser());
304         assertFalse(setting.isListening());
305     }
306 
307     @Test
testGetCurrentUserId()308     public void testGetCurrentUserId() throws Exception {
309         assertEquals(USER, mAutoTileManager.getCurrentUserId());
310 
311         TestableLooper.get(this).runWithLooper(() ->
312                 mAutoTileManager.changeUser(UserHandle.of(USER + 100))
313         );
314 
315         assertEquals(USER + 100, mAutoTileManager.getCurrentUserId());
316     }
317 
318     @Test
nightTileAdded_whenActivated()319     public void nightTileAdded_whenActivated() {
320         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
321             return;
322         }
323         mAutoTileManager.mNightDisplayCallback.onActivated(true);
324         verify(mQsTileHost).addTile("night");
325     }
326 
327     @Test
nightTileNotAdded_whenDeactivated()328     public void nightTileNotAdded_whenDeactivated() {
329         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
330             return;
331         }
332         mAutoTileManager.mNightDisplayCallback.onActivated(false);
333         verify(mQsTileHost, never()).addTile("night");
334     }
335 
336     @Test
nightTileAdded_whenNightModeTwilight()337     public void nightTileAdded_whenNightModeTwilight() {
338         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
339             return;
340         }
341         mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
342                 ColorDisplayManager.AUTO_MODE_TWILIGHT);
343         verify(mQsTileHost).addTile("night");
344     }
345 
346     @Test
nightTileAdded_whenNightModeCustom()347     public void nightTileAdded_whenNightModeCustom() {
348         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
349             return;
350         }
351         mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
352                 ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
353         verify(mQsTileHost).addTile("night");
354     }
355 
356     @Test
nightTileNotAdded_whenNightModeDisabled()357     public void nightTileNotAdded_whenNightModeDisabled() {
358         if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
359             return;
360         }
361         mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
362                 ColorDisplayManager.AUTO_MODE_DISABLED);
363         verify(mQsTileHost, never()).addTile("night");
364     }
365 
366     @Test
reduceBrightColorsTileAdded_whenActivated()367     public void reduceBrightColorsTileAdded_whenActivated() {
368         mAutoTileManager.mReduceBrightColorsCallback.onActivated(true);
369         verify(mQsTileHost).addTile("reduce_brightness");
370     }
371 
372     @Test
reduceBrightColorsTileNotAdded_whenDeactivated()373     public void reduceBrightColorsTileNotAdded_whenDeactivated() {
374         mAutoTileManager.mReduceBrightColorsCallback.onActivated(false);
375         verify(mQsTileHost, never()).addTile("reduce_brightness");
376     }
377 
buildFakeCastDevice(boolean isCasting)378     private static List<CastDevice> buildFakeCastDevice(boolean isCasting) {
379         CastDevice cd = new CastDevice();
380         cd.state = isCasting ? CastDevice.STATE_CONNECTED : CastDevice.STATE_DISCONNECTED;
381         return Collections.singletonList(cd);
382     }
383 
384     @Test
castTileAdded_whenDeviceIsCasting()385     public void castTileAdded_whenDeviceIsCasting() {
386         doReturn(buildFakeCastDevice(true)).when(mCastController).getCastDevices();
387         mAutoTileManager.mCastCallback.onCastDevicesChanged();
388         verify(mQsTileHost).addTile("cast");
389     }
390 
391     @Test
castTileNotAdded_whenDeviceIsNotCasting()392     public void castTileNotAdded_whenDeviceIsNotCasting() {
393         doReturn(buildFakeCastDevice(false)).when(mCastController).getCastDevices();
394         mAutoTileManager.mCastCallback.onCastDevicesChanged();
395         verify(mQsTileHost, never()).addTile("cast");
396     }
397 
398     @Test
testSettingTileAdded_onChanged()399     public void testSettingTileAdded_onChanged() {
400         changeValue(TEST_SETTING, 1);
401         verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
402         verify(mQsTileHost).addTile(TEST_SPEC);
403     }
404 
405     @Test
testSettingTileAddedComponentAtEnd_onChanged()406     public void testSettingTileAddedComponentAtEnd_onChanged() {
407         changeValue(TEST_SETTING_COMPONENT, 1);
408         verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC);
409         verify(mQsTileHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)
410             , /* end */ true);
411     }
412 
413     @Test
testSettingTileAdded_onlyOnce()414     public void testSettingTileAdded_onlyOnce() {
415         changeValue(TEST_SETTING, 1);
416         changeValue(TEST_SETTING, 2);
417         verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
418         verify(mQsTileHost).addTile(TEST_SPEC);
419     }
420 
421     @Test
testSettingTileNotAdded_onChangedTo0()422     public void testSettingTileNotAdded_onChangedTo0() {
423         changeValue(TEST_SETTING, 0);
424         verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
425         verify(mQsTileHost, never()).addTile(TEST_SPEC);
426     }
427 
428     @Test
testSettingTileNotAdded_ifPreviouslyAdded()429     public void testSettingTileNotAdded_ifPreviouslyAdded() {
430         when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
431 
432         changeValue(TEST_SETTING, 1);
433         verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
434         verify(mQsTileHost, never()).addTile(TEST_SPEC);
435     }
436 
437     @Test
testEmptyArray_doesNotCrash()438     public void testEmptyArray_doesNotCrash() {
439         mContext.getOrCreateTestableResources().addOverride(
440                 R.array.config_quickSettingsAutoAdd, new String[0]);
441         createAutoTileManager(mContext).destroy();
442     }
443 
444     @Test
testMissingConfig_doesNotCrash()445     public void testMissingConfig_doesNotCrash() {
446         mContext.getOrCreateTestableResources().addOverride(
447                 R.array.config_quickSettingsAutoAdd, null);
448         createAutoTileManager(mContext).destroy();
449     }
450 
451     // Will only notify if it's listening
changeValue(String key, int value)452     private void changeValue(String key, int value) {
453         mSecureSettings.putIntForUser(key, value, USER);
454         TestableLooper.get(this).processAllMessages();
455     }
456 }
457