• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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