1 /* <lambda>null2 * Copyright (C) 2016 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 android.permission3.cts 18 19 import android.Manifest 20 import android.app.Activity 21 import android.app.Instrumentation 22 import android.content.ComponentName 23 import android.content.Intent 24 import android.content.Intent.ACTION_REVIEW_APP_DATA_SHARING_UPDATES 25 import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK 26 import android.content.Intent.FLAG_ACTIVITY_NEW_TASK 27 import android.content.pm.PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE 28 import android.content.pm.PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE 29 import android.content.pm.PackageInstaller.PACKAGE_SOURCE_OTHER 30 import android.content.pm.PackageInstaller.PACKAGE_SOURCE_STORE 31 import android.content.pm.PackageManager 32 import android.net.Uri 33 import android.os.Build 34 import android.os.Process 35 import android.provider.DeviceConfig 36 import android.provider.Settings 37 import android.text.Spanned 38 import android.text.style.ClickableSpan 39 import android.view.View 40 import androidx.test.uiautomator.By 41 import androidx.test.uiautomator.BySelector 42 import androidx.test.uiautomator.StaleObjectException 43 import androidx.test.uiautomator.UiObjectNotFoundException 44 import androidx.test.uiautomator.UiScrollable 45 import androidx.test.uiautomator.UiSelector 46 import com.android.compatibility.common.util.SystemUtil 47 import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity 48 import com.android.compatibility.common.util.SystemUtil.eventually 49 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity 50 import com.android.modules.utils.build.SdkLevel 51 import java.util.concurrent.TimeUnit 52 import java.util.regex.Pattern 53 import org.junit.After 54 import org.junit.Assert 55 import org.junit.Assert.assertEquals 56 import org.junit.Assert.assertNotNull 57 import org.junit.Assert.assertTrue 58 import org.junit.Before 59 60 abstract class BaseUsePermissionTest : BasePermissionTest() { 61 companion object { 62 const val APP_APK_NAME_31 = "CtsUsePermissionApp31.apk" 63 64 const val APP_APK_PATH_22 = "$APK_DIRECTORY/CtsUsePermissionApp22.apk" 65 const val APP_APK_PATH_22_CALENDAR_ONLY = 66 "$APK_DIRECTORY/CtsUsePermissionApp22CalendarOnly.apk" 67 const val APP_APK_PATH_22_NONE = "$APK_DIRECTORY/CtsUsePermissionApp22None.apk" 68 const val APP_APK_PATH_23 = "$APK_DIRECTORY/CtsUsePermissionApp23.apk" 69 const val APP_APK_PATH_25 = "$APK_DIRECTORY/CtsUsePermissionApp25.apk" 70 const val APP_APK_PATH_26 = "$APK_DIRECTORY/CtsUsePermissionApp26.apk" 71 const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk" 72 const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk" 73 const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk" 74 const val APP_APK_PATH_31 = "$APK_DIRECTORY/$APP_APK_NAME_31" 75 const val APP_APK_PATH_32 = "$APK_DIRECTORY/CtsUsePermissionApp32.apk" 76 77 const val APP_APK_PATH_30_WITH_BACKGROUND = 78 "$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk" 79 const val APP_APK_PATH_30_WITH_BLUETOOTH = 80 "$APK_DIRECTORY/CtsUsePermissionApp30WithBluetooth.apk" 81 const val APP_APK_PATH_LATEST = "$APK_DIRECTORY/CtsUsePermissionAppLatest.apk" 82 const val APP_APK_PATH_LATEST_NONE = "$APK_DIRECTORY/CtsUsePermissionAppLatestNone.apk" 83 const val APP_APK_PATH_WITH_OVERLAY = "$APK_DIRECTORY/CtsUsePermissionAppWithOverlay.apk" 84 const val APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31 = 85 "$APK_DIRECTORY/CtsCreateNotificationChannelsApp31.apk" 86 const val APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_33 = 87 "$APK_DIRECTORY/CtsCreateNotificationChannelsApp33.apk" 88 const val APP_APK_PATH_MEDIA_PERMISSION_33_WITH_STORAGE = 89 "$APK_DIRECTORY/CtsMediaPermissionApp33WithStorage.apk" 90 const val APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE = 91 "$APK_DIRECTORY/CtsUsePermissionAppImplicitUserSelectStorage.apk" 92 const val APP_APK_PATH_OTHER_APP = 93 "$APK_DIRECTORY/CtsDifferentPkgNameApp.apk" 94 const val APP_PACKAGE_NAME = "android.permission3.cts.usepermission" 95 const val OTHER_APP_PACKAGE_NAME = "android.permission3.cts.usepermissionother" 96 const val TEST_INSTALLER_PACKAGE_NAME = "android.permission3.cts" 97 98 const val ALLOW_ALL_BUTTON = 99 "com.android.permissioncontroller:id/permission_allow_all_button" 100 const val SELECT_BUTTON = 101 "com.android.permissioncontroller:id/permission_allow_selected_button" 102 const val DONT_SELECT_MORE_BUTTON = 103 "com.android.permissioncontroller:id/permission_dont_allow_more_selected_button" 104 const val ALLOW_BUTTON = 105 "com.android.permissioncontroller:id/permission_allow_button" 106 const val ALLOW_FOREGROUND_BUTTON = 107 "com.android.permissioncontroller:id/permission_allow_foreground_only_button" 108 const val DENY_BUTTON = "com.android.permissioncontroller:id/permission_deny_button" 109 const val DENY_AND_DONT_ASK_AGAIN_BUTTON = 110 "com.android.permissioncontroller:id/permission_deny_and_dont_ask_again_button" 111 const val NO_UPGRADE_BUTTON = 112 "com.android.permissioncontroller:id/permission_no_upgrade_button" 113 const val NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON = 114 "com.android.permissioncontroller:" + 115 "id/permission_no_upgrade_and_dont_ask_again_button" 116 117 const val ALLOW_ALWAYS_RADIO_BUTTON = 118 "com.android.permissioncontroller:id/allow_always_radio_button" 119 const val ALLOW_RADIO_BUTTON = "com.android.permissioncontroller:id/allow_radio_button" 120 const val ALLOW_FOREGROUND_RADIO_BUTTON = 121 "com.android.permissioncontroller:id/allow_foreground_only_radio_button" 122 const val ASK_RADIO_BUTTON = "com.android.permissioncontroller:id/ask_radio_button" 123 const val DENY_RADIO_BUTTON = "com.android.permissioncontroller:id/deny_radio_button" 124 const val SELECT_RADIO_BUTTON = 125 "com.android.permissioncontroller:id/select_radio_button" 126 127 const val NOTIF_TEXT = "permgrouprequest_notifications" 128 const val ALLOW_BUTTON_TEXT = "grant_dialog_button_allow" 129 const val ALLOW_ALL_FILES_BUTTON_TEXT = "app_permission_button_allow_all_files" 130 const val ALLOW_FOREGROUND_BUTTON_TEXT = "grant_dialog_button_allow_foreground" 131 const val ALLOW_FOREGROUND_PREFERENCE_TEXT = "permission_access_only_foreground" 132 const val ASK_BUTTON_TEXT = "app_permission_button_ask" 133 const val ALLOW_ONE_TIME_BUTTON_TEXT = "grant_dialog_button_allow_one_time" 134 const val DENY_BUTTON_TEXT = "grant_dialog_button_deny" 135 const val DENY_ANYWAY_BUTTON_TEXT = "grant_dialog_button_deny_anyway" 136 const val DENY_AND_DONT_ASK_AGAIN_BUTTON_TEXT = 137 "grant_dialog_button_deny_and_dont_ask_again" 138 const val NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON_TEXT = "grant_dialog_button_no_upgrade" 139 const val ALERT_DIALOG_MESSAGE = "android:id/message" 140 const val ALERT_DIALOG_OK_BUTTON = "android:id/button1" 141 const val APP_PERMISSION_RATIONALE_CONTAINER_VIEW = 142 "com.android.permissioncontroller:id/app_permission_rationale_container" 143 const val APP_PERMISSION_RATIONALE_CONTENT_VIEW = 144 "com.android.permissioncontroller:id/app_permission_rationale_content" 145 const val GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW = 146 "com.android.permissioncontroller:id/permission_rationale_container" 147 const val PERMISSION_RATIONALE_ACTIVITY_TITLE_VIEW = 148 "com.android.permissioncontroller:id/permission_rationale_title" 149 const val DATA_SHARING_SOURCE_TITLE_ID = 150 "com.android.permissioncontroller:id/data_sharing_source_title" 151 const val DATA_SHARING_SOURCE_MESSAGE_ID = 152 "com.android.permissioncontroller:id/data_sharing_source_message" 153 const val PURPOSE_TITLE_ID = "com.android.permissioncontroller:id/purpose_title" 154 const val PURPOSE_MESSAGE_ID = "com.android.permissioncontroller:id/purpose_message" 155 const val LEARN_MORE_TITLE_ID = "com.android.permissioncontroller:id/learn_more_title" 156 const val LEARN_MORE_MESSAGE_ID = "com.android.permissioncontroller:id/learn_more_message" 157 const val PERMISSION_RATIONALE_SETTINGS_SECTION = 158 "com.android.permissioncontroller:id/settings_section" 159 const val SETTINGS_TITLE_ID = 160 "com.android.permissioncontroller:id/settings_title" 161 const val SETTINGS_MESSAGE_ID = 162 "com.android.permissioncontroller:id/settings_message" 163 164 const val REQUEST_LOCATION_MESSAGE = "permgrouprequest_location" 165 166 const val DATA_SHARING_UPDATES = "Data sharing updates for location" 167 const val DATA_SHARING_UPDATES_SUBTITLE = 168 "These apps have changed the way they may share your location data. They may not" + 169 " have shared it before, or may now share it for advertising or marketing" + 170 " purposes." 171 const val DATA_SHARING_NO_UPDATES_MESSAGE = "No updates at this time" 172 const val UPDATES_IN_LAST_30_DAYS = "Updated within 30 days" 173 const val DATA_SHARING_UPDATES_FOOTER_MESSAGE = 174 "The developers of these apps provided info about their data sharing practices" + 175 " to an app store. They may update it over time.\n\nData sharing" + 176 " practices may vary based on your app version, use, region, and age." 177 const val LEARN_ABOUT_DATA_SHARING = "Learn about data sharing" 178 const val LOCATION_PERMISSION = "Location permission" 179 const val APP_PACKAGE_NAME_SUBSTRING = "android.permission3" 180 const val NOW_SHARED_WITH_THIRD_PARTIES = "Your location data is now shared with third " + 181 "parties" 182 const val NOW_SHARED_WITH_THIRD_PARTIES_FOR_ADS = "Your location data is now shared with " + 183 "third parties for advertising or marketing" 184 const val PROPERTY_DATA_SHARING_UPDATE_PERIOD_MILLIS = 185 "data_sharing_update_period_millis" 186 const val PROPERTY_MAX_SAFETY_LABELS_PERSISTED_PER_APP = 187 "max_safety_labels_persisted_per_app" 188 189 // The highest SDK for which the system will show a "low SDK" warning when launching the app 190 const val MAX_SDK_FOR_SDK_WARNING = 27 191 192 val TEST_INSTALLER_ACTIVITY_COMPONENT_NAME = 193 ComponentName(context, TestInstallerActivity::class.java) 194 195 val MEDIA_PERMISSIONS: Set<String> = mutableSetOf( 196 Manifest.permission.ACCESS_MEDIA_LOCATION, 197 Manifest.permission.READ_MEDIA_AUDIO, 198 Manifest.permission.READ_MEDIA_IMAGES, 199 Manifest.permission.READ_MEDIA_VIDEO, 200 ).apply { 201 if (SdkLevel.isAtLeastU()) { 202 add(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED) 203 } 204 }.toSet() 205 206 val STORAGE_AND_MEDIA_PERMISSIONS = MEDIA_PERMISSIONS 207 .plus(Manifest.permission.READ_EXTERNAL_STORAGE) 208 .plus(Manifest.permission.WRITE_EXTERNAL_STORAGE) 209 210 @JvmStatic 211 protected val PICKER_ENABLED_SETTING = "photo_picker_prompt_enabled" 212 213 @JvmStatic 214 protected fun isPhotoPickerPermissionPromptEnabled(): Boolean { 215 return SdkLevel.isAtLeastU() && 216 !isTv && !isAutomotive && !isWatch && 217 callWithShellPermissionIdentity { 218 DeviceConfig.getBoolean( 219 DeviceConfig.NAMESPACE_PRIVACY, PICKER_ENABLED_SETTING, true) 220 } 221 } 222 } 223 224 enum class PermissionState { 225 ALLOWED, 226 DENIED, 227 DENIED_WITH_PREJUDICE 228 } 229 230 private val platformResources = context.createPackageContext("android", 0).resources 231 private val permissionToLabelResNameMap = mapOf( 232 // Contacts 233 android.Manifest.permission.READ_CONTACTS 234 to "@android:string/permgrouplab_contacts", 235 android.Manifest.permission.WRITE_CONTACTS 236 to "@android:string/permgrouplab_contacts", 237 // Calendar 238 android.Manifest.permission.READ_CALENDAR 239 to "@android:string/permgrouplab_calendar", 240 android.Manifest.permission.WRITE_CALENDAR 241 to "@android:string/permgrouplab_calendar", 242 // SMS 243 android.Manifest.permission.SEND_SMS to "@android:string/permgrouplab_sms", 244 android.Manifest.permission.RECEIVE_SMS to "@android:string/permgrouplab_sms", 245 android.Manifest.permission.READ_SMS to "@android:string/permgrouplab_sms", 246 android.Manifest.permission.RECEIVE_WAP_PUSH to "@android:string/permgrouplab_sms", 247 android.Manifest.permission.RECEIVE_MMS to "@android:string/permgrouplab_sms", 248 "android.permission.READ_CELL_BROADCASTS" to "@android:string/permgrouplab_sms", 249 // Storage 250 android.Manifest.permission.READ_EXTERNAL_STORAGE 251 to "@android:string/permgrouplab_storage", 252 android.Manifest.permission.WRITE_EXTERNAL_STORAGE 253 to "@android:string/permgrouplab_storage", 254 // Location 255 android.Manifest.permission.ACCESS_FINE_LOCATION 256 to "@android:string/permgrouplab_location", 257 android.Manifest.permission.ACCESS_COARSE_LOCATION 258 to "@android:string/permgrouplab_location", 259 android.Manifest.permission.ACCESS_BACKGROUND_LOCATION 260 to "@android:string/permgrouplab_location", 261 // Phone 262 android.Manifest.permission.READ_PHONE_STATE 263 to "@android:string/permgrouplab_phone", 264 android.Manifest.permission.CALL_PHONE to "@android:string/permgrouplab_phone", 265 "android.permission.ACCESS_IMS_CALL_SERVICE" 266 to "@android:string/permgrouplab_phone", 267 android.Manifest.permission.READ_CALL_LOG to "@android:string/permgrouplab_phone", 268 android.Manifest.permission.WRITE_CALL_LOG to "@android:string/permgrouplab_phone", 269 android.Manifest.permission.ADD_VOICEMAIL to "@android:string/permgrouplab_phone", 270 android.Manifest.permission.USE_SIP to "@android:string/permgrouplab_phone", 271 android.Manifest.permission.PROCESS_OUTGOING_CALLS 272 to "@android:string/permgrouplab_phone", 273 // Microphone 274 android.Manifest.permission.RECORD_AUDIO 275 to "@android:string/permgrouplab_microphone", 276 // Camera 277 android.Manifest.permission.CAMERA to "@android:string/permgrouplab_camera", 278 // Body sensors 279 android.Manifest.permission.BODY_SENSORS to "@android:string/permgrouplab_sensors", 280 android.Manifest.permission.BODY_SENSORS_BACKGROUND 281 to "@android:string/permgrouplab_sensors", 282 // Bluetooth 283 android.Manifest.permission.BLUETOOTH_CONNECT to 284 "@android:string/permgrouplab_nearby_devices", 285 android.Manifest.permission.BLUETOOTH_SCAN to 286 "@android:string/permgrouplab_nearby_devices", 287 // Aural 288 android.Manifest.permission.READ_MEDIA_AUDIO to 289 "@android:string/permgrouplab_readMediaAural", 290 // Visual 291 android.Manifest.permission.READ_MEDIA_IMAGES to 292 "@android:string/permgrouplab_readMediaVisual", 293 android.Manifest.permission.READ_MEDIA_VIDEO to 294 "@android:string/permgrouplab_readMediaVisual" 295 ) 296 297 @Before 298 @After 299 fun uninstallApp() { 300 uninstallPackage(APP_PACKAGE_NAME, requireSuccess = false) 301 } 302 303 protected fun clearTargetSdkWarning(timeoutMillis: Long = TIMEOUT_MILLIS) { 304 waitForIdle() 305 waitFindObjectOrNull(By.res("android:id/button1"), timeoutMillis)?.let { 306 try { 307 it.click() 308 } catch (e: StaleObjectException) { 309 // Click sometimes fails with StaleObjectException (b/280430717). 310 e.printStackTrace() 311 } 312 waitForIdle() 313 } 314 } 315 316 protected fun clickPermissionReviewContinue() { 317 if (isAutomotive || isWatch) { 318 click(By.text(getPermissionControllerString("review_button_continue"))) 319 } else { 320 click(By.res("com.android.permissioncontroller:id/continue_button")) 321 } 322 } 323 324 protected fun installPackageWithInstallSourceAndEmptyMetadata( 325 apkName: String 326 ) { 327 installPackageViaSession(apkName, AppMetadata.createEmptyAppMetadata()) 328 } 329 330 protected fun installPackageWithInstallSourceAndMetadata( 331 apkName: String 332 ) { 333 installPackageViaSession(apkName, AppMetadata.createDefaultAppMetadata()) 334 } 335 336 protected fun installPackageWithInstallSourceAndMetadataFromStore( 337 apkName: String 338 ) { 339 installPackageViaSession(apkName, AppMetadata.createDefaultAppMetadata(), 340 PACKAGE_SOURCE_STORE) 341 } 342 343 protected fun installPackageWithInstallSourceAndMetadataFromLocalFile( 344 apkName: String 345 ) { 346 installPackageViaSession(apkName, AppMetadata.createDefaultAppMetadata(), 347 PACKAGE_SOURCE_LOCAL_FILE) 348 } 349 350 protected fun installPackageWithInstallSourceAndMetadataFromDownloadedFile( 351 apkName: String 352 ) { 353 installPackageViaSession(apkName, AppMetadata.createDefaultAppMetadata(), 354 PACKAGE_SOURCE_DOWNLOADED_FILE) 355 } 356 357 protected fun installPackageWithInstallSourceAndMetadataFromOther( 358 apkName: String 359 ) { 360 installPackageViaSession(apkName, AppMetadata.createDefaultAppMetadata(), 361 PACKAGE_SOURCE_OTHER) 362 } 363 364 protected fun installPackageWithInstallSourceAndNoMetadata( 365 apkName: String 366 ) { 367 installPackageViaSession(apkName) 368 } 369 370 protected fun installPackageWithInstallSourceAndInvalidMetadata( 371 apkName: String 372 ) { 373 installPackageViaSession(apkName, AppMetadata.createInvalidAppMetadata()) 374 } 375 376 protected fun installPackageWithInstallSourceAndMetadataWithoutTopLevelVersion( 377 apkName: String 378 ) { 379 installPackageViaSession( 380 apkName, AppMetadata.createInvalidAppMetadataWithoutTopLevelVersion()) 381 } 382 383 protected fun installPackageWithInstallSourceAndMetadataWithInvalidTopLevelVersion( 384 apkName: String 385 ) { 386 installPackageViaSession( 387 apkName, AppMetadata.createInvalidAppMetadataWithInvalidTopLevelVersion()) 388 } 389 390 protected fun installPackageWithInstallSourceAndMetadataWithoutSafetyLabelVersion( 391 apkName: String 392 ) { 393 installPackageViaSession( 394 apkName, AppMetadata.createInvalidAppMetadataWithoutSafetyLabelVersion()) 395 } 396 397 protected fun installPackageWithInstallSourceAndMetadataWithInvalidSafetyLabelVersion( 398 apkName: String 399 ) { 400 installPackageViaSession( 401 apkName, AppMetadata.createInvalidAppMetadataWithInvalidSafetyLabelVersion()) 402 } 403 404 protected fun installPackageWithoutInstallSource( 405 apkName: String 406 ) { 407 // TODO(b/257293222): Update/remove when hooking up PackageManager APIs 408 installPackage(apkName) 409 } 410 411 protected fun assertPermissionRationaleActivityTitleIsVisible(expected: Boolean) { 412 findView(By.res(PERMISSION_RATIONALE_ACTIVITY_TITLE_VIEW), expected = expected) 413 } 414 415 protected fun assertPermissionRationaleActivityDataSharingSourceSectionVisible( 416 expected: Boolean 417 ) { 418 findView(By.res(DATA_SHARING_SOURCE_TITLE_ID), expected = expected) 419 findView(By.res(DATA_SHARING_SOURCE_MESSAGE_ID), expected = expected) 420 } 421 422 protected fun assertPermissionRationaleActivityPurposeSectionVisible(expected: Boolean) { 423 findView(By.res(PURPOSE_TITLE_ID), expected = expected) 424 findView(By.res(PURPOSE_MESSAGE_ID), expected = expected) 425 } 426 427 protected fun assertPermissionRationaleActivityLearnMoreSectionVisible(expected: Boolean) { 428 findView(By.res(LEARN_MORE_TITLE_ID), expected = expected) 429 findView(By.res(LEARN_MORE_MESSAGE_ID), expected = expected) 430 } 431 432 protected fun assertPermissionRationaleActivitySettingsSectionVisible(expected: Boolean) { 433 findView(By.res(PERMISSION_RATIONALE_SETTINGS_SECTION), expected = expected) 434 findView(By.res(SETTINGS_TITLE_ID), expected = expected) 435 findView(By.res(SETTINGS_MESSAGE_ID), expected = expected) 436 } 437 438 protected fun assertPermissionRationaleDialogIsVisible( 439 expected: Boolean, 440 showSettingsSection: Boolean = true 441 ) { 442 assertPermissionRationaleActivityTitleIsVisible(expected) 443 assertPermissionRationaleActivityDataSharingSourceSectionVisible(expected) 444 assertPermissionRationaleActivityPurposeSectionVisible(expected) 445 assertPermissionRationaleActivityLearnMoreSectionVisible(expected) 446 if (expected) { 447 assertPermissionRationaleActivitySettingsSectionVisible(showSettingsSection) 448 } 449 } 450 451 protected fun assertPermissionRationaleContainerOnGrantDialogIsVisible(expected: Boolean) { 452 findView(By.res(GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW), expected = expected) 453 } 454 455 protected fun clickPermissionReviewCancel() { 456 if (isAutomotive || isWatch) { 457 click(By.text(getPermissionControllerString("review_button_cancel"))) 458 } else { 459 click(By.res("com.android.permissioncontroller:id/cancel_button")) 460 } 461 } 462 463 protected fun approvePermissionReview() { 464 startAppActivityAndAssertResultCode(Activity.RESULT_OK) { 465 clickPermissionReviewContinue() 466 waitForIdle() 467 clearTargetSdkWarning() 468 } 469 } 470 471 protected fun cancelPermissionReview() { 472 startAppActivityAndAssertResultCode(Activity.RESULT_CANCELED) { 473 clickPermissionReviewCancel() 474 } 475 } 476 477 protected fun assertAppDoesNotNeedPermissionReview() { 478 startAppActivityAndAssertResultCode(Activity.RESULT_OK) {} 479 } 480 481 protected inline fun startAppActivityAndAssertResultCode( 482 expectedResultCode: Int, 483 block: () -> Unit 484 ) { 485 val future = startActivityForFuture( 486 Intent().apply { 487 component = ComponentName( 488 APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.FinishOnCreateActivity" 489 ) 490 } 491 ) 492 block() 493 assertEquals( 494 expectedResultCode, future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS).resultCode 495 ) 496 } 497 498 protected inline fun requestAppPermissionsForNoResult( 499 vararg permissions: String?, 500 block: () -> Unit 501 ) { 502 // Request the permissions 503 context.startActivity( 504 Intent().apply { 505 component = ComponentName( 506 APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.RequestPermissionsActivity" 507 ) 508 putExtra("$APP_PACKAGE_NAME.PERMISSIONS", permissions) 509 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 510 } 511 ) 512 waitForIdle() 513 // Perform the post-request action 514 block() 515 } 516 517 protected inline fun requestAppPermissions( 518 vararg permissions: String?, 519 block: () -> Unit 520 ): Instrumentation.ActivityResult { 521 // Request the permissions 522 val future = startActivityForFuture( 523 Intent().apply { 524 component = ComponentName( 525 APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.RequestPermissionsActivity" 526 ) 527 putExtra("$APP_PACKAGE_NAME.PERMISSIONS", permissions) 528 } 529 ) 530 waitForIdle() 531 532 // Clear the low target SDK warning message if it's expected 533 if (getTargetSdk() <= MAX_SDK_FOR_SDK_WARNING) { 534 clearTargetSdkWarning(timeoutMillis = QUICK_CHECK_TIMEOUT_MILLIS) 535 waitForIdle() 536 } 537 538 // Notification permission prompt is shown first, so get it out of the way 539 clickNotificationPermissionRequestAllowButtonIfAvailable() 540 // Perform the post-request action 541 block() 542 return future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) 543 } 544 545 protected inline fun requestAppPermissionsAndAssertResult( 546 permissions: Array<out String?>, 547 permissionAndExpectedGrantResults: Array<out Pair<String?, Boolean>>, 548 block: () -> Unit 549 ) { 550 val result = requestAppPermissions(*permissions, block = block) 551 assertEquals(Activity.RESULT_OK, result.resultCode) 552 assertEquals( 553 result.resultData!!.getStringArrayExtra("$APP_PACKAGE_NAME.PERMISSIONS")!!.size, 554 result.resultData!!.getIntArrayExtra("$APP_PACKAGE_NAME.GRANT_RESULTS")!!.size 555 ) 556 557 assertEquals( 558 permissionAndExpectedGrantResults.toList(), 559 result.resultData!!.getStringArrayExtra("$APP_PACKAGE_NAME.PERMISSIONS")!! 560 .zip( 561 result.resultData!!.getIntArrayExtra("$APP_PACKAGE_NAME.GRANT_RESULTS")!! 562 .map { it == PackageManager.PERMISSION_GRANTED } 563 ) 564 ) 565 permissionAndExpectedGrantResults.forEach { 566 it.first?.let { permission -> 567 assertAppHasPermission(permission, it.second) 568 } 569 } 570 } 571 572 protected inline fun requestAppPermissionsAndAssertResult( 573 vararg permissionAndExpectedGrantResults: Pair<String?, Boolean>, 574 block: () -> Unit 575 ) = requestAppPermissionsAndAssertResult( 576 permissionAndExpectedGrantResults.map { it.first }.toTypedArray(), 577 permissionAndExpectedGrantResults, 578 block 579 ) 580 581 protected fun clickPermissionRequestAllowButton(timeoutMillis: Long = 20000) { 582 if (isAutomotive) { 583 click(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)), timeoutMillis) 584 } else { 585 click(By.res(ALLOW_BUTTON), timeoutMillis) 586 } 587 } 588 589 protected fun clickPermissionRequestAllowAllButton(timeoutMillis: Long = 20000) { 590 click(By.res(ALLOW_ALL_BUTTON), timeoutMillis) 591 } 592 593 /** 594 * Only for use in tests that are not testing the notification permission popup, on T devices 595 */ 596 protected fun clickNotificationPermissionRequestAllowButtonIfAvailable() { 597 if (!SdkLevel.isAtLeastT()) { 598 return 599 } 600 601 if (waitFindObjectOrNull(By.text(getPermissionControllerString( 602 NOTIF_TEXT, APP_PACKAGE_NAME)), 1000) != null) { 603 if (isAutomotive) { 604 click(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT))) 605 } else { 606 click(By.res(ALLOW_BUTTON)) 607 } 608 } 609 } 610 611 protected fun clickPermissionRequestSettingsLinkAndAllowAlways() { 612 clickPermissionRequestSettingsLink() 613 eventually({ 614 clickAllowAlwaysInSettings() 615 }, TIMEOUT_MILLIS * 2) 616 pressBack() 617 } 618 619 protected fun clickAllowAlwaysInSettings() { 620 if (isAutomotive || isTv || isWatch) { 621 click(By.text(getPermissionControllerString("app_permission_button_allow_always"))) 622 } else { 623 click(By.res("com.android.permissioncontroller:id/allow_always_radio_button")) 624 } 625 } 626 627 protected fun clickAllowForegroundInSettings() { 628 click(By.res(ALLOW_FOREGROUND_RADIO_BUTTON)) 629 } 630 631 protected fun clicksDenyInSettings() { 632 if (isAutomotive || isWatch) { 633 click(By.text(getPermissionControllerString("app_permission_button_deny"))) 634 } else { 635 click(By.res("com.android.permissioncontroller:id/deny_radio_button")) 636 } 637 } 638 639 protected fun clickPermissionRequestAllowForegroundButton(timeoutMillis: Long = 10_000) { 640 if (isAutomotive) { 641 click(By.text( 642 getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT)), timeoutMillis) 643 } else { 644 click(By.res(ALLOW_FOREGROUND_BUTTON), timeoutMillis) 645 } 646 } 647 648 protected fun clickPermissionRequestDenyButton() { 649 if (isAutomotive || isWatch || isTv) { 650 click(By.text(getPermissionControllerString(DENY_BUTTON_TEXT))) 651 } else { 652 click(By.res(DENY_BUTTON)) 653 } 654 } 655 656 protected fun clickPermissionRequestSettingsLinkAndDeny() { 657 clickPermissionRequestSettingsLink() 658 eventually({ 659 clicksDenyInSettings() 660 }, TIMEOUT_MILLIS * 2) 661 waitForIdle() 662 pressBack() 663 } 664 665 protected fun clickPermissionRequestSettingsLink() { 666 waitForIdle() 667 eventually { 668 // UiObject2 doesn't expose CharSequence. 669 val node = if (isAutomotive) { 670 // Should match "Allow in settings." (location) and "go to settings." (body sensors) 671 uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByText( 672 " settings." 673 )[0] 674 } else { 675 uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByViewId( 676 "com.android.permissioncontroller:id/detail_message" 677 )[0] 678 } 679 if (!node.isVisibleToUser) { 680 scrollToBottom() 681 } 682 assertTrue(node.isVisibleToUser) 683 val text = node.text as Spanned 684 val clickableSpan = text.getSpans(0, text.length, ClickableSpan::class.java)[0] 685 // We could pass in null here in Java, but we need an instance in Kotlin. 686 clickableSpan.onClick(View(context)) 687 } 688 waitForIdle() 689 } 690 691 protected fun clickPermissionRequestDenyAndDontAskAgainButton() { 692 if (isAutomotive) { 693 click(By.text(getPermissionControllerString(DENY_AND_DONT_ASK_AGAIN_BUTTON_TEXT))) 694 } else if (isWatch) { 695 click(By.text(getPermissionControllerString(DENY_BUTTON_TEXT))) 696 } else { 697 click(By.res(DENY_AND_DONT_ASK_AGAIN_BUTTON)) 698 } 699 } 700 701 // Only used in TV and Watch form factors 702 protected fun clickPermissionRequestDontAskAgainButton() { 703 if (isWatch) { 704 click(By.text(getPermissionControllerString(DENY_BUTTON_TEXT))) 705 } else { 706 click( 707 By.res("com.android.permissioncontroller:id/permission_deny_dont_ask_again_button") 708 ) 709 } 710 } 711 712 protected fun clickPermissionRequestNoUpgradeAndDontAskAgainButton() { 713 if (isAutomotive) { 714 click(By.text(getPermissionControllerString(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON_TEXT))) 715 } else { 716 click(By.res(NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON)) 717 } 718 } 719 720 protected fun clickPermissionRationaleContentInAppPermission() { 721 waitForIdle() 722 click(By.res(APP_PERMISSION_RATIONALE_CONTENT_VIEW)) 723 } 724 725 protected fun clickPermissionRationaleViewInGrantDialog() { 726 waitForIdle() 727 click(By.res(GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW)) 728 } 729 730 protected fun grantAppPermissions(vararg permissions: String) { 731 setAppPermissionState(*permissions, state = PermissionState.ALLOWED, isLegacyApp = false) 732 } 733 734 protected fun revokeAppPermissions( 735 vararg permissions: String, 736 isLegacyApp: Boolean = false 737 ) { 738 setAppPermissionState(*permissions, state = PermissionState.DENIED, 739 isLegacyApp = isLegacyApp) 740 } 741 742 private fun navigateToAppPermissionSettings() { 743 if (isTv) { 744 // Dismiss DeprecatedTargetSdkVersionDialog, if present 745 if (waitFindObjectOrNull(By.text(APP_PACKAGE_NAME), 1000L) != null) { 746 pressBack() 747 } 748 pressHome() 749 } else { 750 pressBack() 751 pressBack() 752 pressBack() 753 } 754 755 // Try multiple times as the AppInfo page might have read stale data 756 eventually({ 757 try { 758 // Open the app details settings 759 context.startActivity( 760 Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { 761 data = Uri.fromParts("package", APP_PACKAGE_NAME, null) 762 addCategory(Intent.CATEGORY_DEFAULT) 763 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 764 addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 765 } 766 ) 767 if (isTv) { 768 waitForIdle() 769 pressDPadDown() 770 pressDPadDown() 771 pressDPadDown() 772 pressDPadDown() 773 } 774 // Open the permissions UI 775 click(byTextRes(R.string.permissions).enabled(true)) 776 } catch (e: Exception) { 777 pressBack() 778 throw e 779 } 780 }, TIMEOUT_MILLIS) 781 } 782 783 protected fun getTargetSdk(packageName: String = APP_PACKAGE_NAME): Int { 784 return callWithShellPermissionIdentity { 785 try { 786 context.packageManager.getApplicationInfo(packageName, 0).targetSdkVersion 787 } catch (e: PackageManager.NameNotFoundException) { 788 Assert.fail("Package $packageName not found") 789 -1 790 } 791 } 792 } 793 794 protected fun navigateToIndividualPermissionSetting( 795 permission: String, 796 manuallyNavigate: Boolean = false 797 ) { 798 if (getTargetSdk() <= MAX_SDK_FOR_SDK_WARNING) { 799 clearTargetSdkWarning() 800 } 801 802 val useLegacyNavigation = isWatch || isAutomotive || manuallyNavigate 803 if (useLegacyNavigation) { 804 navigateToAppPermissionSettings() 805 val permissionLabel = getPermissionLabel(permission) 806 if (isWatch) { 807 click(By.text(permissionLabel), 40_000) 808 } else { 809 clickPermissionControllerUi(By.text(permissionLabel)) 810 } 811 return 812 } 813 814 runWithShellPermissionIdentity { 815 context.startActivity( 816 Intent(Intent.ACTION_MANAGE_APP_PERMISSION).apply { 817 putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME) 818 putExtra(Intent.EXTRA_PERMISSION_NAME, permission) 819 putExtra(Intent.EXTRA_USER, Process.myUserHandle()) 820 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 821 addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 822 }) 823 } 824 } 825 826 /** Starts activity with intent [ACTION_REVIEW_APP_DATA_SHARING_UPDATES]. */ 827 fun startAppDataSharingUpdatesActivity() { 828 runWithShellPermissionIdentity { 829 context.startActivity( 830 Intent(ACTION_REVIEW_APP_DATA_SHARING_UPDATES).apply { 831 addFlags(FLAG_ACTIVITY_NEW_TASK) 832 }) 833 } 834 } 835 836 private fun setAppPermissionState( 837 vararg permissions: String, 838 state: PermissionState, 839 isLegacyApp: Boolean, 840 manuallyNavigate: Boolean = false, 841 ) { 842 val targetSdk = getTargetSdk() 843 if (targetSdk <= MAX_SDK_FOR_SDK_WARNING) { 844 clearTargetSdkWarning(QUICK_CHECK_TIMEOUT_MILLIS) 845 } 846 val useLegacyNavigation = isWatch || isAutomotive || manuallyNavigate 847 if (useLegacyNavigation) { 848 navigateToAppPermissionSettings() 849 } 850 851 val navigatedGroupLabels = mutableSetOf<String>() 852 for (permission in permissions) { 853 // Find the permission screen 854 val permissionLabel = getPermissionLabel(permission) 855 if (navigatedGroupLabels.contains(getPermissionLabel(permission))) { 856 continue 857 } 858 navigatedGroupLabels.add(permissionLabel) 859 if (useLegacyNavigation) { 860 if (isWatch) { 861 click(By.text(permissionLabel), 40_000) 862 } else { 863 clickPermissionControllerUi(By.text(permissionLabel)) 864 } 865 } else { 866 runWithShellPermissionIdentity { 867 context.startActivity( 868 Intent(Intent.ACTION_MANAGE_APP_PERMISSION).apply { 869 putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME) 870 putExtra(Intent.EXTRA_PERMISSION_NAME, permission) 871 putExtra(Intent.EXTRA_USER, Process.myUserHandle()) 872 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 873 addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) 874 }) 875 } 876 } 877 878 val wasGranted = if (isAutomotive) { 879 // Automotive doesn't support one time permissions, and thus 880 // won't show an "Ask every time" message 881 !waitFindObject(By.text( 882 getPermissionControllerString("app_permission_button_deny"))).isChecked 883 } else if (isTv || isWatch) { 884 !(waitFindObject( 885 By.text(getPermissionControllerString(DENY_BUTTON_TEXT))).isChecked || 886 (!isLegacyApp && hasAskButton(permission) && waitFindObject( 887 By.text(getPermissionControllerString(ASK_BUTTON_TEXT))).isChecked)) 888 } else { 889 !(waitFindObject(By.res(DENY_RADIO_BUTTON)).isChecked || 890 (!isLegacyApp && hasAskButton(permission) && 891 waitFindObject(By.res(ASK_RADIO_BUTTON)).isChecked)) 892 } 893 var alreadyChecked = false 894 val button = waitFindObject( 895 if (isAutomotive) { 896 // Automotive doesn't support one time permissions, and thus 897 // won't show an "Ask every time" message 898 when (state) { 899 PermissionState.ALLOWED -> 900 if (showsForegroundOnlyButton(permission)) { 901 By.text(getPermissionControllerString( 902 "app_permission_button_allow_foreground")) 903 } else { 904 By.text(getPermissionControllerString( 905 "app_permission_button_allow")) 906 } 907 PermissionState.DENIED -> By.text( 908 getPermissionControllerString("app_permission_button_deny")) 909 PermissionState.DENIED_WITH_PREJUDICE -> By.text( 910 getPermissionControllerString("app_permission_button_deny")) 911 } 912 } else if (isTv || isWatch) { 913 when (state) { 914 PermissionState.ALLOWED -> 915 if (showsForegroundOnlyButton(permission)) { 916 By.text(getPermissionControllerString( 917 ALLOW_FOREGROUND_PREFERENCE_TEXT)) 918 } else { 919 byAnyText(getPermissionControllerResString(ALLOW_BUTTON_TEXT), 920 getPermissionControllerResString(ALLOW_ALL_FILES_BUTTON_TEXT)) 921 } 922 PermissionState.DENIED -> 923 if (!isLegacyApp && hasAskButton(permission)) { 924 By.text(getPermissionControllerString(ASK_BUTTON_TEXT)) 925 } else { 926 By.text(getPermissionControllerString(DENY_BUTTON_TEXT)) 927 } 928 PermissionState.DENIED_WITH_PREJUDICE -> By.text( 929 getPermissionControllerString(DENY_BUTTON_TEXT)) 930 } 931 } else { 932 when (state) { 933 PermissionState.ALLOWED -> 934 if (showsForegroundOnlyButton(permission)) { 935 By.res(ALLOW_FOREGROUND_RADIO_BUTTON) 936 } else if (showsAlwaysButton(permission)) { 937 By.res(ALLOW_ALWAYS_RADIO_BUTTON) 938 } else { 939 By.res(ALLOW_RADIO_BUTTON) 940 } 941 PermissionState.DENIED -> 942 if (!isLegacyApp && hasAskButton(permission)) { 943 By.res(ASK_RADIO_BUTTON) 944 } else { 945 By.res(DENY_RADIO_BUTTON) 946 } 947 PermissionState.DENIED_WITH_PREJUDICE -> By.res(DENY_RADIO_BUTTON) 948 } 949 } 950 ) 951 alreadyChecked = button.isChecked 952 if (!alreadyChecked) { 953 button.click() 954 } 955 956 val shouldShowStorageWarning = SdkLevel.isAtLeastT() && 957 targetSdk <= Build.VERSION_CODES.S_V2 && 958 permission in MEDIA_PERMISSIONS 959 if (shouldShowStorageWarning) { 960 click(By.res(ALERT_DIALOG_OK_BUTTON)) 961 } else if (!alreadyChecked && isLegacyApp && wasGranted) { 962 if (!isTv) { 963 // Wait for alert dialog to popup, then scroll to the bottom of it 964 if (isWatch) { 965 waitFindObject(By.text( 966 getPermissionControllerString("old_sdk_deny_warning"))) 967 } else { 968 waitFindObject(By.res(ALERT_DIALOG_MESSAGE)) 969 } 970 scrollToBottom() 971 } 972 973 // Due to the limited real estate, Wear uses buttons with icons instead of text 974 // for dialogs 975 if (isWatch) { 976 click(By.res( 977 "com.android.permissioncontroller:id/wear_alertdialog_positive_button")) 978 } else { 979 val resources = context.createPackageContext( 980 packageManager.permissionControllerPackageName, 0 981 ).resources 982 val confirmTextRes = resources.getIdentifier( 983 "com.android.permissioncontroller:string/grant_dialog_button_deny_anyway", 984 null, null 985 ) 986 987 val confirmText = resources.getString(confirmTextRes) 988 click(byTextStartsWithCaseInsensitive(confirmText)) 989 } 990 } 991 pressBack() 992 } 993 pressBack() 994 pressBack() 995 } 996 997 private fun getPermissionLabel(permission: String): String { 998 val labelResName = permissionToLabelResNameMap[permission] 999 assertNotNull("Unknown permission $permission", labelResName) 1000 val labelRes = platformResources.getIdentifier(labelResName, null, null) 1001 return platformResources.getString(labelRes) 1002 } 1003 1004 private fun hasAskButton(permission: String): Boolean = 1005 when (permission) { 1006 android.Manifest.permission.CAMERA, 1007 android.Manifest.permission.RECORD_AUDIO, 1008 android.Manifest.permission.ACCESS_FINE_LOCATION, 1009 android.Manifest.permission.ACCESS_COARSE_LOCATION, 1010 android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true 1011 else -> false 1012 } 1013 private fun showsAllowPhotosButton(permission: String): Boolean { 1014 if (!isPhotoPickerPermissionPromptEnabled()) { 1015 return false 1016 } 1017 return when (permission) { 1018 Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, 1019 Manifest.permission.READ_MEDIA_IMAGES, 1020 Manifest.permission.READ_MEDIA_VIDEO -> true 1021 else -> false 1022 } 1023 } 1024 1025 private fun showsForegroundOnlyButton(permission: String): Boolean = 1026 when (permission) { 1027 android.Manifest.permission.CAMERA, 1028 android.Manifest.permission.RECORD_AUDIO -> true 1029 else -> false 1030 } 1031 1032 private fun showsAlwaysButton(permission: String): Boolean = 1033 when (permission) { 1034 android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true 1035 else -> false 1036 } 1037 1038 private fun scrollToBottom() { 1039 val scrollable = UiScrollable(UiSelector().scrollable(true)).apply { 1040 if (isWatch) { 1041 swipeDeadZonePercentage = 0.1 1042 } else { 1043 swipeDeadZonePercentage = 0.25 1044 } 1045 } 1046 waitForIdle() 1047 if (scrollable.exists()) { 1048 try { 1049 scrollable.flingToEnd(10) 1050 } catch (e: UiObjectNotFoundException) { 1051 // flingToEnd() sometimes still fails despite waitForIdle() and the exists() check 1052 // (b/246984354). 1053 e.printStackTrace() 1054 } 1055 } 1056 } 1057 1058 private fun byTextRes(textRes: Int): BySelector = By.text(context.getString(textRes)) 1059 1060 private fun byTextStartsWithCaseInsensitive(prefix: String): BySelector = 1061 By.text(Pattern.compile("(?i)^${Pattern.quote(prefix)}.*$")) 1062 1063 protected fun assertAppHasPermission(permissionName: String, expectPermission: Boolean) { 1064 assertEquals( "Permission $permissionName", 1065 if (expectPermission) { 1066 PackageManager.PERMISSION_GRANTED 1067 } else { 1068 PackageManager.PERMISSION_DENIED 1069 }, 1070 packageManager.checkPermission(permissionName, APP_PACKAGE_NAME) 1071 ) 1072 } 1073 1074 protected fun assertAppHasCalendarAccess(expectAccess: Boolean) { 1075 val future = startActivityForFuture( 1076 Intent().apply { 1077 component = ComponentName( 1078 APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.CheckCalendarAccessActivity" 1079 ) 1080 } 1081 ) 1082 waitForIdle() 1083 clickNotificationPermissionRequestAllowButtonIfAvailable() 1084 val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS) 1085 assertEquals(Activity.RESULT_OK, result.resultCode) 1086 assertTrue(result.resultData!!.hasExtra("$APP_PACKAGE_NAME.HAS_ACCESS")) 1087 assertEquals( 1088 expectAccess, 1089 result.resultData!!.getBooleanExtra("$APP_PACKAGE_NAME.HAS_ACCESS", false) 1090 ) 1091 } 1092 1093 protected fun assertPermissionFlags(permName: String, vararg flags: Pair<Int, Boolean>) { 1094 val user = Process.myUserHandle() 1095 SystemUtil.runWithShellPermissionIdentity { 1096 val currFlags = 1097 packageManager.getPermissionFlags(permName, APP_PACKAGE_NAME, user) 1098 for ((flag, set) in flags) { 1099 assertEquals("flag $flag: ", set, currFlags and flag != 0) 1100 } 1101 } 1102 } 1103 } 1104