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