1 /*
2  * Copyright (C) 20019 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.server;
18 
19 import android.app.AlarmManager;
20 import android.app.IUiModeManager;
21 import android.content.BroadcastReceiver;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.pm.PackageManager;
27 import android.content.res.Configuration;
28 import android.content.res.Resources;
29 import android.os.Handler;
30 import android.os.PowerManager;
31 import android.os.PowerManagerInternal;
32 import android.os.PowerSaveState;
33 import android.os.RemoteException;
34 import android.testing.AndroidTestingRunner;
35 import android.testing.TestableLooper;
36 
37 import com.android.server.twilight.TwilightListener;
38 import com.android.server.twilight.TwilightManager;
39 import com.android.server.twilight.TwilightState;
40 import com.android.server.wm.WindowManagerInternal;
41 import org.junit.Before;
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 import org.mockito.Mock;
45 
46 import java.time.LocalDateTime;
47 import java.time.LocalTime;
48 import java.time.ZoneId;
49 import java.util.function.Consumer;
50 
51 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
52 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
53 import static android.app.UiModeManager.MODE_NIGHT_NO;
54 import static android.app.UiModeManager.MODE_NIGHT_YES;
55 import static junit.framework.TestCase.assertFalse;
56 import static junit.framework.TestCase.assertTrue;
57 import static org.junit.Assert.assertEquals;
58 import static org.mockito.ArgumentMatchers.any;
59 import static org.mockito.ArgumentMatchers.anyInt;
60 import static org.mockito.ArgumentMatchers.anyLong;
61 import static org.mockito.ArgumentMatchers.anyString;
62 import static org.mockito.ArgumentMatchers.eq;
63 import static org.mockito.ArgumentMatchers.notNull;
64 import static org.mockito.BDDMockito.given;
65 import static org.mockito.Mockito.atLeast;
66 import static org.mockito.Mockito.atLeastOnce;
67 import static org.mockito.Mockito.doAnswer;
68 import static org.mockito.Mockito.doReturn;
69 import static org.mockito.Mockito.times;
70 import static org.mockito.Mockito.verify;
71 import static org.mockito.Mockito.when;
72 import static org.mockito.MockitoAnnotations.initMocks;
73 
74 @RunWith(AndroidTestingRunner.class)
75 @TestableLooper.RunWithLooper
76 public class UiModeManagerServiceTest extends UiServiceTestCase {
77     private UiModeManagerService mUiManagerService;
78     private IUiModeManager mService;
79     @Mock
80     private ContentResolver mContentResolver;
81     @Mock
82     private WindowManagerInternal mWindowManager;
83     @Mock
84     private Context mContext;
85     @Mock
86     private Resources mResources;
87     @Mock
88     private TwilightManager mTwilightManager;
89     @Mock
90     private PowerManager.WakeLock mWakeLock;
91     @Mock
92     private AlarmManager mAlarmManager;
93     @Mock
94     private PowerManager mPowerManager;
95     @Mock
96     private TwilightState mTwilightState;
97     @Mock
98     PowerManagerInternal mLocalPowerManager;
99 
100     private BroadcastReceiver mScreenOffCallback;
101     private BroadcastReceiver mTimeChangedCallback;
102     private AlarmManager.OnAlarmListener mCustomListener;
103     private Consumer<PowerSaveState> mPowerSaveConsumer;
104     private TwilightListener mTwilightListener;
105 
106     @Before
setUp()107     public void setUp() {
108         initMocks(this);
109         when(mContext.checkCallingOrSelfPermission(anyString()))
110                 .thenReturn(PackageManager.PERMISSION_GRANTED);
111         doAnswer(inv -> {
112             mTwilightListener = (TwilightListener) inv.getArgument(0);
113             return null;
114         }).when(mTwilightManager).registerListener(any(), any());
115         doAnswer(inv -> {
116             mPowerSaveConsumer = (Consumer<PowerSaveState>) inv.getArgument(1);
117             return null;
118         }).when(mLocalPowerManager).registerLowPowerModeObserver(anyInt(), any());
119         when(mLocalPowerManager.getLowPowerState(anyInt()))
120                 .thenReturn(new PowerSaveState.Builder().setBatterySaverEnabled(false).build());
121         when(mContext.getResources()).thenReturn(mResources);
122         when(mContext.getContentResolver()).thenReturn(mContentResolver);
123         when(mPowerManager.isInteractive()).thenReturn(true);
124         when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock);
125         when(mTwilightManager.getLastTwilightState()).thenReturn(mTwilightState);
126         when(mTwilightState.isNight()).thenReturn(true);
127         when(mContext.registerReceiver(notNull(), notNull())).then(inv -> {
128             IntentFilter filter = inv.getArgument(1);
129             if (filter.hasAction(Intent.ACTION_TIMEZONE_CHANGED)) {
130                 mTimeChangedCallback = inv.getArgument(0);
131             }
132             if (filter.hasAction(Intent.ACTION_SCREEN_OFF)) {
133                 mScreenOffCallback = inv.getArgument(0);
134             }
135             return null;
136         });
137         doAnswer(inv -> {
138             mCustomListener = inv.getArgument(3);
139             return null;
140         }).when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(),
141                 any(AlarmManager.OnAlarmListener.class), any(Handler.class));
142 
143         doAnswer(inv -> {
144             mCustomListener = () -> {};
145             return null;
146         }).when(mAlarmManager).cancel(eq(mCustomListener));
147         when(mContext.getSystemService(eq(Context.POWER_SERVICE)))
148                 .thenReturn(mPowerManager);
149         when(mContext.getSystemService(eq(Context.ALARM_SERVICE)))
150                 .thenReturn(mAlarmManager);
151         addLocalService(WindowManagerInternal.class, mWindowManager);
152         addLocalService(PowerManagerInternal.class, mLocalPowerManager);
153         addLocalService(TwilightManager.class, mTwilightManager);
154 
155         mUiManagerService = new UiModeManagerService(mContext, true,
156                 mTwilightManager);
157         try {
158             mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
159         } catch (SecurityException e) {/* ignore for permission denial */}
160         mService = mUiManagerService.getService();
161     }
162 
addLocalService(Class<T> clazz, T service)163     private <T> void addLocalService(Class<T> clazz, T service) {
164         LocalServices.removeServiceForTest(clazz);
165         LocalServices.addService(clazz, service);
166     }
167 
168     @Test
setNightMoveActivated_overridesFunctionCorrectly()169     public void setNightMoveActivated_overridesFunctionCorrectly() throws RemoteException {
170         // set up
171         when(mPowerManager.isInteractive()).thenReturn(false);
172         mService.setNightMode(MODE_NIGHT_NO);
173         assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
174 
175         // assume it is day time
176         doReturn(false).when(mTwilightState).isNight();
177 
178         // set mode to auto
179         mService.setNightMode(MODE_NIGHT_AUTO);
180 
181         // set night mode on overriding current config
182         mService.setNightModeActivated(true);
183 
184         assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
185 
186         // now it is night time
187         doReturn(true).when(mTwilightState).isNight();
188         mTwilightListener.onTwilightStateChanged(mTwilightState);
189 
190         assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
191 
192         // now it is next day mid day
193         doReturn(false).when(mTwilightState).isNight();
194         mTwilightListener.onTwilightStateChanged(mTwilightState);
195 
196         assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
197     }
198 
199     @Test
setAutoMode_screenOffRegistered()200     public void setAutoMode_screenOffRegistered() throws RemoteException {
201         try {
202             mService.setNightMode(MODE_NIGHT_NO);
203         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
204         mService.setNightMode(MODE_NIGHT_AUTO);
205         verify(mContext, atLeastOnce()).registerReceiver(any(BroadcastReceiver.class), any());
206     }
207 
208     @Test
setAutoMode_screenOffUnRegistered()209     public void setAutoMode_screenOffUnRegistered() throws RemoteException {
210         try {
211             mService.setNightMode(MODE_NIGHT_AUTO);
212         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
213         try {
214             mService.setNightMode(MODE_NIGHT_NO);
215         } catch (SecurityException e) { /*we should ignore this update config exception*/ }
216         given(mContext.registerReceiver(any(), any())).willThrow(SecurityException.class);
217         verify(mContext, atLeastOnce()).unregisterReceiver(any(BroadcastReceiver.class));
218     }
219 
220     @Test
setNightModeActivated_fromNoToYesAndBAck()221     public void setNightModeActivated_fromNoToYesAndBAck() throws RemoteException {
222         mService.setNightMode(MODE_NIGHT_NO);
223         mService.setNightModeActivated(true);
224         assertTrue(isNightModeActivated());
225         mService.setNightModeActivated(false);
226         assertFalse(isNightModeActivated());
227     }
228 
229     @Test
autoNightModeSwitch_batterySaverOn()230     public void autoNightModeSwitch_batterySaverOn() throws RemoteException {
231         mService.setNightMode(MODE_NIGHT_NO);
232         when(mTwilightState.isNight()).thenReturn(false);
233         mService.setNightMode(MODE_NIGHT_AUTO);
234 
235         // night NO
236         assertFalse(isNightModeActivated());
237 
238         mPowerSaveConsumer.accept(
239                 new PowerSaveState.Builder().setBatterySaverEnabled(true).build());
240 
241         // night YES
242         assertTrue(isNightModeActivated());
243     }
244 
245     @Test
setAutoMode_clearCache()246     public void setAutoMode_clearCache() throws RemoteException {
247         try {
248             mService.setNightMode(MODE_NIGHT_AUTO);
249         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
250         try {
251             mService.setNightMode(MODE_NIGHT_NO);
252         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
253         verify(mWindowManager).clearSnapshotCache();
254     }
255 
256     @Test
setNightModeActive_fromNightModeYesToNoWhenFalse()257     public void setNightModeActive_fromNightModeYesToNoWhenFalse() throws RemoteException {
258         try {
259             mService.setNightMode(MODE_NIGHT_YES);
260         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
261         try {
262             mService.setNightModeActivated(false);
263         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
264         assertEquals(MODE_NIGHT_NO, mService.getNightMode());
265     }
266 
267     @Test
setNightModeActive_fromNightModeNoToYesWhenTrue()268     public void setNightModeActive_fromNightModeNoToYesWhenTrue() throws RemoteException {
269         try {
270             mService.setNightMode(MODE_NIGHT_NO);
271         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
272         try {
273             mService.setNightModeActivated(true);
274         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
275         assertEquals(MODE_NIGHT_YES, mService.getNightMode());
276     }
277 
278     @Test
setNightModeActive_autoNightModeNoChanges()279     public void setNightModeActive_autoNightModeNoChanges() throws RemoteException {
280         try {
281             mService.setNightMode(MODE_NIGHT_AUTO);
282         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
283         try {
284             mService.setNightModeActivated(true);
285         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
286         assertEquals(MODE_NIGHT_AUTO, mService.getNightMode());
287     }
288 
289     @Test
isNightModeActive_nightModeYes()290     public void isNightModeActive_nightModeYes() throws RemoteException {
291         try {
292             mService.setNightMode(MODE_NIGHT_YES);
293         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
294         assertTrue(isNightModeActivated());
295     }
296 
297     @Test
isNightModeActive_nightModeNo()298     public void isNightModeActive_nightModeNo() throws RemoteException {
299         try {
300             mService.setNightMode(MODE_NIGHT_NO);
301         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
302         assertFalse(isNightModeActivated());
303     }
304 
305     @Test
customTime_darkThemeOn()306     public void customTime_darkThemeOn() throws RemoteException {
307         LocalTime now = LocalTime.now();
308         mService.setNightMode(MODE_NIGHT_NO);
309         mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
310         mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
311         mService.setNightMode(MODE_NIGHT_CUSTOM);
312         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
313         assertTrue(isNightModeActivated());
314     }
315 
316     @Test
customTime_darkThemeOff()317     public void customTime_darkThemeOff() throws RemoteException {
318         LocalTime now = LocalTime.now();
319         mService.setNightMode(MODE_NIGHT_YES);
320         mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
321         mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000);
322         mService.setNightMode(MODE_NIGHT_CUSTOM);
323         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
324         assertFalse(isNightModeActivated());
325     }
326 
327     @Test
customTime_darkThemeOff_afterStartEnd()328     public void customTime_darkThemeOff_afterStartEnd() throws RemoteException {
329         LocalTime now = LocalTime.now();
330         mService.setNightMode(MODE_NIGHT_YES);
331         mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
332         mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000);
333         mService.setNightMode(MODE_NIGHT_CUSTOM);
334         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
335         assertFalse(isNightModeActivated());
336     }
337 
338     @Test
customTime_darkThemeOn_afterStartEnd()339     public void customTime_darkThemeOn_afterStartEnd() throws RemoteException {
340         LocalTime now = LocalTime.now();
341         mService.setNightMode(MODE_NIGHT_YES);
342         mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
343         mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000);
344         mService.setNightMode(MODE_NIGHT_CUSTOM);
345         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
346         assertFalse(isNightModeActivated());
347     }
348 
349 
350 
351     @Test
customTime_darkThemeOn_beforeStartEnd()352     public void customTime_darkThemeOn_beforeStartEnd() throws RemoteException {
353         LocalTime now = LocalTime.now();
354         mService.setNightMode(MODE_NIGHT_YES);
355         mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
356         mService.setCustomNightModeEnd(now.minusHours(2L).toNanoOfDay() / 1000);
357         mService.setNightMode(MODE_NIGHT_CUSTOM);
358         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
359         assertTrue(isNightModeActivated());
360     }
361 
362     @Test
customTime_darkThemeOff_beforeStartEnd()363     public void customTime_darkThemeOff_beforeStartEnd() throws RemoteException {
364         LocalTime now = LocalTime.now();
365         mService.setNightMode(MODE_NIGHT_YES);
366         mService.setCustomNightModeStart(now.minusHours(2L).toNanoOfDay() / 1000);
367         mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000);
368         mService.setNightMode(MODE_NIGHT_CUSTOM);
369         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
370         assertFalse(isNightModeActivated());
371     }
372 
373     @Test
customTIme_customAlarmSetWhenScreenTimeChanges()374     public void customTIme_customAlarmSetWhenScreenTimeChanges() throws RemoteException {
375         when(mPowerManager.isInteractive()).thenReturn(false);
376         mService.setNightMode(MODE_NIGHT_CUSTOM);
377         verify(mAlarmManager, times(1))
378                 .setExact(anyInt(), anyLong(), anyString(), any(), any());
379         mTimeChangedCallback.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
380         verify(mAlarmManager, atLeast(2))
381                 .setExact(anyInt(), anyLong(), anyString(), any(), any());
382     }
383 
384     @Test
customTime_alarmSetInTheFutureWhenOn()385     public void customTime_alarmSetInTheFutureWhenOn() throws RemoteException {
386         LocalDateTime now = LocalDateTime.now();
387         when(mPowerManager.isInteractive()).thenReturn(false);
388         mService.setNightMode(MODE_NIGHT_YES);
389         mService.setCustomNightModeStart(now.toLocalTime().minusHours(1L).toNanoOfDay() / 1000);
390         mService.setCustomNightModeEnd(now.toLocalTime().plusHours(1L).toNanoOfDay() / 1000);
391         LocalDateTime next = now.plusHours(1L);
392         final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
393         mService.setNightMode(MODE_NIGHT_CUSTOM);
394         verify(mAlarmManager)
395                 .setExact(anyInt(), eq(millis), anyString(), any(), any());
396     }
397 
398     @Test
customTime_appliesImmediatelyWhenScreenOff()399     public void customTime_appliesImmediatelyWhenScreenOff() throws RemoteException {
400         when(mPowerManager.isInteractive()).thenReturn(false);
401         LocalTime now = LocalTime.now();
402         mService.setNightMode(MODE_NIGHT_NO);
403         mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
404         mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
405         mService.setNightMode(MODE_NIGHT_CUSTOM);
406         assertTrue(isNightModeActivated());
407     }
408 
409     @Test
customTime_appliesOnlyWhenScreenOff()410     public void customTime_appliesOnlyWhenScreenOff() throws RemoteException {
411         LocalTime now = LocalTime.now();
412         mService.setNightMode(MODE_NIGHT_NO);
413         mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
414         mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
415         mService.setNightMode(MODE_NIGHT_CUSTOM);
416         assertFalse(isNightModeActivated());
417         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
418         assertTrue(isNightModeActivated());
419     }
420 
421     @Test
nightAuto_appliesOnlyWhenScreenOff()422     public void nightAuto_appliesOnlyWhenScreenOff() throws RemoteException {
423         when(mTwilightState.isNight()).thenReturn(true);
424         mService.setNightMode(MODE_NIGHT_NO);
425         mService.setNightMode(MODE_NIGHT_AUTO);
426         assertFalse(isNightModeActivated());
427         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
428         assertTrue(isNightModeActivated());
429     }
430 
isNightModeActivated()431     private boolean isNightModeActivated() {
432         return (mUiManagerService.getConfiguration().uiMode
433                 & Configuration.UI_MODE_NIGHT_YES) != 0;
434     }
435 }
436