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.Mockito.verifyZeroInteractions 77 import org.mockito.MockitoAnnotations 78 import org.mockito.MockitoSession 79 import org.mockito.quality.Strictness 80 81 @SmallTest 82 @RunWith(AndroidTestingRunner::class) 83 @RunWithLooper 84 @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) 85 class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { 86 @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() 87 88 private val logger = SensitiveNotificationProtectionControllerLogger(logcatLogBuffer()) 89 90 @Mock private lateinit var activityManager: IActivityManager 91 @Mock private lateinit var mediaProjectionManager: MediaProjectionManager 92 @Mock private lateinit var packageManager: PackageManager 93 @Mock private lateinit var telephonyManager: TelephonyManager 94 @Mock private lateinit var listener1: Runnable 95 @Mock private lateinit var listener2: Runnable 96 @Mock private lateinit var listener3: Runnable 97 98 private lateinit var staticMockSession: MockitoSession 99 private lateinit var executor: FakeExecutor 100 private lateinit var globalSettings: FakeGlobalSettings 101 private lateinit var mediaProjectionCallback: MediaProjectionManager.Callback 102 private lateinit var controller: SensitiveNotificationProtectionControllerImpl 103 private lateinit var mediaProjectionInfo: MediaProjectionInfo 104 105 @Before setUpnull106 fun setUp() { 107 staticMockSession = 108 mockitoSession() 109 .mockStatic(FrameworkStatsLog::class.java) 110 .strictness(Strictness.LENIENT) 111 .startMocking() 112 allowTestableLooperAsMainThread() // for updating exempt packages and notifying listeners 113 MockitoAnnotations.initMocks(this) 114 115 setShareFullScreen() 116 whenever(activityManager.bugreportWhitelistedPackages) 117 .thenReturn(listOf(BUGREPORT_PACKAGE_NAME)) 118 whenever( 119 packageManager.getPackageUidAsUser( 120 TEST_PROJECTION_PACKAGE_NAME, 121 UserHandle.CURRENT.identifier 122 ) 123 ) 124 .thenReturn(TEST_PROJECTION_PACKAGE_UID) 125 whenever( 126 packageManager.getPackageUidAsUser( 127 BUGREPORT_PACKAGE_NAME, 128 UserHandle.CURRENT.identifier 129 ) 130 ) 131 .thenReturn(BUGREPORT_PACKAGE_UID) 132 // SystemUi context package name is exempt, but in test scenarios its 133 // com.android.systemui.tests so use that instead of hardcoding. Setup packagemanager to 134 // return the correct uid in this scenario 135 whenever( 136 packageManager.getPackageUidAsUser( 137 mContext.packageName, 138 UserHandle.CURRENT.identifier 139 ) 140 ) 141 .thenReturn(mContext.applicationInfo.uid) 142 143 whenever(packageManager.checkPermission(anyString(), anyString())) 144 .thenReturn(PackageManager.PERMISSION_DENIED) 145 146 whenever(telephonyManager.getEmergencyAssistancePackageName()) 147 .thenReturn(EMERGENCY_ASSISTANCE_PACKAGE_NAME) 148 149 executor = FakeExecutor(FakeSystemClock()) 150 globalSettings = FakeGlobalSettings() 151 controller = 152 SensitiveNotificationProtectionControllerImpl( 153 mContext, 154 globalSettings, 155 mediaProjectionManager, 156 activityManager, 157 packageManager, 158 telephonyManager, 159 mockExecutorHandler(executor), 160 executor, 161 logger 162 ) 163 164 // Process pending work (getting global setting and list of exemptions) 165 executor.runAllReady() 166 167 // Obtain useful MediaProjectionCallback 168 mediaProjectionCallback = withArgCaptor { 169 verify(mediaProjectionManager).addCallback(capture(), any()) 170 } 171 } 172 173 @After tearDownnull174 fun tearDown() { 175 staticMockSession.finishMocking() 176 } 177 178 @Test init_registerMediaProjectionManagerCallbacknull179 fun init_registerMediaProjectionManagerCallback() { 180 assertNotNull(mediaProjectionCallback) 181 } 182 183 @Test registerSensitiveStateListener_singleListenernull184 fun registerSensitiveStateListener_singleListener() { 185 controller.registerSensitiveStateListener(listener1) 186 187 mediaProjectionCallback.onStart(mediaProjectionInfo) 188 mediaProjectionCallback.onStop(mediaProjectionInfo) 189 190 verify(listener1, times(2)).run() 191 } 192 193 @Test registerSensitiveStateListener_multipleListenersnull194 fun registerSensitiveStateListener_multipleListeners() { 195 controller.registerSensitiveStateListener(listener1) 196 controller.registerSensitiveStateListener(listener2) 197 198 mediaProjectionCallback.onStart(mediaProjectionInfo) 199 mediaProjectionCallback.onStop(mediaProjectionInfo) 200 201 verify(listener1, times(2)).run() 202 verify(listener2, times(2)).run() 203 } 204 205 @Test registerSensitiveStateListener_afterProjectionActivenull206 fun registerSensitiveStateListener_afterProjectionActive() { 207 mediaProjectionCallback.onStart(mediaProjectionInfo) 208 209 controller.registerSensitiveStateListener(listener1) 210 verifyZeroInteractions(listener1) 211 212 mediaProjectionCallback.onStop(mediaProjectionInfo) 213 214 verify(listener1).run() 215 } 216 217 @Test unregisterSensitiveStateListener_singleListenernull218 fun unregisterSensitiveStateListener_singleListener() { 219 controller.registerSensitiveStateListener(listener1) 220 221 mediaProjectionCallback.onStart(mediaProjectionInfo) 222 mediaProjectionCallback.onStop(mediaProjectionInfo) 223 224 verify(listener1, times(2)).run() 225 226 controller.unregisterSensitiveStateListener(listener1) 227 228 mediaProjectionCallback.onStart(mediaProjectionInfo) 229 mediaProjectionCallback.onStop(mediaProjectionInfo) 230 231 verifyNoMoreInteractions(listener1) 232 } 233 234 @Test unregisterSensitiveStateListener_multipleListenersnull235 fun unregisterSensitiveStateListener_multipleListeners() { 236 controller.registerSensitiveStateListener(listener1) 237 controller.registerSensitiveStateListener(listener2) 238 controller.registerSensitiveStateListener(listener3) 239 240 mediaProjectionCallback.onStart(mediaProjectionInfo) 241 mediaProjectionCallback.onStop(mediaProjectionInfo) 242 243 verify(listener1, times(2)).run() 244 verify(listener2, times(2)).run() 245 verify(listener3, times(2)).run() 246 247 controller.unregisterSensitiveStateListener(listener1) 248 controller.unregisterSensitiveStateListener(listener2) 249 250 mediaProjectionCallback.onStart(mediaProjectionInfo) 251 mediaProjectionCallback.onStop(mediaProjectionInfo) 252 253 verifyNoMoreInteractions(listener1) 254 verifyNoMoreInteractions(listener2) 255 verify(listener3, times(4)).run() 256 } 257 258 @Test isSensitiveStateActive_projectionInactive_falsenull259 fun isSensitiveStateActive_projectionInactive_false() { 260 assertFalse(controller.isSensitiveStateActive) 261 } 262 263 @Test isSensitiveStateActive_projectionActive_truenull264 fun isSensitiveStateActive_projectionActive_true() { 265 mediaProjectionCallback.onStart(mediaProjectionInfo) 266 267 assertTrue(controller.isSensitiveStateActive) 268 } 269 270 @Test isSensitiveStateActive_projectionInactiveAfterActive_falsenull271 fun isSensitiveStateActive_projectionInactiveAfterActive_false() { 272 mediaProjectionCallback.onStart(mediaProjectionInfo) 273 mediaProjectionCallback.onStop(mediaProjectionInfo) 274 275 assertFalse(controller.isSensitiveStateActive) 276 } 277 278 @Test isSensitiveStateActive_projectionActiveAfterInactive_truenull279 fun isSensitiveStateActive_projectionActiveAfterInactive_true() { 280 mediaProjectionCallback.onStart(mediaProjectionInfo) 281 mediaProjectionCallback.onStop(mediaProjectionInfo) 282 mediaProjectionCallback.onStart(mediaProjectionInfo) 283 284 assertTrue(controller.isSensitiveStateActive) 285 } 286 287 @Test isSensitiveStateActive_projectionActive_singleActivity_falsenull288 fun isSensitiveStateActive_projectionActive_singleActivity_false() { 289 setShareSingleApp() 290 mediaProjectionCallback.onStart(mediaProjectionInfo) 291 292 assertFalse(controller.isSensitiveStateActive) 293 } 294 295 @Test isSensitiveStateActive_projectionActive_sysuiExempt_falsenull296 fun isSensitiveStateActive_projectionActive_sysuiExempt_false() { 297 // SystemUi context package name is exempt, but in test scenarios its 298 // com.android.systemui.tests so use that instead of hardcoding 299 setShareFullScreenViaSystemUi() 300 mediaProjectionCallback.onStart(mediaProjectionInfo) 301 302 assertFalse(controller.isSensitiveStateActive) 303 } 304 305 @Test 306 @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) isSensitiveStateActive_projectionActive_permissionExempt_flagDisabled_truenull307 fun isSensitiveStateActive_projectionActive_permissionExempt_flagDisabled_true() { 308 whenever( 309 packageManager.checkPermission( 310 android.Manifest.permission.RECORD_SENSITIVE_CONTENT, 311 mediaProjectionInfo.packageName 312 ) 313 ) 314 .thenReturn(PackageManager.PERMISSION_GRANTED) 315 mediaProjectionCallback.onStart(mediaProjectionInfo) 316 317 assertTrue(controller.isSensitiveStateActive) 318 } 319 320 @Test 321 @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) isSensitiveStateActive_projectionActive_permissionExempt_falsenull322 fun isSensitiveStateActive_projectionActive_permissionExempt_false() { 323 whenever( 324 packageManager.checkPermission( 325 android.Manifest.permission.RECORD_SENSITIVE_CONTENT, 326 mediaProjectionInfo.packageName 327 ) 328 ) 329 .thenReturn(PackageManager.PERMISSION_GRANTED) 330 mediaProjectionCallback.onStart(mediaProjectionInfo) 331 332 assertFalse(controller.isSensitiveStateActive) 333 } 334 335 @Test isSensitiveStateActive_projectionActive_bugReportHandlerExempt_falsenull336 fun isSensitiveStateActive_projectionActive_bugReportHandlerExempt_false() { 337 setShareFullScreenViaBugReportHandler() 338 mediaProjectionCallback.onStart(mediaProjectionInfo) 339 340 assertFalse(controller.isSensitiveStateActive) 341 } 342 343 @Test isSensitiveStateActive_projectionActive_disabledViaDevOption_falsenull344 fun isSensitiveStateActive_projectionActive_disabledViaDevOption_false() { 345 setDisabledViaDeveloperOption() 346 mediaProjectionCallback.onStart(mediaProjectionInfo) 347 348 assertFalse(controller.isSensitiveStateActive) 349 } 350 351 @Test shouldProtectNotification_projectionInactive_falsenull352 fun shouldProtectNotification_projectionInactive_false() { 353 val notificationEntry = mock(NotificationEntry::class.java) 354 355 assertFalse(controller.shouldProtectNotification(notificationEntry)) 356 } 357 358 @Test shouldProtectNotification_projectionActive_singleActivity_falsenull359 fun shouldProtectNotification_projectionActive_singleActivity_false() { 360 setShareSingleApp() 361 mediaProjectionCallback.onStart(mediaProjectionInfo) 362 363 val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) 364 365 assertFalse(controller.shouldProtectNotification(notificationEntry)) 366 } 367 368 @Test shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_falsenull369 fun shouldProtectNotification_projectionActive_fgsNotificationFromProjectionApp_false() { 370 mediaProjectionCallback.onStart(mediaProjectionInfo) 371 372 val notificationEntry = setupFgsNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 373 374 assertFalse(controller.shouldProtectNotification(notificationEntry)) 375 } 376 377 @Test shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_truenull378 fun shouldProtectNotification_projectionActive_fgsNotificationNotFromProjectionApp_true() { 379 mediaProjectionCallback.onStart(mediaProjectionInfo) 380 381 val notificationEntry = setupFgsNotificationEntry(TEST_PACKAGE_NAME) 382 383 assertTrue(controller.shouldProtectNotification(notificationEntry)) 384 } 385 386 @Test shouldProtectNotification_projectionActive_notFgsNotification_truenull387 fun shouldProtectNotification_projectionActive_notFgsNotification_true() { 388 mediaProjectionCallback.onStart(mediaProjectionInfo) 389 390 val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 391 392 assertTrue(controller.shouldProtectNotification(notificationEntry)) 393 } 394 395 @Test 396 @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_truenull397 fun shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_true() { 398 mediaProjectionCallback.onStart(mediaProjectionInfo) 399 400 val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 401 402 assertTrue(controller.shouldProtectNotification(notificationEntry)) 403 } 404 405 @Test 406 @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) shouldProtectNotification_projectionActive_isFromCoreApp_falsenull407 fun shouldProtectNotification_projectionActive_isFromCoreApp_false() { 408 mediaProjectionCallback.onStart(mediaProjectionInfo) 409 410 val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 411 412 assertFalse(controller.shouldProtectNotification(notificationEntry)) 413 } 414 415 @Test 416 @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) shouldProtectNotification_projectionActive_isFromEmergencyPackage_fixDisabled_truenull417 fun shouldProtectNotification_projectionActive_isFromEmergencyPackage_fixDisabled_true() { 418 mediaProjectionCallback.onStart(mediaProjectionInfo) 419 420 val notificationEntry = setupNotificationEntry(EMERGENCY_ASSISTANCE_PACKAGE_NAME) 421 422 assertTrue(controller.shouldProtectNotification(notificationEntry)) 423 } 424 425 @Test 426 @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) shouldProtectNotification_projectionActive_isFromEmergencyPackage_falsenull427 fun shouldProtectNotification_projectionActive_isFromEmergencyPackage_false() { 428 mediaProjectionCallback.onStart(mediaProjectionInfo) 429 430 val notificationEntry = setupNotificationEntry(EMERGENCY_ASSISTANCE_PACKAGE_NAME) 431 432 assertFalse(controller.shouldProtectNotification(notificationEntry)) 433 } 434 435 @Test shouldProtectNotification_projectionActive_sysuiExempt_falsenull436 fun shouldProtectNotification_projectionActive_sysuiExempt_false() { 437 // SystemUi context package name is exempt, but in test scenarios its 438 // com.android.systemui.tests so use that instead of hardcoding 439 setShareFullScreenViaSystemUi() 440 mediaProjectionCallback.onStart(mediaProjectionInfo) 441 442 val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) 443 444 assertFalse(controller.shouldProtectNotification(notificationEntry)) 445 } 446 447 @Test 448 @RequiresFlagsDisabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) shouldProtectNotification_projectionActive_permissionExempt_flagDisabled_truenull449 fun shouldProtectNotification_projectionActive_permissionExempt_flagDisabled_true() { 450 whenever( 451 packageManager.checkPermission( 452 android.Manifest.permission.RECORD_SENSITIVE_CONTENT, 453 mediaProjectionInfo.packageName 454 ) 455 ) 456 .thenReturn(PackageManager.PERMISSION_GRANTED) 457 mediaProjectionCallback.onStart(mediaProjectionInfo) 458 459 val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) 460 461 assertTrue(controller.shouldProtectNotification(notificationEntry)) 462 } 463 464 @Test 465 @RequiresFlagsEnabled(FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION) shouldProtectNotification_projectionActive_permissionExempt_falsenull466 fun shouldProtectNotification_projectionActive_permissionExempt_false() { 467 whenever( 468 packageManager.checkPermission( 469 android.Manifest.permission.RECORD_SENSITIVE_CONTENT, 470 mediaProjectionInfo.packageName 471 ) 472 ) 473 .thenReturn(PackageManager.PERMISSION_GRANTED) 474 mediaProjectionCallback.onStart(mediaProjectionInfo) 475 476 val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) 477 478 assertFalse(controller.shouldProtectNotification(notificationEntry)) 479 } 480 481 @Test shouldProtectNotification_projectionActive_bugReportHandlerExempt_falsenull482 fun shouldProtectNotification_projectionActive_bugReportHandlerExempt_false() { 483 setShareFullScreenViaBugReportHandler() 484 mediaProjectionCallback.onStart(mediaProjectionInfo) 485 486 val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) 487 488 assertFalse(controller.shouldProtectNotification(notificationEntry)) 489 } 490 491 @Test shouldProtectNotification_projectionActive_disabledViaDevOption_falsenull492 fun shouldProtectNotification_projectionActive_disabledViaDevOption_false() { 493 setDisabledViaDeveloperOption() 494 mediaProjectionCallback.onStart(mediaProjectionInfo) 495 496 val notificationEntry = setupNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 497 498 assertFalse(controller.shouldProtectNotification(notificationEntry)) 499 } 500 501 @Test shouldProtectNotification_projectionActive_publicNotification_falsenull502 fun shouldProtectNotification_projectionActive_publicNotification_false() { 503 mediaProjectionCallback.onStart(mediaProjectionInfo) 504 505 // App marked notification visibility as public 506 val notificationEntry = setupPublicNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) 507 508 assertFalse(controller.shouldProtectNotification(notificationEntry)) 509 } 510 511 @Test shouldProtectNotification_projectionActive_publicNotificationUserChannelOverride_truenull512 fun shouldProtectNotification_projectionActive_publicNotificationUserChannelOverride_true() { 513 mediaProjectionCallback.onStart(mediaProjectionInfo) 514 515 val notificationEntry = 516 setupPublicNotificationEntryWithUserOverriddenChannel(TEST_PROJECTION_PACKAGE_NAME) 517 518 assertTrue(controller.shouldProtectNotification(notificationEntry)) 519 } 520 521 @Test logSensitiveContentProtectionSessionnull522 fun logSensitiveContentProtectionSession() { 523 mediaProjectionCallback.onStart(mediaProjectionInfo) 524 525 verify { 526 FrameworkStatsLog.write( 527 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 528 anyLong(), 529 eq(TEST_PROJECTION_PACKAGE_UID), 530 eq(false), 531 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START), 532 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 533 ) 534 } 535 536 mediaProjectionCallback.onStop(mediaProjectionInfo) 537 538 verify { 539 FrameworkStatsLog.write( 540 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 541 anyLong(), 542 eq(TEST_PROJECTION_PACKAGE_UID), 543 eq(false), 544 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP), 545 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 546 ) 547 } 548 } 549 550 @Test logSensitiveContentProtectionSession_exemptViaShareSingleAppnull551 fun logSensitiveContentProtectionSession_exemptViaShareSingleApp() { 552 setShareSingleApp() 553 554 mediaProjectionCallback.onStart(mediaProjectionInfo) 555 556 verify { 557 FrameworkStatsLog.write( 558 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 559 anyLong(), 560 eq(TEST_PROJECTION_PACKAGE_UID), 561 eq(true), 562 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START), 563 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 564 ) 565 } 566 567 mediaProjectionCallback.onStop(mediaProjectionInfo) 568 569 verify { 570 FrameworkStatsLog.write( 571 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 572 anyLong(), 573 eq(TEST_PROJECTION_PACKAGE_UID), 574 eq(true), 575 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP), 576 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 577 ) 578 } 579 } 580 581 @Test logSensitiveContentProtectionSession_exemptViaDeveloperOptionnull582 fun logSensitiveContentProtectionSession_exemptViaDeveloperOption() { 583 setDisabledViaDeveloperOption() 584 585 mediaProjectionCallback.onStart(mediaProjectionInfo) 586 587 verify { 588 FrameworkStatsLog.write( 589 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 590 anyLong(), 591 eq(TEST_PROJECTION_PACKAGE_UID), 592 eq(true), 593 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START), 594 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 595 ) 596 } 597 598 mediaProjectionCallback.onStop(mediaProjectionInfo) 599 600 verify { 601 FrameworkStatsLog.write( 602 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 603 anyLong(), 604 eq(TEST_PROJECTION_PACKAGE_UID), 605 eq(true), 606 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP), 607 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 608 ) 609 } 610 } 611 612 @Test logSensitiveContentProtectionSession_exemptViaSystemUinull613 fun logSensitiveContentProtectionSession_exemptViaSystemUi() { 614 // SystemUi context package name is exempt, but in test scenarios its 615 // com.android.systemui.tests so use that instead of hardcoding 616 setShareFullScreenViaSystemUi() 617 618 mediaProjectionCallback.onStart(mediaProjectionInfo) 619 620 verify { 621 FrameworkStatsLog.write( 622 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 623 anyLong(), 624 eq(mContext.applicationInfo.uid), 625 eq(true), 626 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START), 627 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 628 ) 629 } 630 631 mediaProjectionCallback.onStop(mediaProjectionInfo) 632 633 verify { 634 FrameworkStatsLog.write( 635 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 636 anyLong(), 637 eq(mContext.applicationInfo.uid), 638 eq(true), 639 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP), 640 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 641 ) 642 } 643 } 644 645 @Test logSensitiveContentProtectionSession_exemptViaBugReportHandlernull646 fun logSensitiveContentProtectionSession_exemptViaBugReportHandler() { 647 // Setup exempt via bugreport handler 648 setShareFullScreenViaBugReportHandler() 649 mediaProjectionCallback.onStart(mediaProjectionInfo) 650 651 verify { 652 FrameworkStatsLog.write( 653 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 654 anyLong(), 655 eq(BUGREPORT_PACKAGE_UID), 656 eq(true), 657 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__START), 658 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 659 ) 660 } 661 662 mediaProjectionCallback.onStop(mediaProjectionInfo) 663 664 verify { 665 FrameworkStatsLog.write( 666 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION), 667 anyLong(), 668 eq(BUGREPORT_PACKAGE_UID), 669 eq(true), 670 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__STATE__STOP), 671 eq(FrameworkStatsLog.SENSITIVE_CONTENT_MEDIA_PROJECTION_SESSION__SOURCE__SYS_UI) 672 ) 673 } 674 } 675 setDisabledViaDeveloperOptionnull676 private fun setDisabledViaDeveloperOption() { 677 globalSettings.putInt(DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, 1) 678 679 // Process pending work that gets current developer option global setting 680 executor.runAllReady() 681 } 682 setShareFullScreennull683 private fun setShareFullScreen() { 684 setShareScreen(TEST_PROJECTION_PACKAGE_NAME, true) 685 } 686 setShareFullScreenViaBugReportHandlernull687 private fun setShareFullScreenViaBugReportHandler() { 688 setShareScreen(BUGREPORT_PACKAGE_NAME, true) 689 } 690 setShareFullScreenViaSystemUinull691 private fun setShareFullScreenViaSystemUi() { 692 // SystemUi context package name is exempt, but in test scenarios its 693 // com.android.systemui.tests so use that instead of hardcoding 694 setShareScreen(mContext.packageName, true) 695 } 696 setShareSingleAppnull697 private fun setShareSingleApp() { 698 setShareScreen(TEST_PROJECTION_PACKAGE_NAME, false) 699 } 700 setShareScreennull701 private fun setShareScreen(packageName: String, fullScreen: Boolean) { 702 val launchCookie = if (fullScreen) null else ActivityOptions.LaunchCookie() 703 mediaProjectionInfo = MediaProjectionInfo(packageName, UserHandle.CURRENT, launchCookie) 704 } 705 setupNotificationEntrynull706 private fun setupNotificationEntry( 707 packageName: String, 708 isFgs: Boolean = false, 709 isCoreApp: Boolean = false, 710 overrideVisibility: Boolean = false, 711 overrideChannelVisibility: Boolean = false, 712 ): NotificationEntry { 713 val notification = Notification() 714 if (isFgs) { 715 notification.flags = notification.flags or FLAG_FOREGROUND_SERVICE 716 } 717 if (overrideVisibility) { 718 // Developer has marked notification as public 719 notification.visibility = VISIBILITY_PUBLIC 720 } 721 val notificationEntryBuilder = 722 NotificationEntryBuilder().setNotification(notification).setPkg(packageName) 723 if (isCoreApp) { 724 notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID - 10) 725 } else { 726 notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID + 10) 727 } 728 val notificationEntry = notificationEntryBuilder.build() 729 val channel = NotificationChannel("1", "1", IMPORTANCE_HIGH) 730 if (overrideChannelVisibility) { 731 // User doesn't allow private notifications at the channel level 732 channel.lockscreenVisibility = VISIBILITY_PRIVATE 733 } 734 notificationEntry.setRanking( 735 RankingBuilder(notificationEntry.ranking) 736 .setChannel(channel) 737 .setVisibilityOverride(VISIBILITY_NO_OVERRIDE) 738 .build() 739 ) 740 return notificationEntry 741 } 742 setupFgsNotificationEntrynull743 private fun setupFgsNotificationEntry(packageName: String): NotificationEntry { 744 return setupNotificationEntry(packageName, isFgs = true) 745 } 746 setupCoreAppNotificationEntrynull747 private fun setupCoreAppNotificationEntry(packageName: String): NotificationEntry { 748 return setupNotificationEntry(packageName, isCoreApp = true) 749 } 750 setupPublicNotificationEntrynull751 private fun setupPublicNotificationEntry(packageName: String): NotificationEntry { 752 return setupNotificationEntry(packageName, overrideVisibility = true) 753 } 754 setupPublicNotificationEntryWithUserOverriddenChannelnull755 private fun setupPublicNotificationEntryWithUserOverriddenChannel( 756 packageName: String 757 ): NotificationEntry { 758 return setupNotificationEntry( 759 packageName, 760 overrideVisibility = true, 761 overrideChannelVisibility = true 762 ) 763 } 764 765 companion object { 766 private const val TEST_PROJECTION_PACKAGE_UID = 23 767 private const val BUGREPORT_PACKAGE_UID = 24 768 private const val TEST_PROJECTION_PACKAGE_NAME = 769 "com.android.systemui.statusbar.policy.projectionpackage" 770 private const val TEST_PACKAGE_NAME = "com.android.systemui.statusbar.policy.testpackage" 771 private const val EMERGENCY_ASSISTANCE_PACKAGE_NAME = "com.android.test.emergencyassistance" 772 private const val BUGREPORT_PACKAGE_NAME = "com.android.test.bugreporthandler" 773 } 774 } 775