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 static android.app.UiModeManager.MODE_NIGHT_AUTO;
20 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
21 import static android.app.UiModeManager.MODE_NIGHT_NO;
22 import static android.app.UiModeManager.MODE_NIGHT_YES;
23 import static android.app.UiModeManager.PROJECTION_TYPE_ALL;
24 import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
25 import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
26 
27 import static junit.framework.TestCase.assertFalse;
28 import static junit.framework.TestCase.assertTrue;
29 
30 import static org.hamcrest.Matchers.contains;
31 import static org.hamcrest.Matchers.empty;
32 import static org.junit.Assert.assertEquals;
33 import static org.junit.Assert.assertThat;
34 import static org.mockito.ArgumentMatchers.any;
35 import static org.mockito.ArgumentMatchers.anyInt;
36 import static org.mockito.ArgumentMatchers.anyLong;
37 import static org.mockito.ArgumentMatchers.anyString;
38 import static org.mockito.ArgumentMatchers.eq;
39 import static org.mockito.ArgumentMatchers.notNull;
40 import static org.mockito.BDDMockito.given;
41 import static org.mockito.Mockito.atLeast;
42 import static org.mockito.Mockito.atLeastOnce;
43 import static org.mockito.Mockito.doAnswer;
44 import static org.mockito.Mockito.doReturn;
45 import static org.mockito.Mockito.doThrow;
46 import static org.mockito.Mockito.mock;
47 import static org.mockito.Mockito.never;
48 import static org.mockito.Mockito.times;
49 import static org.mockito.Mockito.verify;
50 import static org.mockito.Mockito.verifyNoMoreInteractions;
51 import static org.mockito.Mockito.verifyZeroInteractions;
52 import static org.mockito.Mockito.when;
53 import static org.mockito.MockitoAnnotations.initMocks;
54 import static org.testng.Assert.assertThrows;
55 
56 import android.Manifest;
57 import android.app.AlarmManager;
58 import android.app.IOnProjectionStateChangedListener;
59 import android.app.IUiModeManager;
60 import android.content.BroadcastReceiver;
61 import android.content.ContentResolver;
62 import android.content.Context;
63 import android.content.Intent;
64 import android.content.IntentFilter;
65 import android.content.pm.PackageManager;
66 import android.content.res.Configuration;
67 import android.content.res.Resources;
68 import android.os.Handler;
69 import android.os.IBinder;
70 import android.os.PowerManager;
71 import android.os.PowerManagerInternal;
72 import android.os.PowerSaveState;
73 import android.os.RemoteException;
74 import android.testing.AndroidTestingRunner;
75 import android.testing.TestableLooper;
76 
77 import com.android.server.twilight.TwilightListener;
78 import com.android.server.twilight.TwilightManager;
79 import com.android.server.twilight.TwilightState;
80 import com.android.server.wm.WindowManagerInternal;
81 
82 import org.junit.Before;
83 import org.junit.Ignore;
84 import org.junit.Test;
85 import org.junit.runner.RunWith;
86 import org.mockito.ArgumentCaptor;
87 import org.mockito.Mock;
88 
89 import java.time.LocalDateTime;
90 import java.time.LocalTime;
91 import java.time.ZoneId;
92 import java.util.List;
93 import java.util.function.Consumer;
94 
95 @RunWith(AndroidTestingRunner.class)
96 @TestableLooper.RunWithLooper
97 public class UiModeManagerServiceTest extends UiServiceTestCase {
98     private static final String PACKAGE_NAME = "Diane Coffee";
99     private UiModeManagerService mUiManagerService;
100     private IUiModeManager mService;
101     @Mock
102     private ContentResolver mContentResolver;
103     @Mock
104     private WindowManagerInternal mWindowManager;
105     @Mock
106     private Context mContext;
107     @Mock
108     private Resources mResources;
109     @Mock
110     private TwilightManager mTwilightManager;
111     @Mock
112     private PowerManager.WakeLock mWakeLock;
113     @Mock
114     private AlarmManager mAlarmManager;
115     @Mock
116     private PowerManager mPowerManager;
117     @Mock
118     private TwilightState mTwilightState;
119     @Mock
120     PowerManagerInternal mLocalPowerManager;
121     @Mock
122     private PackageManager mPackageManager;
123     @Mock
124     private IBinder mBinder;
125 
126     private BroadcastReceiver mScreenOffCallback;
127     private BroadcastReceiver mTimeChangedCallback;
128     private AlarmManager.OnAlarmListener mCustomListener;
129     private Consumer<PowerSaveState> mPowerSaveConsumer;
130     private TwilightListener mTwilightListener;
131 
132     @Before
setUp()133     public void setUp() {
134         initMocks(this);
135         when(mContext.checkCallingOrSelfPermission(anyString()))
136                 .thenReturn(PackageManager.PERMISSION_GRANTED);
137         doAnswer(inv -> {
138             mTwilightListener = (TwilightListener) inv.getArgument(0);
139             return null;
140         }).when(mTwilightManager).registerListener(any(), any());
141         doAnswer(inv -> {
142             mPowerSaveConsumer = (Consumer<PowerSaveState>) inv.getArgument(1);
143             return null;
144         }).when(mLocalPowerManager).registerLowPowerModeObserver(anyInt(), any());
145         when(mLocalPowerManager.getLowPowerState(anyInt()))
146                 .thenReturn(new PowerSaveState.Builder().setBatterySaverEnabled(false).build());
147         when(mContext.getResources()).thenReturn(mResources);
148         when(mContext.getContentResolver()).thenReturn(mContentResolver);
149         when(mContext.getPackageManager()).thenReturn(mPackageManager);
150         when(mPowerManager.isInteractive()).thenReturn(true);
151         when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(mWakeLock);
152         when(mTwilightManager.getLastTwilightState()).thenReturn(mTwilightState);
153         when(mTwilightState.isNight()).thenReturn(true);
154         when(mContext.registerReceiver(notNull(), notNull())).then(inv -> {
155             IntentFilter filter = inv.getArgument(1);
156             if (filter.hasAction(Intent.ACTION_TIMEZONE_CHANGED)) {
157                 mTimeChangedCallback = inv.getArgument(0);
158             }
159             if (filter.hasAction(Intent.ACTION_SCREEN_OFF)) {
160                 mScreenOffCallback = inv.getArgument(0);
161             }
162             return null;
163         });
164         doAnswer(inv -> {
165             mCustomListener = inv.getArgument(3);
166             return null;
167         }).when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(),
168                 any(AlarmManager.OnAlarmListener.class), any(Handler.class));
169 
170         doAnswer(inv -> {
171             mCustomListener = () -> {};
172             return null;
173         }).when(mAlarmManager).cancel(eq(mCustomListener));
174         when(mContext.getSystemService(eq(Context.POWER_SERVICE)))
175                 .thenReturn(mPowerManager);
176         when(mContext.getSystemService(eq(Context.ALARM_SERVICE)))
177                 .thenReturn(mAlarmManager);
178         addLocalService(WindowManagerInternal.class, mWindowManager);
179         addLocalService(PowerManagerInternal.class, mLocalPowerManager);
180         addLocalService(TwilightManager.class, mTwilightManager);
181 
182         mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true,
183                 mTwilightManager, new TestInjector());
184         try {
185             mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
186         } catch (SecurityException e) {/* ignore for permission denial */}
187         mService = mUiManagerService.getService();
188     }
189 
addLocalService(Class<T> clazz, T service)190     private <T> void addLocalService(Class<T> clazz, T service) {
191         LocalServices.removeServiceForTest(clazz);
192         LocalServices.addService(clazz, service);
193     }
194 
195     @Ignore // b/152719290 - Fails on stage-aosp-master
196     @Test
setNightMoveActivated_overridesFunctionCorrectly()197     public void setNightMoveActivated_overridesFunctionCorrectly() throws RemoteException {
198         // set up
199         when(mPowerManager.isInteractive()).thenReturn(false);
200         mService.setNightMode(MODE_NIGHT_NO);
201         assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
202 
203         // assume it is day time
204         doReturn(false).when(mTwilightState).isNight();
205 
206         // set mode to auto
207         mService.setNightMode(MODE_NIGHT_AUTO);
208 
209         // set night mode on overriding current config
210         mService.setNightModeActivated(true);
211 
212         assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
213 
214         // now it is night time
215         doReturn(true).when(mTwilightState).isNight();
216         mTwilightListener.onTwilightStateChanged(mTwilightState);
217 
218         assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
219 
220         // now it is next day mid day
221         doReturn(false).when(mTwilightState).isNight();
222         mTwilightListener.onTwilightStateChanged(mTwilightState);
223 
224         assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
225     }
226 
227     @Test
setAutoMode_screenOffRegistered()228     public void setAutoMode_screenOffRegistered() throws RemoteException {
229         try {
230             mService.setNightMode(MODE_NIGHT_NO);
231         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
232         mService.setNightMode(MODE_NIGHT_AUTO);
233         verify(mContext, atLeastOnce()).registerReceiver(any(BroadcastReceiver.class), any());
234     }
235 
236     @Ignore // b/152719290 - Fails on stage-aosp-master
237     @Test
setAutoMode_screenOffUnRegistered()238     public void setAutoMode_screenOffUnRegistered() throws RemoteException {
239         try {
240             mService.setNightMode(MODE_NIGHT_AUTO);
241         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
242         try {
243             mService.setNightMode(MODE_NIGHT_NO);
244         } catch (SecurityException e) { /*we should ignore this update config exception*/ }
245         given(mContext.registerReceiver(any(), any())).willThrow(SecurityException.class);
246         verify(mContext, atLeastOnce()).unregisterReceiver(any(BroadcastReceiver.class));
247     }
248 
249     @Test
setNightModeActivated_fromNoToYesAndBAck()250     public void setNightModeActivated_fromNoToYesAndBAck() throws RemoteException {
251         mService.setNightMode(MODE_NIGHT_NO);
252         mService.setNightModeActivated(true);
253         assertTrue(isNightModeActivated());
254         mService.setNightModeActivated(false);
255         assertFalse(isNightModeActivated());
256     }
257 
258     @Test
setNightModeActivated_permissiontoChangeOtherUsers()259     public void setNightModeActivated_permissiontoChangeOtherUsers() throws RemoteException {
260         SystemService.TargetUser user = mock(SystemService.TargetUser.class);
261         doReturn(9).when(user).getUserIdentifier();
262         mUiManagerService.onUserSwitching(user, user);
263         when(mContext.checkCallingOrSelfPermission(
264                 eq(Manifest.permission.INTERACT_ACROSS_USERS)))
265                 .thenReturn(PackageManager.PERMISSION_DENIED);
266         assertFalse(mService.setNightModeActivated(true));
267     }
268 
269     @Test
autoNightModeSwitch_batterySaverOn()270     public void autoNightModeSwitch_batterySaverOn() throws RemoteException {
271         mService.setNightMode(MODE_NIGHT_NO);
272         when(mTwilightState.isNight()).thenReturn(false);
273         mService.setNightMode(MODE_NIGHT_AUTO);
274 
275         // night NO
276         assertFalse(isNightModeActivated());
277 
278         mPowerSaveConsumer.accept(
279                 new PowerSaveState.Builder().setBatterySaverEnabled(true).build());
280 
281         // night YES
282         assertTrue(isNightModeActivated());
283     }
284 
285     @Test
setAutoMode_clearCache()286     public void setAutoMode_clearCache() throws RemoteException {
287         try {
288             mService.setNightMode(MODE_NIGHT_AUTO);
289         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
290         try {
291             mService.setNightMode(MODE_NIGHT_NO);
292         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
293         verify(mWindowManager).clearSnapshotCache();
294     }
295 
296     @Test
setNightModeActive_fromNightModeYesToNoWhenFalse()297     public void setNightModeActive_fromNightModeYesToNoWhenFalse() throws RemoteException {
298         try {
299             mService.setNightMode(MODE_NIGHT_YES);
300         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
301         try {
302             mService.setNightModeActivated(false);
303         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
304         assertEquals(MODE_NIGHT_NO, mService.getNightMode());
305     }
306 
307     @Test
setNightModeActive_fromNightModeNoToYesWhenTrue()308     public void setNightModeActive_fromNightModeNoToYesWhenTrue() throws RemoteException {
309         try {
310             mService.setNightMode(MODE_NIGHT_NO);
311         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
312         try {
313             mService.setNightModeActivated(true);
314         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
315         assertEquals(MODE_NIGHT_YES, mService.getNightMode());
316     }
317 
318     @Test
setNightModeActive_autoNightModeNoChanges()319     public void setNightModeActive_autoNightModeNoChanges() throws RemoteException {
320         try {
321             mService.setNightMode(MODE_NIGHT_AUTO);
322         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
323         try {
324             mService.setNightModeActivated(true);
325         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
326         assertEquals(MODE_NIGHT_AUTO, mService.getNightMode());
327     }
328 
329     @Test
isNightModeActive_nightModeYes()330     public void isNightModeActive_nightModeYes() throws RemoteException {
331         try {
332             mService.setNightMode(MODE_NIGHT_YES);
333         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
334         assertTrue(isNightModeActivated());
335     }
336 
337     @Test
isNightModeActive_nightModeNo()338     public void isNightModeActive_nightModeNo() throws RemoteException {
339         try {
340             mService.setNightMode(MODE_NIGHT_NO);
341         } catch (SecurityException e) { /* we should ignore this update config exception*/ }
342         assertFalse(isNightModeActivated());
343     }
344 
345     @Test
customTime_darkThemeOn()346     public void customTime_darkThemeOn() throws RemoteException {
347         LocalTime now = LocalTime.now();
348         mService.setNightMode(MODE_NIGHT_NO);
349         mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
350         mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
351         mService.setNightMode(MODE_NIGHT_CUSTOM);
352         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
353         assertTrue(isNightModeActivated());
354     }
355 
356     @Test
customTime_darkThemeOff()357     public void customTime_darkThemeOff() throws RemoteException {
358         LocalTime now = LocalTime.now();
359         mService.setNightMode(MODE_NIGHT_YES);
360         mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
361         mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000);
362         mService.setNightMode(MODE_NIGHT_CUSTOM);
363         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
364         assertFalse(isNightModeActivated());
365     }
366 
367     @Test
customTime_darkThemeOff_afterStartEnd()368     public void customTime_darkThemeOff_afterStartEnd() throws RemoteException {
369         LocalTime now = LocalTime.now();
370         mService.setNightMode(MODE_NIGHT_YES);
371         mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
372         mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000);
373         mService.setNightMode(MODE_NIGHT_CUSTOM);
374         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
375         assertFalse(isNightModeActivated());
376     }
377 
378     @Test
customTime_darkThemeOn_afterStartEnd()379     public void customTime_darkThemeOn_afterStartEnd() throws RemoteException {
380         LocalTime now = LocalTime.now();
381         mService.setNightMode(MODE_NIGHT_YES);
382         mService.setCustomNightModeStart(now.plusHours(1L).toNanoOfDay() / 1000);
383         mService.setCustomNightModeEnd(now.plusHours(2L).toNanoOfDay() / 1000);
384         mService.setNightMode(MODE_NIGHT_CUSTOM);
385         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
386         assertFalse(isNightModeActivated());
387     }
388 
389 
390 
391     @Test
customTime_darkThemeOn_beforeStartEnd()392     public void customTime_darkThemeOn_beforeStartEnd() throws RemoteException {
393         LocalTime now = LocalTime.now();
394         mService.setNightMode(MODE_NIGHT_YES);
395         mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
396         mService.setCustomNightModeEnd(now.minusHours(2L).toNanoOfDay() / 1000);
397         mService.setNightMode(MODE_NIGHT_CUSTOM);
398         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
399         assertTrue(isNightModeActivated());
400     }
401 
402     @Test
customTime_darkThemeOff_beforeStartEnd()403     public void customTime_darkThemeOff_beforeStartEnd() throws RemoteException {
404         LocalTime now = LocalTime.now();
405         mService.setNightMode(MODE_NIGHT_YES);
406         mService.setCustomNightModeStart(now.minusHours(2L).toNanoOfDay() / 1000);
407         mService.setCustomNightModeEnd(now.minusHours(1L).toNanoOfDay() / 1000);
408         mService.setNightMode(MODE_NIGHT_CUSTOM);
409         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
410         assertFalse(isNightModeActivated());
411     }
412 
413     @Test
customTIme_customAlarmSetWhenScreenTimeChanges()414     public void customTIme_customAlarmSetWhenScreenTimeChanges() throws RemoteException {
415         when(mPowerManager.isInteractive()).thenReturn(false);
416         mService.setNightMode(MODE_NIGHT_CUSTOM);
417         verify(mAlarmManager, times(1))
418                 .setExact(anyInt(), anyLong(), anyString(), any(), any());
419         mTimeChangedCallback.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
420         verify(mAlarmManager, atLeast(2))
421                 .setExact(anyInt(), anyLong(), anyString(), any(), any());
422     }
423 
424     @Test
customTime_alarmSetInTheFutureWhenOn()425     public void customTime_alarmSetInTheFutureWhenOn() throws RemoteException {
426         LocalDateTime now = LocalDateTime.now();
427         when(mPowerManager.isInteractive()).thenReturn(false);
428         mService.setNightMode(MODE_NIGHT_YES);
429         mService.setCustomNightModeStart(now.toLocalTime().minusHours(1L).toNanoOfDay() / 1000);
430         mService.setCustomNightModeEnd(now.toLocalTime().plusHours(1L).toNanoOfDay() / 1000);
431         LocalDateTime next = now.plusHours(1L);
432         final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
433         mService.setNightMode(MODE_NIGHT_CUSTOM);
434         verify(mAlarmManager)
435                 .setExact(anyInt(), eq(millis), anyString(), any(), any());
436     }
437 
438     @Test
customTime_appliesImmediatelyWhenScreenOff()439     public void customTime_appliesImmediatelyWhenScreenOff() throws RemoteException {
440         when(mPowerManager.isInteractive()).thenReturn(false);
441         LocalTime now = LocalTime.now();
442         mService.setNightMode(MODE_NIGHT_NO);
443         mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
444         mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
445         mService.setNightMode(MODE_NIGHT_CUSTOM);
446         assertTrue(isNightModeActivated());
447     }
448 
449     @Test
customTime_appliesOnlyWhenScreenOff()450     public void customTime_appliesOnlyWhenScreenOff() throws RemoteException {
451         LocalTime now = LocalTime.now();
452         mService.setNightMode(MODE_NIGHT_NO);
453         mService.setCustomNightModeStart(now.minusHours(1L).toNanoOfDay() / 1000);
454         mService.setCustomNightModeEnd(now.plusHours(1L).toNanoOfDay() / 1000);
455         mService.setNightMode(MODE_NIGHT_CUSTOM);
456         assertFalse(isNightModeActivated());
457         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
458         assertTrue(isNightModeActivated());
459     }
460 
461     @Test
nightAuto_appliesOnlyWhenScreenOff()462     public void nightAuto_appliesOnlyWhenScreenOff() throws RemoteException {
463         when(mTwilightState.isNight()).thenReturn(true);
464         mService.setNightMode(MODE_NIGHT_NO);
465         mService.setNightMode(MODE_NIGHT_AUTO);
466         assertFalse(isNightModeActivated());
467         mScreenOffCallback.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
468         assertTrue(isNightModeActivated());
469     }
470 
isNightModeActivated()471     private boolean isNightModeActivated() {
472         return (mUiManagerService.getConfiguration().uiMode
473                 & Configuration.UI_MODE_NIGHT_YES) != 0;
474     }
475 
476     @Test
requestProjection_failsForBogusPackageName()477     public void requestProjection_failsForBogusPackageName() throws Exception {
478         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
479                 .thenReturn(TestInjector.CALLING_UID + 1);
480 
481         assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
482                 PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
483         assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
484     }
485 
486     @Test
requestProjection_failsIfNameNotFound()487     public void requestProjection_failsIfNameNotFound() throws Exception {
488         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
489                 .thenThrow(new PackageManager.NameNotFoundException());
490 
491         assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
492                 PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
493         assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
494     }
495 
496     @Test
requestProjection_failsIfNoProjectionTypes()497     public void requestProjection_failsIfNoProjectionTypes() throws Exception {
498         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
499                 .thenReturn(TestInjector.CALLING_UID);
500 
501         assertThrows(IllegalArgumentException.class,
502                 () -> mService.requestProjection(mBinder, PROJECTION_TYPE_NONE, PACKAGE_NAME));
503         verify(mContext, never()).enforceCallingPermission(
504                 eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any());
505         verifyZeroInteractions(mBinder);
506         assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
507     }
508 
509     @Test
requestProjection_failsIfMultipleProjectionTypes()510     public void requestProjection_failsIfMultipleProjectionTypes() throws Exception {
511         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
512                 .thenReturn(TestInjector.CALLING_UID);
513 
514         // Don't use PROJECTION_TYPE_ALL because that's actually == -1 and will fail the > 0 check.
515         int multipleProjectionTypes = PROJECTION_TYPE_AUTOMOTIVE | 0x0002 | 0x0004;
516 
517         assertThrows(IllegalArgumentException.class,
518                 () -> mService.requestProjection(mBinder, multipleProjectionTypes, PACKAGE_NAME));
519         verify(mContext, never()).enforceCallingPermission(
520                 eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any());
521         verifyZeroInteractions(mBinder);
522         assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
523     }
524 
525     @Test
requestProjection_enforcesToggleAutomotiveProjectionPermission()526     public void requestProjection_enforcesToggleAutomotiveProjectionPermission() throws Exception {
527         doThrow(new SecurityException())
528                 .when(mPackageManager).getPackageUidAsUser(eq(PACKAGE_NAME), anyInt());
529 
530         assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
531                 PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
532         assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
533     }
534 
535     @Test
requestProjection_automotive_failsIfAlreadySetByOtherPackage()536     public void requestProjection_automotive_failsIfAlreadySetByOtherPackage() throws Exception {
537         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
538                 .thenReturn(TestInjector.CALLING_UID);
539         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
540         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
541 
542         String otherPackage = "Raconteurs";
543         when(mPackageManager.getPackageUidAsUser(eq(otherPackage), anyInt()))
544                 .thenReturn(TestInjector.CALLING_UID);
545         assertFalse(mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, otherPackage));
546         assertThat(mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE),
547                 contains(PACKAGE_NAME));
548     }
549 
550     @Test
requestProjection_failsIfCannotLinkToDeath()551     public void requestProjection_failsIfCannotLinkToDeath() throws Exception {
552         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
553                 .thenReturn(TestInjector.CALLING_UID);
554         doThrow(new RemoteException()).when(mBinder).linkToDeath(any(), anyInt());
555 
556         assertFalse(mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
557         assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
558     }
559 
560     @Test
requestProjection()561     public void requestProjection() throws Exception {
562         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
563                 .thenReturn(TestInjector.CALLING_UID);
564         // Should work for all powers of two.
565         for (int i = 0; i < Integer.SIZE; ++i) {
566             int projectionType = 1 << i;
567             assertTrue(mService.requestProjection(mBinder, projectionType, PACKAGE_NAME));
568             assertTrue((mService.getActiveProjectionTypes() & projectionType) != 0);
569             assertThat(mService.getProjectingPackages(projectionType), contains(PACKAGE_NAME));
570             // Subsequent calls should still succeed.
571             assertTrue(mService.requestProjection(mBinder, projectionType, PACKAGE_NAME));
572         }
573         assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
574     }
575 
576     @Test
releaseProjection_failsForBogusPackageName()577     public void releaseProjection_failsForBogusPackageName() throws Exception {
578         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
579                 .thenReturn(TestInjector.CALLING_UID);
580         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
581         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
582 
583         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
584                 .thenReturn(TestInjector.CALLING_UID + 1);
585 
586         assertThrows(SecurityException.class, () -> mService.releaseProjection(
587                 PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
588         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
589     }
590 
591     @Test
releaseProjection_failsIfNameNotFound()592     public void releaseProjection_failsIfNameNotFound() throws Exception {
593         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
594                 .thenReturn(TestInjector.CALLING_UID);
595         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
596         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
597         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
598                 .thenThrow(new PackageManager.NameNotFoundException());
599 
600         assertThrows(SecurityException.class, () -> mService.releaseProjection(
601                 PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
602         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
603     }
604 
605     @Test
releaseProjection_enforcesToggleAutomotiveProjectionPermission()606     public void releaseProjection_enforcesToggleAutomotiveProjectionPermission() throws Exception {
607         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
608                 .thenReturn(TestInjector.CALLING_UID);
609         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
610         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
611         doThrow(new SecurityException()).when(mContext).enforceCallingPermission(
612                 eq(Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION), any());
613 
614         // Should not be enforced for other types of projection.
615         int nonAutomotiveProjectionType = PROJECTION_TYPE_AUTOMOTIVE * 2;
616         mService.releaseProjection(nonAutomotiveProjectionType, PACKAGE_NAME);
617         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
618 
619         assertThrows(SecurityException.class, () -> mService.requestProjection(mBinder,
620                 PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
621         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
622     }
623 
624     @Test
releaseProjection()625     public void releaseProjection() throws Exception {
626         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
627                 .thenReturn(TestInjector.CALLING_UID);
628         requestAllPossibleProjectionTypes();
629         assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
630 
631         assertTrue(mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME));
632         int everythingButAutomotive = PROJECTION_TYPE_ALL & ~PROJECTION_TYPE_AUTOMOTIVE;
633         assertEquals(everythingButAutomotive, mService.getActiveProjectionTypes());
634 
635         for (int i = 0; i < Integer.SIZE; ++i) {
636             int projectionType = 1 << i;
637             assertEquals(projectionType != PROJECTION_TYPE_AUTOMOTIVE,
638                     (boolean) mService.releaseProjection(projectionType, PACKAGE_NAME));
639         }
640 
641         assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
642     }
643 
644     @Test
binderDeath_releasesProjection()645     public void binderDeath_releasesProjection() throws Exception {
646         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
647                 .thenReturn(TestInjector.CALLING_UID);
648         requestAllPossibleProjectionTypes();
649         assertEquals(PROJECTION_TYPE_ALL, mService.getActiveProjectionTypes());
650         ArgumentCaptor<IBinder.DeathRecipient> deathRecipientCaptor = ArgumentCaptor.forClass(
651                 IBinder.DeathRecipient.class);
652         verify(mBinder, atLeastOnce()).linkToDeath(deathRecipientCaptor.capture(), anyInt());
653 
654         // Wipe them out. All of them.
655         deathRecipientCaptor.getAllValues().forEach(IBinder.DeathRecipient::binderDied);
656         assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
657     }
658 
659     @Test
getActiveProjectionTypes()660     public void getActiveProjectionTypes() throws Exception {
661         assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
662         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
663                 .thenReturn(TestInjector.CALLING_UID);
664         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
665         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
666         mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
667         assertEquals(PROJECTION_TYPE_NONE, mService.getActiveProjectionTypes());
668     }
669 
670     @Test
getProjectingPackages()671     public void getProjectingPackages() throws Exception {
672         assertTrue(mService.getProjectingPackages(PROJECTION_TYPE_ALL).isEmpty());
673         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
674                 .thenReturn(TestInjector.CALLING_UID);
675         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
676         assertEquals(1, mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE).size());
677         assertEquals(1, mService.getProjectingPackages(PROJECTION_TYPE_ALL).size());
678         assertThat(mService.getProjectingPackages(PROJECTION_TYPE_AUTOMOTIVE),
679                 contains(PACKAGE_NAME));
680         assertThat(mService.getProjectingPackages(PROJECTION_TYPE_ALL), contains(PACKAGE_NAME));
681         mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
682         assertThat(mService.getProjectingPackages(PROJECTION_TYPE_ALL), empty());
683     }
684 
685     @Test
addOnProjectionStateChangedListener_enforcesReadProjStatePermission()686     public void addOnProjectionStateChangedListener_enforcesReadProjStatePermission() {
687         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
688                 eq(android.Manifest.permission.READ_PROJECTION_STATE), any());
689         IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
690 
691         assertThrows(SecurityException.class, () -> mService.addOnProjectionStateChangedListener(
692                 listener, PROJECTION_TYPE_ALL));
693     }
694 
695     @Test
addOnProjectionStateChangedListener_callsListenerIfProjectionActive()696     public void addOnProjectionStateChangedListener_callsListenerIfProjectionActive()
697             throws Exception {
698         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
699                 .thenReturn(TestInjector.CALLING_UID);
700         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
701         assertEquals(PROJECTION_TYPE_AUTOMOTIVE, mService.getActiveProjectionTypes());
702 
703         IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
704         when(listener.asBinder()).thenReturn(mBinder);  // Any binder will do
705         mService.addOnProjectionStateChangedListener(listener, PROJECTION_TYPE_ALL);
706         verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_AUTOMOTIVE),
707                 eq(List.of(PACKAGE_NAME)));
708     }
709 
710     @Test
removeOnProjectionStateChangedListener_enforcesReadProjStatePermission()711     public void removeOnProjectionStateChangedListener_enforcesReadProjStatePermission() {
712         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
713                 eq(android.Manifest.permission.READ_PROJECTION_STATE), any());
714         IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
715 
716         assertThrows(SecurityException.class, () -> mService.removeOnProjectionStateChangedListener(
717                 listener));
718     }
719 
720     @Test
removeOnProjectionStateChangedListener()721     public void removeOnProjectionStateChangedListener() throws Exception {
722         IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
723         when(listener.asBinder()).thenReturn(mBinder); // Any binder will do.
724         mService.addOnProjectionStateChangedListener(listener, PROJECTION_TYPE_ALL);
725 
726         mService.removeOnProjectionStateChangedListener(listener);
727         // Now set automotive projection, should not call back.
728         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
729                 .thenReturn(TestInjector.CALLING_UID);
730         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
731         verify(listener, never()).onProjectionStateChanged(anyInt(), any());
732     }
733 
734     @Test
projectionStateChangedListener_calledWhenStateChanges()735     public void projectionStateChangedListener_calledWhenStateChanges() throws Exception {
736         IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
737         when(listener.asBinder()).thenReturn(mBinder); // Any binder will do.
738         mService.addOnProjectionStateChangedListener(listener, PROJECTION_TYPE_ALL);
739         verify(listener, atLeastOnce()).asBinder(); // Called twice during register.
740 
741         // No calls initially, no projection state set.
742         verifyNoMoreInteractions(listener);
743 
744         // Now set automotive projection, should call back.
745         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
746                 .thenReturn(TestInjector.CALLING_UID);
747         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
748         verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_AUTOMOTIVE),
749                 eq(List.of(PACKAGE_NAME)));
750 
751         // Subsequent calls that are noops do nothing.
752         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
753         int unsetProjectionType = 0x0002;
754         mService.releaseProjection(unsetProjectionType, PACKAGE_NAME);
755         verifyNoMoreInteractions(listener);
756 
757         // Release should call back though.
758         mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
759         verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_NONE),
760                 eq(List.of()));
761 
762         // But only the first time.
763         mService.releaseProjection(PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
764         verifyNoMoreInteractions(listener);
765     }
766 
767     @Test
projectionStateChangedListener_calledForAnyRelevantStateChange()768     public void projectionStateChangedListener_calledForAnyRelevantStateChange() throws Exception {
769         int fakeProjectionType = 0x0002;
770         int otherFakeProjectionType = 0x0004;
771         String otherPackageName = "Internet Arms";
772         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
773                 .thenReturn(TestInjector.CALLING_UID);
774         when(mPackageManager.getPackageUidAsUser(eq(otherPackageName), anyInt()))
775                 .thenReturn(TestInjector.CALLING_UID);
776         IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
777         when(listener.asBinder()).thenReturn(mBinder); // Any binder will do.
778         IOnProjectionStateChangedListener listener2 = mock(IOnProjectionStateChangedListener.class);
779         when(listener2.asBinder()).thenReturn(mBinder); // Any binder will do.
780         mService.addOnProjectionStateChangedListener(listener, fakeProjectionType);
781         mService.addOnProjectionStateChangedListener(listener2,
782                 fakeProjectionType | otherFakeProjectionType);
783         verify(listener, atLeastOnce()).asBinder(); // Called twice during register.
784         verify(listener2, atLeastOnce()).asBinder(); // Called twice during register.
785 
786         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
787         verifyNoMoreInteractions(listener, listener2);
788 
789         // fakeProjectionType should trigger both.
790         mService.requestProjection(mBinder, fakeProjectionType, PACKAGE_NAME);
791         verify(listener).onProjectionStateChanged(eq(fakeProjectionType),
792                 eq(List.of(PACKAGE_NAME)));
793         verify(listener2).onProjectionStateChanged(eq(fakeProjectionType),
794                 eq(List.of(PACKAGE_NAME)));
795 
796         // otherFakeProjectionType should only trigger the second listener.
797         mService.requestProjection(mBinder, otherFakeProjectionType, otherPackageName);
798         verifyNoMoreInteractions(listener);
799         verify(listener2).onProjectionStateChanged(
800                 eq(fakeProjectionType | otherFakeProjectionType),
801                 eq(List.of(PACKAGE_NAME, otherPackageName)));
802 
803         // Turning off fakeProjectionType should trigger both again.
804         mService.releaseProjection(fakeProjectionType, PACKAGE_NAME);
805         verify(listener).onProjectionStateChanged(eq(PROJECTION_TYPE_NONE), eq(List.of()));
806         verify(listener2).onProjectionStateChanged(eq(otherFakeProjectionType),
807                 eq(List.of(otherPackageName)));
808 
809         // Turning off otherFakeProjectionType should only trigger the second listener.
810         mService.releaseProjection(otherFakeProjectionType, otherPackageName);
811         verifyNoMoreInteractions(listener);
812         verify(listener2).onProjectionStateChanged(eq(PROJECTION_TYPE_NONE), eq(List.of()));
813     }
814 
815     @Test
projectionStateChangedListener_unregisteredOnDeath()816     public void projectionStateChangedListener_unregisteredOnDeath() throws Exception {
817         IOnProjectionStateChangedListener listener = mock(IOnProjectionStateChangedListener.class);
818         IBinder listenerBinder = mock(IBinder.class);
819         when(listener.asBinder()).thenReturn(listenerBinder);
820         mService.addOnProjectionStateChangedListener(listener, PROJECTION_TYPE_ALL);
821         ArgumentCaptor<IBinder.DeathRecipient> listenerDeathRecipient = ArgumentCaptor.forClass(
822                 IBinder.DeathRecipient.class);
823         verify(listenerBinder).linkToDeath(listenerDeathRecipient.capture(), anyInt());
824 
825         // Now kill the binder for the listener. This should remove it from the list of listeners.
826         listenerDeathRecipient.getValue().binderDied();
827         when(mPackageManager.getPackageUidAsUser(eq(PACKAGE_NAME), anyInt()))
828                 .thenReturn(TestInjector.CALLING_UID);
829         mService.requestProjection(mBinder, PROJECTION_TYPE_AUTOMOTIVE, PACKAGE_NAME);
830         verify(listener, never()).onProjectionStateChanged(anyInt(), any());
831     }
832 
requestAllPossibleProjectionTypes()833     private void requestAllPossibleProjectionTypes() throws RemoteException {
834         for (int i = 0; i < Integer.SIZE; ++i) {
835             mService.requestProjection(mBinder, 1 << i, PACKAGE_NAME);
836         }
837     }
838 
839     private static class TestInjector extends UiModeManagerService.Injector {
840         private static final int CALLING_UID = 8675309;
841 
getCallingUid()842         public int getCallingUid() {
843             return CALLING_UID;
844         }
845     }
846 }
847