1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.statusbar.policy 18 19 import android.app.ActivityOptions 20 import android.app.IActivityManager 21 import android.app.Notification 22 import android.app.Notification.FLAG_FOREGROUND_SERVICE 23 import android.app.Notification.VISIBILITY_PRIVATE 24 import android.app.Notification.VISIBILITY_PUBLIC 25 import android.app.NotificationChannel 26 import android.app.NotificationManager.IMPORTANCE_HIGH 27 import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE 28 import android.content.pm.PackageManager 29 import android.media.projection.MediaProjectionInfo 30 import android.media.projection.MediaProjectionManager 31 import android.os.Process 32 import android.os.UserHandle 33 import android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION 34 import android.platform.test.annotations.DisableFlags 35 import android.platform.test.annotations.EnableFlags 36 import android.platform.test.annotations.RequiresFlagsDisabled 37 import android.platform.test.annotations.RequiresFlagsEnabled 38 import android.platform.test.flag.junit.DeviceFlagsValueProvider 39 import android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS 40 import android.telephony.TelephonyManager 41 import android.testing.AndroidTestingRunner 42 import android.testing.TestableLooper.RunWithLooper 43 import androidx.test.filters.SmallTest 44 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession 45 import com.android.dx.mockito.inline.extended.ExtendedMockito.verify 46 import com.android.internal.util.FrameworkStatsLog 47 import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING 48 import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX 49 import com.android.systemui.SysuiTestCase 50 import com.android.systemui.log.logcatLogBuffer 51 import com.android.systemui.statusbar.RankingBuilder 52 import com.android.systemui.statusbar.notification.collection.NotificationEntry 53 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder 54 import com.android.systemui.util.concurrency.FakeExecutor 55 import com.android.systemui.util.concurrency.mockExecutorHandler 56 import com.android.systemui.util.mockito.whenever 57 import com.android.systemui.util.mockito.withArgCaptor 58 import com.android.systemui.util.settings.FakeGlobalSettings 59 import com.android.systemui.util.time.FakeSystemClock 60 import org.junit.After 61 import org.junit.Assert.assertFalse 62 import org.junit.Assert.assertNotNull 63 import org.junit.Assert.assertTrue 64 import org.junit.Before 65 import org.junit.Rule 66 import org.junit.Test 67 import org.junit.runner.RunWith 68 import org.mockito.ArgumentMatchers.any 69 import org.mockito.ArgumentMatchers.anyLong 70 import org.mockito.ArgumentMatchers.anyString 71 import org.mockito.ArgumentMatchers.eq 72 import org.mockito.Mock 73 import org.mockito.Mockito.mock 74 import org.mockito.Mockito.times 75 import org.mockito.Mockito.verifyNoMoreInteractions 76 import org.mockito.MockitoAnnotations 77 import org.mockito.MockitoSession 78 import org.mockito.quality.Strictness 79 80 @SmallTest 81 @RunWith(AndroidTestingRunner::class) 82 @RunWithLooper 83 @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) 84 class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { 85 @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() 86 87 private val logger = SensitiveNotificationProtectionControllerLogger(logcatLogBuffer()) 88 89 @Mock private lateinit var activityManager: IActivityManager 90 @Mock private lateinit var mediaProjectionManager: MediaProjectionManager 91 @Mock private lateinit var packageManager: PackageManager 92 @Mock private lateinit var telephonyManager: TelephonyManager 93 @Mock private lateinit var listener1: Runnable 94 @Mock private lateinit var listener2: Runnable 95 @Mock private lateinit var listener3: Runnable 96 97 private lateinit var staticMockSession: MockitoSession 98 private lateinit var executor: FakeExecutor 99 private lateinit var globalSettings: FakeGlobalSettings 100 private lateinit var mediaProjectionCallback: MediaProjectionManager.Callback 101 private lateinit var controller: SensitiveNotificationProtectionControllerImpl 102 private lateinit var mediaProjectionInfo: MediaProjectionInfo 103 104 @Before setUpnull105 fun setUp() { 106 staticMockSession = 107 mockitoSession() 108 .mockStatic(FrameworkStatsLog::class.java) 109 .strictness(Strictness.LENIENT) 110 .startMocking() 111 allowTestableLooperAsMainThread() // for updating exempt packages and notifying listeners 112 MockitoAnnotations.initMocks(this) 113 114 setShareFullScreen() 115 whenever(activityManager.bugreportWhitelistedPackages) 116 .thenReturn(listOf(BUGREPORT_PACKAGE_NAME)) 117 whenever( 118 packageManager.getPackageUidAsUser( 119 TEST_PROJECTION_PACKAGE_NAME, 120 UserHandle.CURRENT.identifier 121 ) 122 ) 123 .thenReturn(TEST_PROJECTION_PACKAGE_UID) 124 whenever( 125 packageManager.getPackageUidAsUser( 126 BUGREPORT_PACKAGE_NAME, 127 UserHandle.CURRENT.identifier 128 ) 129 ) 130 .thenReturn(BUGREPORT_PACKAGE_UID) 131 // SystemUi context package name is exempt, but in test scenarios its 132 // com.android.systemui.tests so use that instead of hardcoding. Setup packagemanager to 133 // return the correct uid in this scenario 134 whenever( 135 packageManager.getPackageUidAsUser( 136 mContext.packageName, 137 UserHandle.CURRENT.identifier 138 ) 139 ) 140 .thenReturn(mContext.applicationInfo.uid) 141 142 whenever(packageManager.checkPermission(anyString(), anyString())) 143 .thenReturn(PackageManager.PERMISSION_DENIED) 144 145 whenever(telephonyManager.getEmergencyAssistancePackageName()) 146 .thenReturn(EMERGENCY_ASSISTANCE_PACKAGE_NAME) 147 148 executor = FakeExecutor(FakeSystemClock()) 149 globalSettings = FakeGlobalSettings() 150 controller = 151 SensitiveNotificationProtectionControllerImpl( 152 mContext, 153 globalSettings, 154 mediaProjectionManager, 155 activityManager, 156 packageManager, 157 telephonyManager, 158 mockExecutorHandler(executor), 159 executor, 160 logger 161 ) 162 163 // Process pending work (getting global setting and list of exemptions) 164 executor.runAllReady() 165 166 // Obtain useful MediaProjectionCallback 167 mediaProjectionCallback = withArgCaptor { 168 verify(mediaProjectionManager).addCallback(capture(), any()) 169 } 170 } 171 172 @After tearDownnull173 fun tearDown() { 174 staticMockSession.finishMocking() 175 } 176 177 @Test init_registerMediaProjectionManagerCallbacknull178 fun init_registerMediaProjectionManagerCallback() { 179 assertNotNull(mediaProjectionCallback) 180 } 181 182 @Test registerSensitiveStateListener_singleListenernull183 fun registerSensitiveStateListener_singleListener() { 184 controller.registerSensitiveStateListener(listener1) 185 186 mediaProjectionCallback.onStart(mediaProjectionInfo) 187 mediaProjectionCallback.onStop(mediaProjectionInfo) 188 189 verify(listener1, times(2)).run() 190 } 191 192 @Test registerSensitiveStateListener_multipleListenersnull193 fun registerSensitiveStateListener_multipleListeners() { 194 controller.registerSensitiveStateListener(listener1) 195 controller.registerSensitiveStateListener(listener2) 196 197 mediaProjectionCallback.onStart(mediaProjectionInfo) 198 mediaProjectionCallback.onStop(mediaProjectionInfo) 199 200 verify(listener1, times(2)).run() 201 verify(listener2, times(2)).run() 202 } 203 204 @Test registerSensitiveStateListener_afterProjectionActivenull205 fun registerSensitiveStateListener_afterProjectionActive() { 206 mediaProjectionCallback.onStart(mediaProjectionInfo) 207 208 controller.registerSensitiveStateListener(listener1) 209 verifyNoMoreInteractions(listener1) 210 211 mediaProjectionCallback.onStop(mediaProjectionInfo) 212 213 verify(listener1).run() 214 } 215 216 @Test unregisterSensitiveStateListener_singleListenernull217 fun unregisterSensitiveStateListener_singleListener() { 218 controller.registerSensitiveStateListener(listener1) 219 220 mediaProjectionCallback.onStart(mediaProjectionInfo) 221 mediaProjectionCallback.onStop(mediaProjectionInfo) 222 223 verify(listener1, times(2)).run() 224 225 controller.unregisterSensitiveStateListener(listener1) 226 227 mediaProjectionCallback.onStart(mediaProjectionInfo) 228 mediaProjectionCallback.onStop(mediaProjectionInfo) 229 230 verifyNoMoreInteractions(listener1) 231 } 232 233 @Test unregisterSensitiveStateListener_multipleListenersnull234 fun unregisterSensitiveStateListener_multipleListeners() { 235 controller.registerSensitiveStateListener(listener1) 236 controller.registerSensitiveStateListener(listener2) 237 controller.registerSensitiveStateListener(listener3) 238 239 mediaProjectionCallback.onStart(mediaProjectionInfo) 240 mediaProjectionCallback.onStop(mediaProjectionInfo) 241 242 verify(listener1, times(2)).run() 243 verify(listener2, times(2)).run() 244 verify(listener3, times(2)).run() 245 246 controller.unregisterSensitiveStateListener(listener1) 247 controller.unregisterSensitiveStateListener(listener2) 248 249 mediaProjectionCallback.onStart(mediaProjectionInfo) 250 mediaProjectionCallback.onStop(mediaProjectionInfo) 251 252 verifyNoMoreInteractions(listener1) 253 verifyNoMoreInteractions(listener2) 254 verify(listener3, times(4)).run() 255 } 256 257 @Test isSensitiveStateActive_projectionInactive_falsenull258 fun isSensitiveStateActive_projectionInactive_false() { 259 assertFalse(controller.isSensitiveStateActive) 260 } 261 262 @Test isSensitiveStateActive_projectionActive_truenull263 fun isSensitiveStateActive_projectionActive_true() { 264 mediaProjectionCallback.onStart(mediaProjectionInfo) 265 266 assertTrue(controller.isSensitiveStateActive) 267 } 268 269 @Test isSensitiveStateActive_projectionInactiveAfterActive_falsenull270 fun isSensitiveStateActive_projectionInactiveAfterActive_false() { 271 mediaProjectionCallback.onStart(mediaProjectionInfo) 272 mediaProjectionCallback.onStop(mediaProjectionInfo) 273 274 assertFalse(controller.isSensitiveStateActive) 275 } 276 277 @Test isSensitiveStateActive_projectionActiveAfterInactive_truenull278 fun isSensitiveStateActive_projectionActiveAfterInactive_true() { 279 mediaProjectionCallback.onStart(mediaProjectionInfo) 280 mediaProjectionCallback.onStop(mediaProjectionInfo) 281 mediaProjectionCallback.onStart(mediaProjectionInfo) 282 283 assertTrue(controller.isSensitiveStateActive) 284 } 285 286 @Test isSensitiveStateActive_projectionActive_singleActivity_falsenull287 fun isSensitiveStateActive_projectionActive_singleActivity_false() { 288 setShareSingleApp() 289 mediaProjectionCallback.onStart(mediaProjectionInfo) 290 291 assertFalse(controller.isSensitiveStateActive) 292 } 293 294 @Test isSensitiveStateActive_projectionActive_sysuiExempt_falsenull295 fun isSensitiveStateActive_projectionActive_sysuiExempt_false() { 296 // SystemUi context package name is exempt, but in test scenarios its 297 // com.android.systemui.tests so use that instead of hardcoding 298 setShareFullScreenViaSystemUi() 299 mediaProjectionCallback.onStart(mediaProjectionInfo) 300 301 assertFalse(controller.isSensitiveStateActive) 302 } 303 304 @Test 305 @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) isSensitiveStateActive_projectionActive_permissionExempt_flagDisabled_truenull306 fun isSensitiveStateActive_projectionActive_permissionExempt_flagDisabled_true() { 307 whenever( 308 packageManager.checkPermission( 309 android.Manifest.permission.RECORD_SENSITIVE_CONTENT, 310 mediaProjectionInfo.packageName 311 ) 312 ) 313 .thenReturn(PackageManager.PERMISSION_GRANTED) 314 mediaProjectionCallback.onStart(mediaProjectionInfo) 315 316 assertTrue(controller.isSensitiveStateActive) 317 } 318 319 @Test 320 @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) isSensitiveStateActive_projectionActive_permissionExempt_falsenull321 fun isSensitiveStateActive_projectionActive_permissionExempt_false() { 322 whenever( 323 packageManager.checkPermission( 324 android.Manifest.permission.RECORD_SENSITIVE_CONTENT, 325 mediaProjectionInfo.packageName 326 ) 327 ) 328 .thenReturn(PackageManager.PERMISSION_GRANTED) 329 mediaProjectionCallback.onStart(mediaProjectionInfo) 330 331 assertFalse(controller.isSensitiveStateActive) 332 } 333 334 @Test isSensitiveStateActive_projectionActive_bugReportHandlerExempt_falsenull335 fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() { 336 setShareFullScreenViaBugReportHandler() 337 mediaProjectionCallback.onStart(mediaProjectionInfo) 338 339 assertFalse(controller.isSensitiveStateActive) 340 } 341 342 @Test isSensitiveStateActive_projectionActive_disabledViaDevOption_falsenull343 fun isSensitiveStateActive_projectionActive_disabledViaDevOption_false() { 344 setDisabledViaDeveloperOption() 345 mediaProjectionCallback.onStart(mediaProjectionInfo) 346 347 assertFalse(controller.isSensitiveStateActive) 348 } 349 350 @Test shouldProtectNotification_projectionInactive_falsenull351 fun shouldProtectNotification_projectionInactive_false() { 352 val notificationEntry = mock(NotificationEntry::class.java) 353 354 assertFalse(controller.shouldProtectNotification(notificationEntry)) 355 } 356 357 @Test shouldProtectNotification_projectionActive_singleActivity_falsenull358 fun shouldProtectNotification_projectionActive_singleActivity_false() { 359 setShareSingleApp() 360 mediaProjectionCallback.onStart(mediaProjectionInfo) 361 362 val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) 363 364 assertFalse(controller.shouldProtectNotification(notificationEntry)) 365 } 366 367 @Test shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_falsenull368 fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() { 369 mediaProjectionCallback.onStart(mediaProjectionInfo) 370 371 val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 372 373 assertFalse(controller.shouldProtectNotification(notificationEntry)) 374 } 375 376 @Test shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_truenull377 fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() { 378 mediaProjectionCallback.onStart(mediaProjectionInfo) 379 380 val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME) 381 382 assertTrue(controller.shouldProtectNotification(notificationEntry)) 383 } 384 385 @Test shouldProtectNotification_projectionActive_notFgsNotification_truenull386 fun shouldProtectNotification_projectionActive_notFgsNotification_true() { 387 mediaProjectionCallback.onStart(mediaProjectionInfo) 388 389 val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 390 391 assertTrue(controller.shouldProtectNotification(notificationEntry)) 392 } 393 394 @Test 395 @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_truenull396 fun shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_true() { 397 mediaProjectionCallback.onStart(mediaProjectionInfo) 398 399 val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 400 401 assertTrue(controller.shouldProtectNotification(notificationEntry)) 402 } 403 404 @Test 405 @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) shouldProtectNotification_projectionActive_isFromCoreApp_falsenull406 fun shouldProtectNotification_projectionActive_isFromCoreApp_false() { 407 mediaProjectionCallback.onStart(mediaProjectionInfo) 408 409 val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 410 411 assertFalse(controller.shouldProtectNotification(notificationEntry)) 412 } 413 414 @Test 415 @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) shouldProtectNotification_projectionActive_isFromEmergencyPackage_fixDisabled_truenull416 fun shouldProtectNotification_projectionActive_isFromEmergencyPackage_fixDisabled_true() { 417 mediaProjectionCallback.onStart(mediaProjectionInfo) 418 419 val notificationEntry = setupNotificationEntry(EMERGENCY_ASSISTANCE_PACKAGE_NAME) 420 421 assertTrue(controller.shouldProtectNotification(notificationEntry)) 422 } 423 424 @Test 425 @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) shouldProtectNotification_projectionActive_isFromEmergencyPackage_falsenull426 fun shouldProtectNotification_projectionActive_isFromEmergencyPackage_false() { 427 mediaProjectionCallback.onStart(mediaProjectionInfo) 428 429 val notificationEntry = setupNotificationEntry(EMERGENCY_ASSISTANCE_PACKAGE_NAME) 430 431 assertFalse(controller.shouldProtectNotification(notificationEntry)) 432 } 433 434 @Test shouldProtectNotification_projectionActive_sysuiExempt_falsenull435 fun shouldProtectNotification_projectionActive_sysuiExempt_false() { 436 // SystemUi context package name is exempt, but in test scenarios its 437 // com.android.systemui.tests so use that instead of hardcoding 438 setShareFullScreenViaSystemUi() 439 mediaProjectionCallback.onStart(mediaProjectionInfo) 440 441 val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) 442 443 assertFalse(controller.shouldProtectNotification(notificationEntry)) 444 } 445 446 @Test 447 @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) shouldProtectNotification_projectionActive_permissionExempt_flagDisabled_truenull448 fun shouldProtectNotification_projectionActive_permissionExempt_flagDisabled_true() { 449 whenever( 450 packageManager.checkPermission( 451 android.Manifest.permission.RECORD_SENSITIVE_CONTENT, 452 mediaProjectionInfo.packageName 453 ) 454 ) 455 .thenReturn(PackageManager.PERMISSION_GRANTED) 456 mediaProjectionCallback.onStart(mediaProjectionInfo) 457 458 val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) 459 460 assertTrue(controller.shouldProtectNotification(notificationEntry)) 461 } 462 463 @Test 464 @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) shouldProtectNotification_projectionActive_permissionExempt_falsenull465 fun shouldProtectNotification_projectionActive_permissionExempt_false() { 466 whenever( 467 packageManager.checkPermission( 468 android.Manifest.permission.RECORD_SENSITIVE_CONTENT, 469 mediaProjectionInfo.packageName 470 ) 471 ) 472 .thenReturn(PackageManager.PERMISSION_GRANTED) 473 mediaProjectionCallback.onStart(mediaProjectionInfo) 474 475 val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) 476 477 assertFalse(controller.shouldProtectNotification(notificationEntry)) 478 } 479 480 @Test shouldProtectNotification_projectionActive_bugReportHandlerExempt_falsenull481 fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() { 482 setShareFullScreenViaBugReportHandler() 483 mediaProjectionCallback.onStart(mediaProjectionInfo) 484 485 val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) 486 487 assertFalse(controller.shouldProtectNotification(notificationEntry)) 488 } 489 490 @Test shouldProtectNotification_projectionActive_disabledViaDevOption_falsenull491 fun shouldProtectNotification_projectionActive_disabledViaDevOption_false() { 492 setDisabledViaDeveloperOption() 493 mediaProjectionCallback.onStart(mediaProjectionInfo) 494 495 val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 496 497 assertFalse(controller.shouldProtectNotification(notificationEntry)) 498 } 499 500 @Test shouldProtectNotification_projectionActive_publicNotification_falsenull501 fun shouldProtectNotification_projectionActive_publicNotification_false() { 502 mediaProjectionCallback.onStart(mediaProjectionInfo) 503 504 // App marked notification visibility as public 505 val notificationEntry = setupPublicNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 506 507 assertFalse(controller.shouldProtectNotification(notificationEntry)) 508 } 509 510 @Test shouldProtectNotification_projectionActive_publicNotificationUserChannelOverride_truenull511 fun shouldProtectNotification_projectionActive_publicNotificationUserChannelOverride_true() { 512 mediaProjectionCallback.onStart(mediaProjectionInfo) 513 514 val notificationEntry = 515 setupPublicNotificationEntryWithUserOverriddenChannel(TEST_PROJECTION_PACKAGE_NAME) 516 517 assertTrue(controller.shouldProtectNotification(notificationEntry)) 518 } 519 520 @Test logSensitiveContentProtectionSessionnull521 fun logSensitiveContentProtectionSession() { 522 mediaProjectionCallback.onStart(mediaProjectionInfo) 523 524 verify { 525 FrameworkStatsLog.write( 526 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 527 anyLong(), 528 eq(TEST_PROJECTION_PACKAGE_UID), 529 eq(false), 530 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START), 531 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 532 ) 533 } 534 535 mediaProjectionCallback.onStop(mediaProjectionInfo) 536 537 verify { 538 FrameworkStatsLog.write( 539 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 540 anyLong(), 541 eq(TEST_PROJECTION_PACKAGE_UID), 542 eq(false), 543 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP), 544 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 545 ) 546 } 547 } 548 549 @Test logSensitiveContentProtectionSession_exemptViaShareSingleAppnull550 fun logSensitiveContentProtectionSession_exemptViaShareSingleApp() { 551 setShareSingleApp() 552 553 mediaProjectionCallback.onStart(mediaProjectionInfo) 554 555 verify { 556 FrameworkStatsLog.write( 557 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 558 anyLong(), 559 eq(TEST_PROJECTION_PACKAGE_UID), 560 eq(true), 561 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START), 562 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 563 ) 564 } 565 566 mediaProjectionCallback.onStop(mediaProjectionInfo) 567 568 verify { 569 FrameworkStatsLog.write( 570 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 571 anyLong(), 572 eq(TEST_PROJECTION_PACKAGE_UID), 573 eq(true), 574 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP), 575 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 576 ) 577 } 578 } 579 580 @Test logSensitiveContentProtectionSession_exemptViaDeveloperOptionnull581 fun logSensitiveContentProtectionSession_exemptViaDeveloperOption() { 582 setDisabledViaDeveloperOption() 583 584 mediaProjectionCallback.onStart(mediaProjectionInfo) 585 586 verify { 587 FrameworkStatsLog.write( 588 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 589 anyLong(), 590 eq(TEST_PROJECTION_PACKAGE_UID), 591 eq(true), 592 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START), 593 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 594 ) 595 } 596 597 mediaProjectionCallback.onStop(mediaProjectionInfo) 598 599 verify { 600 FrameworkStatsLog.write( 601 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 602 anyLong(), 603 eq(TEST_PROJECTION_PACKAGE_UID), 604 eq(true), 605 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP), 606 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 607 ) 608 } 609 } 610 611 @Test logSensitiveContentProtectionSession_exemptViaSystemUinull612 fun logSensitiveContentProtectionSession_exemptViaSystemUi() { 613 // SystemUi context package name is exempt, but in test scenarios its 614 // com.android.systemui.tests so use that instead of hardcoding 615 setShareFullScreenViaSystemUi() 616 617 mediaProjectionCallback.onStart(mediaProjectionInfo) 618 619 verify { 620 FrameworkStatsLog.write( 621 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 622 anyLong(), 623 eq(mContext.applicationInfo.uid), 624 eq(true), 625 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START), 626 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 627 ) 628 } 629 630 mediaProjectionCallback.onStop(mediaProjectionInfo) 631 632 verify { 633 FrameworkStatsLog.write( 634 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 635 anyLong(), 636 eq(mContext.applicationInfo.uid), 637 eq(true), 638 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP), 639 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 640 ) 641 } 642 } 643 644 @Test logSensitiveContentProtectionSession_exemptViaBugReportHandlernull645 fun logSensitiveContentProtectionSession_exemptViaBugReportHandler() { 646 // Setup exempt via bugreport handler 647 setShareFullScreenViaBugReportHandler() 648 mediaProjectionCallback.onStart(mediaProjectionInfo) 649 650 verify { 651 FrameworkStatsLog.write( 652 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 653 anyLong(), 654 eq(BUGREPORT_PACKAGE_UID), 655 eq(true), 656 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START), 657 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 658 ) 659 } 660 661 mediaProjectionCallback.onStop(mediaProjectionInfo) 662 663 verify { 664 FrameworkStatsLog.write( 665 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 666 anyLong(), 667 eq(BUGREPORT_PACKAGE_UID), 668 eq(true), 669 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP), 670 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 671 ) 672 } 673 } 674 setDisabledViaDeveloperOptionnull675 private fun setDisabledViaDeveloperOption() { 676 globalSettings.putInt(DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1) 677 678 // Process pending work that gets current developer option global setting 679 executor.runAllReady() 680 } 681 setShareFullScreennull682 private fun setShareFullScreen() { 683 setShareScreen(TEST_PROJECTION_PACKAGE_NAME, true) 684 } 685 setShareFullScreenViaBugReportHandlernull686 private fun setShareFullScreenViaBugReportHandler() { 687 setShareScreen(BUGREPORT_PACKAGE_NAME, true) 688 } 689 setShareFullScreenViaSystemUinull690 private fun setShareFullScreenViaSystemUi() { 691 // SystemUi context package name is exempt, but in test scenarios its 692 // com.android.systemui.tests so use that instead of hardcoding 693 setShareScreen(mContext.packageName, true) 694 } 695 setShareSingleAppnull696 private fun setShareSingleApp() { 697 setShareScreen(TEST_PROJECTION_PACKAGE_NAME, false) 698 } 699 setShareScreennull700 private fun setShareScreen(packageName: String, fullScreen: Boolean) { 701 val launchCookie = if (fullScreen) null else ActivityOptions.LaunchCookie() 702 mediaProjectionInfo = MediaProjectionInfo(packageName, UserHandle.CURRENT, launchCookie) 703 } 704 setupNotificationEntrynull705 private fun setupNotificationEntry( 706 packageName: String, 707 isFgs: Boolean = false, 708 isCoreApp: Boolean = false, 709 overrideVisibility: Boolean = false, 710 overrideChannelVisibility: Boolean = false, 711 ): NotificationEntry { 712 val notification = Notification() 713 if (isFgs) { 714 notification.flags = notification.flags or FLAG_FOREGROUND_SERVICE 715 } 716 if (overrideVisibility) { 717 // Developer has marked notification as public 718 notification.visibility = VISIBILITY_PUBLIC 719 } 720 val notificationEntryBuilder = 721 NotificationEntryBuilder().setNotification(notification).setPkg(packageName) 722 if (isCoreApp) { 723 notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID - 10) 724 } else { 725 notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID + 10) 726 } 727 val notificationEntry = notificationEntryBuilder.build() 728 val channel = NotificationChannel("1", "1", IMPORTANCE_HIGH) 729 if (overrideChannelVisibility) { 730 // User doesn't allow private notifications at the channel level 731 channel.lockscreenVisibility = VISIBILITY_PRIVATE 732 } 733 notificationEntry.setRanking( 734 RankingBuilder(notificationEntry.ranking) 735 .setChannel(channel) 736 .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) 737 .build() 738 ) 739 return notificationEntry 740 } 741 setupFgsNotificationEntrynull742 private fun setupFgsNotificationEntry(packageName: String): NotificationEntry { 743 return setupNotificationEntry(packageName, isFgs = true) 744 } 745 setupCoreAppNotificationEntrynull746 private fun setupCoreAppNotificationEntry(packageName: String): NotificationEntry { 747 return setupNotificationEntry(packageName, isCoreApp = true) 748 } 749 setupPublicNotificationEntrynull750 private fun setupPublicNotificationEntry(packageName: String): NotificationEntry { 751 return setupNotificationEntry(packageName, overrideVisibility = true) 752 } 753 setupPublicNotificationEntryWithUserOverriddenChannelnull754 private fun setupPublicNotificationEntryWithUserOverriddenChannel( 755 packageName: String 756 ): NotificationEntry { 757 return setupNotificationEntry( 758 packageName, 759 overrideVisibility = true, 760 overrideChannelVisibility = true 761 ) 762 } 763 764 companion object { 765 private const val TEST_PROJECTION_PACKAGE_UID = 23 766 private const val BUGREPORT_PACKAGE_UID = 24 767 private const val TEST_PROJECTION_PACKAGE_NAME = 768 "com.android.systemui.statusbar.policy.projectionpackage" 769 private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage" 770 private const val EMERGENCY_ASSISTANCE_PACKAGE_NAME = "com.android.test.emergencyassistance" 771 private const val BUGREPORT_PACKAGE_NAME = "com.android.test.bugreporthandler" 772 } 773 } 774