1 /* <lambda>null2 * Copyright (C) 2023 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 package android.permissionui.cts 17 18 import android.Manifest 19 import android.app.Instrumentation 20 import android.app.UiAutomation 21 import android.app.compat.CompatChanges 22 import android.content.AttributionSource 23 import android.content.Context 24 import android.content.Intent 25 import android.content.pm.PackageManager 26 import android.hardware.camera2.CameraManager 27 import android.os.Build 28 import android.os.Process 29 import android.os.SystemClock 30 import android.os.SystemProperties 31 import android.os.UserManager 32 import android.permission.PermissionManager 33 import android.permission.cts.MtsIgnore 34 import android.platform.test.annotations.AsbSecurityTest 35 import android.platform.test.rule.ScreenRecordRule 36 import android.provider.DeviceConfig 37 import android.provider.Settings 38 import android.safetycenter.SafetyCenterManager 39 import android.server.wm.WindowManagerStateHelper 40 import android.util.Log 41 import androidx.annotation.RequiresApi 42 import androidx.test.filters.FlakyTest 43 import androidx.test.filters.SdkSuppress 44 import androidx.test.platform.app.InstrumentationRegistry 45 import androidx.test.uiautomator.By 46 import androidx.test.uiautomator.BySelector 47 import androidx.test.uiautomator.StaleObjectException 48 import androidx.test.uiautomator.UiDevice 49 import androidx.test.uiautomator.UiObject2 50 import androidx.test.uiautomator.UiSelector 51 import com.android.compatibility.common.util.CddTest 52 import com.android.compatibility.common.util.DisableAnimationRule 53 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity 54 import com.android.compatibility.common.util.SystemUtil.eventually 55 import com.android.compatibility.common.util.SystemUtil.runShellCommand 56 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow 57 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity 58 import com.android.compatibility.common.util.UiAutomatorUtils2 59 import com.android.compatibility.common.util.UiAutomatorUtils2.assertWithUiDump 60 import com.android.modules.utils.build.SdkLevel 61 import com.android.sts.common.util.StsExtraBusinessLogicTestCase 62 import java.util.regex.Pattern 63 import org.junit.After 64 import org.junit.Assert 65 import org.junit.Assert.assertEquals 66 import org.junit.Assert.assertFalse 67 import org.junit.Assert.assertNotNull 68 import org.junit.Assert.assertNull 69 import org.junit.Assert.assertTrue 70 import org.junit.Assume.assumeFalse 71 import org.junit.Assume.assumeTrue 72 import org.junit.Before 73 import org.junit.Rule 74 import org.junit.Test 75 76 private const val APK_PATH = 77 "/data/local/tmp/cts-permissionui/CtsAppThatAccessesMicAndCameraPermission.apk" 78 private const val APP_LABEL = "CtsCameraMicAccess" 79 private const val APP_PKG = "android.permissionui.cts.appthataccessescameraandmic" 80 private const val SHELL_PKG = "com.android.shell" 81 private const val USE_CAMERA = "use_camera" 82 private const val USE_MICROPHONE = "use_microphone" 83 private const val USE_HOTWORD = "use_hotword" 84 private const val FINISH_EARLY = "finish_early" 85 private const val USE_INTENT_ACTION = "test.action.USE_CAMERA_OR_MIC" 86 private const val PRIVACY_CHIP_ID = "com.android.systemui:id/privacy_chip" 87 private const val PRIVACY_ITEM_ID = "com.android.systemui:id/privacy_item" 88 private const val INDICATORS_FLAG = "camera_mic_icons_enabled" 89 private const val WEAR_MIC_LABEL = "Microphone" 90 private const val PERMISSION_INDICATORS_NOT_PRESENT = 162547999L 91 private const val IDLE_TIMEOUT_MILLIS: Long = 2000 92 private const val TIMEOUT_MILLIS: Long = 20000 93 private const val TV_MIC_INDICATOR_WINDOW_TITLE = "MicrophoneCaptureIndicator" 94 private const val MIC_LABEL_NAME = "microphone_toggle_label_qs" 95 private const val CAMERA_LABEL_NAME = "camera_toggle_label_qs" 96 private val HOTWORD_DETECTION_SERVICE_REQUIRED = 97 SystemProperties.getBoolean("ro.hotword.detection_service_required", false) 98 99 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S") 100 @ScreenRecordRule.ScreenRecord 101 @FlakyTest 102 class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase { 103 private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() 104 private val context: Context = instrumentation.context 105 private val uiAutomation: UiAutomation = instrumentation.uiAutomation 106 private val uiDevice: UiDevice = UiDevice.getInstance(instrumentation) 107 private val packageManager: PackageManager = context.packageManager 108 private val permissionManager: PermissionManager = 109 context.getSystemService(PermissionManager::class.java)!! 110 111 private val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) 112 private val isCar = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) 113 private val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH) 114 private val originalCameraLabel = 115 packageManager 116 .getPermissionGroupInfo(Manifest.permission_group.CAMERA, 0) 117 .loadLabel(packageManager) 118 .toString() 119 private val originalMicLabel = 120 packageManager 121 .getPermissionGroupInfo(Manifest.permission_group.MICROPHONE, 0) 122 .loadLabel(packageManager) 123 .toString() 124 private val cameraLabel = originalCameraLabel.lowercase() 125 private val micLabel = originalMicLabel.lowercase() 126 private var wasEnabled = false 127 private var isScreenOn = false 128 private var screenTimeoutBeforeTest: Long = 0L 129 private lateinit var carMicPrivacyChipId: String 130 private lateinit var carCameraPrivacyChipId: String 131 132 @get:Rule val disableAnimationRule = DisableAnimationRule() 133 134 @get:Rule val screenRecordRule = ScreenRecordRule(false, false) 135 136 constructor() : super() 137 138 companion object { 139 private const val AUTO_MIC_INDICATOR_DISMISSAL_TIMEOUT_MS = 30_000L 140 const val SAFETY_CENTER_ENABLED = "safety_center_is_enabled" 141 const val DELAY_MILLIS = 3000L 142 private val TAG = CameraMicIndicatorsPermissionTest::class.java.simpleName 143 } 144 145 private val safetyCenterEnabled = callWithShellPermissionIdentity { 146 DeviceConfig.getString( 147 DeviceConfig.NAMESPACE_PRIVACY, 148 SAFETY_CENTER_ENABLED, 149 false.toString() 150 )!! 151 } 152 153 private fun uninstall() { 154 val output = runShellCommand("pm uninstall $APP_PKG").trim() 155 assertEquals("Success", output) 156 } 157 158 private fun install() { 159 val output = runShellCommandOrThrow("pm install -g $APK_PATH").trim() 160 assertEquals("Success", output) 161 } 162 163 @Before 164 fun setUp() { 165 // Camera and Mic are not supported for secondary user visible as a background user. 166 assumeFalse(isAutomotiveWithVisibleBackgroundUser()) 167 runWithShellPermissionIdentity { 168 screenTimeoutBeforeTest = 169 Settings.System.getLong(context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT) 170 Settings.System.putLong( 171 context.contentResolver, 172 Settings.System.SCREEN_OFF_TIMEOUT, 173 1800000L 174 ) 175 } 176 177 if (!isScreenOn) { 178 uiDevice.wakeUp() 179 runShellCommand(instrumentation, "wm dismiss-keyguard") 180 Thread.sleep(DELAY_MILLIS) 181 isScreenOn = true 182 } 183 uiDevice.findObject(By.text("Close"))?.click() 184 wasEnabled = setIndicatorsEnabledStateIfNeeded(true) 185 // If the change Id is not present, then isChangeEnabled will return true. To bypass this, 186 // the change is set to "false" if present. 187 assumeFalse( 188 "feature not present on this device", 189 callWithShellPermissionIdentity { 190 CompatChanges.isChangeEnabled(PERMISSION_INDICATORS_NOT_PRESENT, Process.SYSTEM_UID) 191 } 192 ) 193 install() 194 } 195 196 private fun setIndicatorsEnabledStateIfNeeded(shouldBeEnabled: Boolean): Boolean { 197 var currentlyEnabled = false 198 runWithShellPermissionIdentity { 199 currentlyEnabled = 200 DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, INDICATORS_FLAG, true) 201 if (currentlyEnabled != shouldBeEnabled) { 202 DeviceConfig.setProperty( 203 DeviceConfig.NAMESPACE_PRIVACY, 204 INDICATORS_FLAG, 205 shouldBeEnabled.toString(), 206 false 207 ) 208 } 209 } 210 return currentlyEnabled 211 } 212 213 @After 214 fun tearDown() { 215 if (isAutomotiveWithVisibleBackgroundUser()) { 216 return 217 } 218 uninstall() 219 if (isCar) { 220 // Deselect the indicator since it persists otherwise 221 pressBack() 222 } 223 eventually( 224 { assertIndicatorsShown(false, false, false) }, 225 AUTO_MIC_INDICATOR_DISMISSAL_TIMEOUT_MS 226 ) 227 if (!wasEnabled) { 228 setIndicatorsEnabledStateIfNeeded(false) 229 } 230 runWithShellPermissionIdentity { 231 Settings.System.putLong( 232 context.contentResolver, 233 Settings.System.SCREEN_OFF_TIMEOUT, 234 screenTimeoutBeforeTest 235 ) 236 } 237 changeSafetyCenterFlag(safetyCenterEnabled) 238 if (!isTv) { 239 pressBack() 240 pressBack() 241 } 242 pressHome() 243 pressHome() 244 } 245 246 private fun openApp( 247 useMic: Boolean, 248 useCamera: Boolean, 249 useHotword: Boolean, 250 finishEarly: Boolean = false 251 ) { 252 context.startActivity( 253 Intent(USE_INTENT_ACTION).apply { 254 putExtra(USE_CAMERA, useCamera) 255 putExtra(USE_MICROPHONE, useMic) 256 putExtra(USE_HOTWORD, useHotword) 257 putExtra(FINISH_EARLY, finishEarly) 258 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 259 } 260 ) 261 } 262 263 @Test 264 @CddTest(requirement = "9.8.2/H-5-1,T-5-1,A-2-1") 265 fun testCameraIndicator() { 266 // If camera is not available skip the test 267 assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) 268 val manager = context.getSystemService(CameraManager::class.java)!! 269 assumeTrue(manager.cameraIdList.isNotEmpty()) 270 changeSafetyCenterFlag(false.toString()) 271 assumeSafetyCenterDisabled() 272 testCameraAndMicIndicator(useMic = false, useCamera = true) 273 } 274 275 @Test 276 @CddTest(requirement = "9.8.2/H-4-1,T-4-1,A-1-1") 277 fun testMicIndicator() { 278 changeSafetyCenterFlag(false.toString()) 279 assumeSafetyCenterDisabled() 280 testCameraAndMicIndicator(useMic = true, useCamera = false) 281 } 282 283 @Test 284 @AsbSecurityTest(cveBugId = [258672042]) 285 @MtsIgnore(bugId = 351903707) 286 fun testMicIndicatorWithManualFinishOpStillShows() { 287 testCameraAndMicIndicator( 288 useMic = true, 289 useCamera = false, 290 finishEarly = true, 291 safetyCenterEnabled = getSafetyCenterEnabled() 292 ) 293 } 294 295 @Test 296 @CddTest(requirement = "9.8.2/H-4-1,T-4-1,A-1-1") 297 fun testHotwordIndicatorBehavior() { 298 changeSafetyCenterFlag(false.toString()) 299 assumeSafetyCenterDisabled() 300 testCameraAndMicIndicator(useMic = false, useCamera = false, useHotword = true) 301 } 302 303 @Test 304 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") 305 fun testChainUsageWithOtherUsage() { 306 // TV has only the mic icon 307 assumeFalse(isTv) 308 // Car has separate panels for mic and camera for now. 309 // TODO(b/218788634): enable this test for car once the new camera indicator is implemented. 310 assumeFalse(isCar) 311 // If camera is not available skip the test 312 assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) 313 changeSafetyCenterFlag(false.toString()) 314 assumeSafetyCenterDisabled() 315 testCameraAndMicIndicator(useMic = false, useCamera = true, chainUsage = true) 316 } 317 318 @Test 319 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") 320 fun testSafetyCenterCameraIndicator() { 321 assumeFalse(isTv) 322 assumeFalse(isCar) 323 val manager = context.getSystemService(CameraManager::class.java)!! 324 assumeTrue(manager.cameraIdList.isNotEmpty()) 325 changeSafetyCenterFlag(true.toString()) 326 assumeSafetyCenterEnabled() 327 testCameraAndMicIndicator(useMic = false, useCamera = true, safetyCenterEnabled = true) 328 } 329 330 @Test 331 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") 332 fun testSafetyCenterMicIndicator() { 333 assumeFalse(isTv) 334 assumeFalse(isCar) 335 changeSafetyCenterFlag(true.toString()) 336 assumeSafetyCenterEnabled() 337 testCameraAndMicIndicator(useMic = true, useCamera = false, safetyCenterEnabled = true) 338 } 339 340 @Test 341 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") 342 fun testSafetyCenterHotwordIndicatorBehavior() { 343 assumeFalse(isTv) 344 assumeFalse(isCar) 345 assumeTrue(HOTWORD_DETECTION_SERVICE_REQUIRED) 346 changeSafetyCenterFlag(true.toString()) 347 assumeSafetyCenterEnabled() 348 testCameraAndMicIndicator( 349 useMic = false, 350 useCamera = false, 351 useHotword = true, 352 safetyCenterEnabled = true 353 ) 354 } 355 356 @Test 357 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu") 358 fun testSafetyCenterChainUsageWithOtherUsage() { 359 assumeFalse(isTv) 360 assumeFalse(isCar) 361 changeSafetyCenterFlag(true.toString()) 362 assumeSafetyCenterEnabled() 363 testCameraAndMicIndicator( 364 useMic = false, 365 useCamera = true, 366 chainUsage = true, 367 safetyCenterEnabled = true 368 ) 369 } 370 371 private fun testCameraAndMicIndicator( 372 useMic: Boolean, 373 useCamera: Boolean, 374 useHotword: Boolean = false, 375 chainUsage: Boolean = false, 376 safetyCenterEnabled: Boolean = false, 377 finishEarly: Boolean = false 378 ) { 379 Log.d( 380 TAG, 381 "testCameraAndMicIndicator useMic=$useMic useCamera=$useCamera " + 382 "safetyCenterEnabled=$safetyCenterEnabled finishEarly=$finishEarly" 383 ) 384 // If camera is not available skip the test 385 if (useCamera) { 386 assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) 387 } 388 var chainAttribution: AttributionSource? = null 389 openApp(useMic, useCamera, useHotword, finishEarly) 390 try { 391 eventually { 392 val appView = 393 if (isWatch) { 394 // Title is disabled by default on watch apps 395 uiDevice.findObject(UiSelector().packageName(APP_PKG)) 396 } else { 397 uiDevice.findObject(UiSelector().textContains(APP_LABEL)) 398 } 399 400 assertWithUiDump { 401 assertTrue("View with text $APP_LABEL not found", appView.exists()) 402 } 403 } 404 if (chainUsage) { 405 chainAttribution = createChainAttribution() 406 runWithShellPermissionIdentity { 407 val ret = 408 permissionManager.checkPermissionForStartDataDelivery( 409 Manifest.permission.RECORD_AUDIO, 410 chainAttribution!!, 411 "" 412 ) 413 assertEquals(PermissionManager.PERMISSION_GRANTED, ret) 414 } 415 } 416 417 Log.d(TAG, "assert to make sure Indicators are displayed") 418 assertIndicatorsShown(useMic, useCamera, useHotword, chainUsage, safetyCenterEnabled) 419 420 if (finishEarly) { 421 // Assert that the indicator doesn't go away 422 var failed = false 423 try { 424 // Check if the indicator has gone away. This will throw an exception if the 425 // indicator is still present 426 Log.d(TAG, "checking on indicators again") 427 assertIndicatorsShown(false, false, false) 428 Log.d(TAG, "indicators are gone") 429 // If we successfully asserted that the indicator went away, fail the test 430 failed = true 431 } catch (t: Throwable) { 432 Log.d(TAG, "indicators are continuing to show") 433 // expected 434 } 435 if (failed) { 436 assertWithUiDump { Assert.fail("Expected the indicator to remain present") } 437 } 438 } 439 } finally { 440 if (chainAttribution != null) { 441 runWithShellPermissionIdentity { 442 permissionManager.finishDataDelivery( 443 Manifest.permission.RECORD_AUDIO, 444 chainAttribution 445 ) 446 } 447 } 448 } 449 } 450 451 private fun assertIndicatorsShown( 452 useMic: Boolean, 453 useCamera: Boolean, 454 useHotword: Boolean = false, 455 chainUsage: Boolean = false, 456 safetyCenterEnabled: Boolean = false, 457 ) { 458 if (isTv) { 459 assertTvIndicatorsShown(useMic, useCamera, useHotword) 460 } else if (isCar) { 461 assertCarIndicatorsShown(useMic, useCamera, useHotword, chainUsage) 462 } else if (isWatch) { 463 assertWatchIndicatorsShown(useMic, useCamera, useHotword) 464 } else { 465 uiDevice.openQuickSettings() 466 val micInUse = 467 if (SdkLevel.isAtLeastU() && HOTWORD_DETECTION_SERVICE_REQUIRED) { 468 useMic || useHotword 469 } else { 470 useMic 471 } 472 if (!micInUse && !useCamera) { 473 // We're asserting the indicator is gone. Wait up to IDLE_TIMEOUT after the quick 474 // settings is opened to see if we find the indicator, so we don't automatically 475 // assert the indicator is gone, just because we didn't open quick settings fast 476 // enough. 477 UiAutomatorUtils2.waitFindObjectOrNull(By.res(PRIVACY_CHIP_ID), IDLE_TIMEOUT_MILLIS) 478 } 479 assertPrivacyChipAndIndicatorsPresent( 480 micInUse, 481 useCamera, 482 chainUsage, 483 safetyCenterEnabled 484 ) 485 uiDevice.pressBack() 486 } 487 } 488 489 private fun assertWatchIndicatorsShown( 490 useMic: Boolean, 491 useCamera: Boolean, 492 useHotword: Boolean 493 ) { 494 if (useMic || useHotword || (!useMic && !useCamera && !useHotword)) { 495 val iconView = UiAutomatorUtils2.waitFindObjectOrNull(By.descContains(WEAR_MIC_LABEL)) 496 if (useMic) { 497 assertNotNull("Did not find mic chip", iconView) 498 } else { 499 assertNull("Found mic chip, but did not expect to", iconView) 500 // waitFindObject leaves the watch on the notification screen 501 pressBack() 502 } 503 } 504 } 505 506 private fun assertTvIndicatorsShown(useMic: Boolean, useCamera: Boolean, useHotword: Boolean) { 507 if (useMic || useHotword || (!useMic && !useCamera && !useHotword)) { 508 eventually { 509 val found = 510 WindowManagerStateHelper().waitFor( 511 "Waiting for the mic indicator window to come up" 512 ) { 513 it.containsWindow(TV_MIC_INDICATOR_WINDOW_TITLE) && 514 it.isWindowVisible(TV_MIC_INDICATOR_WINDOW_TITLE) 515 } 516 if (useMic) { 517 assertTrue("Did not find chip", found) 518 } else { 519 assertFalse("Found chip, but did not expect to", found) 520 } 521 } 522 } 523 if (useCamera) { 524 // There is no camera indicator on TVs. 525 } 526 } 527 528 private fun assertCarIndicatorsShown( 529 useMic: Boolean, 530 useCamera: Boolean, 531 useHotword: Boolean, 532 chainUsage: Boolean 533 ) { 534 eventually { 535 // Ensure the privacy chip is present (or not) 536 carMicPrivacyChipId = context.getString(R.string.car_mic_privacy_chip_id) 537 carCameraPrivacyChipId = context.getString(R.string.car_camera_privacy_chip_id) 538 var micPrivacyChip = uiDevice.findObject(By.res(carMicPrivacyChipId)) 539 var cameraPrivacyChip = uiDevice.findObject(By.res(carCameraPrivacyChipId)) 540 if (useMic) { 541 assertNotNull("Did not find mic chip", micPrivacyChip) 542 // Click to chip to show the panel. 543 micPrivacyChip.click() 544 } else if (useCamera) { 545 assertNotNull("Did not find camera chip", cameraPrivacyChip) 546 // Click to chip to show the panel. 547 cameraPrivacyChip.click() 548 } else { 549 assertNull("Found mic chip, but did not expect to", micPrivacyChip) 550 assertNull("Found camera chip, but did not expect to", cameraPrivacyChip) 551 } 552 } 553 554 eventually { 555 if (chainUsage) { 556 // Not applicable for car 557 assertChainMicAndOtherCameraUsed(false) 558 return@eventually 559 } 560 if (useMic) { 561 // There should be a mic privacy panel after mic privacy chip is clicked 562 val micLabelView = uiDevice.findObject(UiSelector().textContains(micLabel)) 563 assertTrue("View with text $micLabel not found", micLabelView.exists()) 564 val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL)) 565 assertTrue("View with text $APP_LABEL not found", appView.exists()) 566 } else if (useCamera) { 567 // There should be a camera privacy panel after camera privacy chip is clicked 568 val cameraLabelView = uiDevice.findObject(UiSelector().textContains(cameraLabel)) 569 assertTrue("View with text $cameraLabel not found", cameraLabelView.exists()) 570 val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL)) 571 assertTrue("View with text $APP_LABEL not found", appView.exists()) 572 } else { 573 // There should be no privacy panel when using hot word 574 val micLabelView = uiDevice.findObject(UiSelector().textContains(micLabel)) 575 assertFalse( 576 "View with text $micLabel found, but did not expect to", 577 micLabelView.exists() 578 ) 579 val cameraLabelView = uiDevice.findObject(UiSelector().textContains(cameraLabel)) 580 assertFalse( 581 "View with text $cameraLabel found, but did not expect to", 582 cameraLabelView.exists() 583 ) 584 val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL)) 585 assertFalse( 586 "View with text $APP_LABEL found, but did not expect to", 587 appView.exists() 588 ) 589 } 590 } 591 } 592 593 private fun assertPrivacyChipAndIndicatorsPresent( 594 useMic: Boolean, 595 useCamera: Boolean, 596 chainUsage: Boolean, 597 safetyCenterEnabled: Boolean = false 598 ) { 599 // Ensure the privacy chip is present 600 if (useCamera || useMic) { 601 eventually { 602 val privacyChip = UiAutomatorUtils2.waitFindObjectOrNull(By.res(PRIVACY_CHIP_ID)) 603 assertWithUiDump { 604 assertNotNull("view with id $PRIVACY_CHIP_ID not found", privacyChip) 605 } 606 privacyChip.click() 607 } 608 } else { 609 Log.d(TAG, "waiting for PRIVACY_CHIP_ID to disappear") 610 assertWithUiDump { UiAutomatorUtils2.waitUntilObjectGone(By.res(PRIVACY_CHIP_ID)) } 611 return 612 } 613 614 eventually { 615 if (chainUsage) { 616 assertChainMicAndOtherCameraUsed(safetyCenterEnabled) 617 return@eventually 618 } 619 if (useMic) { 620 if (safetyCenterEnabled) { 621 assertSafetyCenterMicViewNotNull() 622 } else { 623 val iconView = waitFindObject(By.descContains(micLabel)) 624 assertWithUiDump { 625 assertNotNull("View with description '$micLabel' not found", iconView) 626 } 627 } 628 } 629 if (useCamera) { 630 if (safetyCenterEnabled) { 631 assertSafetyCenterCameraViewNotNull() 632 } else { 633 val iconView = waitFindObject(By.descContains(cameraLabel)) 634 assertWithUiDump { 635 assertNotNull("View with description '$cameraLabel' not found", iconView) 636 } 637 } 638 } 639 var appView = waitFindObject(By.textContains(APP_LABEL)) 640 assertWithUiDump { assertNotNull("View with text $APP_LABEL not found", appView) } 641 } 642 uiDevice.pressBack() 643 } 644 645 private fun createChainAttribution(): AttributionSource? { 646 var attrSource: AttributionSource? = null 647 runWithShellPermissionIdentity { 648 try { 649 val appUid = packageManager.getPackageUid(APP_PKG, 0) 650 val childAttribution = AttributionSource(appUid, APP_PKG, null) 651 val attribution = 652 AttributionSource( 653 Process.myUid(), 654 context.packageName, 655 null, 656 null, 657 permissionManager.registerAttributionSource(childAttribution) 658 ) 659 attrSource = permissionManager.registerAttributionSource(attribution) 660 } catch (e: PackageManager.NameNotFoundException) { 661 Assert.fail("Expected to find a UID for $APP_LABEL") 662 } 663 } 664 return attrSource 665 } 666 667 private fun assertChainMicAndOtherCameraUsed(safetyCenterEnabled: Boolean) { 668 val shellLabel = 669 try { 670 context.packageManager 671 .getApplicationInfo(SHELL_PKG, 0) 672 .loadLabel(context.packageManager) 673 .toString() 674 } catch (e: PackageManager.NameNotFoundException) { 675 "Did not find shell package" 676 } 677 678 if (safetyCenterEnabled) { 679 assertSafetyCenterMicViewNotNull() 680 assertSafetyCenterCameraViewNotNull() 681 var shellView = waitFindObject(By.textContains(shellLabel)) 682 assertNotNull("View with text $shellLabel not found", shellView) 683 } else { 684 val usageViews = uiDevice.findObjects(By.res(PRIVACY_ITEM_ID)) 685 assertEquals("Expected two usage views", 2, usageViews.size) 686 val appViews = uiDevice.findObjects(By.textContains(APP_LABEL)) 687 assertEquals("Expected two $APP_LABEL view", 2, appViews.size) 688 val shellView = uiDevice.findObjects(By.textContains(shellLabel)) 689 assertEquals("Expected only one shell view", 1, shellView.size) 690 } 691 } 692 693 private fun pressBack() { 694 uiDevice.pressBack() 695 } 696 697 private fun pressHome() { 698 uiDevice.pressHome() 699 } 700 701 private fun changeSafetyCenterFlag(safetyCenterEnabled: String) { 702 runWithShellPermissionIdentity { 703 DeviceConfig.setProperty( 704 DeviceConfig.NAMESPACE_PRIVACY, 705 SAFETY_CENTER_ENABLED, 706 safetyCenterEnabled, 707 false 708 ) 709 } 710 } 711 712 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 713 private fun assumeSafetyCenterEnabled() { 714 assumeTrue(getSafetyCenterEnabled()) 715 } 716 717 private fun assumeSafetyCenterDisabled() { 718 assumeFalse(getSafetyCenterEnabled()) 719 } 720 721 private fun getSafetyCenterEnabled(): Boolean { 722 val safetyCenterManager = 723 context.getSystemService(SafetyCenterManager::class.java) ?: return false 724 return runWithShellPermissionIdentity<Boolean> { safetyCenterManager.isSafetyCenterEnabled } 725 } 726 727 protected fun waitFindObject(selector: BySelector): UiObject2? { 728 return findObjectWithRetry({ t -> UiAutomatorUtils2.waitFindObject(selector, t) }) 729 } 730 731 private fun findObjectWithRetry( 732 automatorMethod: (timeoutMillis: Long) -> UiObject2?, 733 timeoutMillis: Long = TIMEOUT_MILLIS 734 ): UiObject2? { 735 val startTime = SystemClock.elapsedRealtime() 736 return try { 737 automatorMethod(timeoutMillis) 738 } catch (e: StaleObjectException) { 739 val remainingTime = timeoutMillis - (SystemClock.elapsedRealtime() - startTime) 740 if (remainingTime <= 0) { 741 throw e 742 } 743 automatorMethod(remainingTime) 744 } 745 } 746 747 private fun getPermissionControllerString(resourceName: String): String { 748 val permissionControllerPkg = context.packageManager.permissionControllerPackageName 749 try { 750 val permissionControllerContext = 751 context.createPackageContext(permissionControllerPkg, 0) 752 val resourceId = 753 permissionControllerContext.resources.getIdentifier( 754 resourceName, 755 "string", 756 "com.android.permissioncontroller" 757 ) 758 return permissionControllerContext.getString(resourceId) 759 } catch (e: PackageManager.NameNotFoundException) { 760 throw RuntimeException(e) 761 } 762 } 763 764 private fun assertSafetyCenterMicViewNotNull() { 765 val safetyCenterMicLabel = getPermissionControllerString(MIC_LABEL_NAME) 766 val micView = waitFindObject(byOneOfText(originalMicLabel, safetyCenterMicLabel)) 767 assertNotNull( 768 "View with text '$originalMicLabel' or '$safetyCenterMicLabel' not found", 769 micView 770 ) 771 } 772 773 private fun assertSafetyCenterCameraViewNotNull() { 774 val safetyCenterCameraLabel = getPermissionControllerString(CAMERA_LABEL_NAME) 775 val cameraView = waitFindObject(byOneOfText(originalCameraLabel, safetyCenterCameraLabel)) 776 assertNotNull( 777 "View with text '$originalCameraLabel' or '$safetyCenterCameraLabel' not found", 778 cameraView 779 ) 780 } 781 782 private fun byOneOfText(vararg textValues: String) = 783 By.text(Pattern.compile(textValues.joinToString(separator = "|") { Pattern.quote(it) })) 784 785 fun isAutomotiveWithVisibleBackgroundUser(): Boolean { 786 val userManager = context.getSystemService(UserManager::class.java) 787 return packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) && 788 userManager.isVisibleBackgroundUsersSupported() 789 } 790 } 791