summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--PermissionController/Android.bp4
-rw-r--r--PermissionController/TEST_MAPPING27
-rw-r--r--PermissionController/jarjar-rules.txt12
-rw-r--r--PermissionController/lint-baseline.xml136
-rw-r--r--PermissionController/res/anim/text_switcher_fade_in.xml22
-rw-r--r--PermissionController/res/anim/text_switcher_fade_out.xml21
-rw-r--r--PermissionController/res/layout-v33/preference_issue_card.xml133
-rw-r--r--PermissionController/res/layout-v33/preference_more_issues_card.xml11
-rw-r--r--PermissionController/res/layout-v33/preference_safety_status.xml11
-rw-r--r--PermissionController/res/layout-v33/view_status_card.xml31
-rw-r--r--PermissionController/res/layout-v35/app_permission_footer_link_preference.xml34
-rw-r--r--PermissionController/res/layout-v35/nav_host_fragment.xml32
-rw-r--r--PermissionController/res/layout-v35/permission_preference_selector_with_widget.xml69
-rw-r--r--PermissionController/res/layout-v35/permission_preference_two_target.xml60
-rw-r--r--PermissionController/res/layout-v35/permission_preference_widget_radiobutton.xml24
-rw-r--r--PermissionController/res/layout/permission_history_widget.xml2
-rw-r--r--PermissionController/res/values-af-v33/strings.xml5
-rw-r--r--PermissionController/res/values-af/strings.xml111
-rw-r--r--PermissionController/res/values-am-v33/strings.xml1
-rw-r--r--PermissionController/res/values-am/strings.xml9
-rw-r--r--PermissionController/res/values-ar-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ar/strings.xml13
-rw-r--r--PermissionController/res/values-as-v33/strings.xml1
-rw-r--r--PermissionController/res/values-as/strings.xml9
-rw-r--r--PermissionController/res/values-az-v33/strings.xml1
-rw-r--r--PermissionController/res/values-az/strings.xml9
-rw-r--r--PermissionController/res/values-b+sr+Latn-v33/strings.xml3
-rw-r--r--PermissionController/res/values-b+sr+Latn/strings.xml11
-rw-r--r--PermissionController/res/values-be-v33/strings.xml1
-rw-r--r--PermissionController/res/values-be/strings.xml9
-rw-r--r--PermissionController/res/values-bg-v33/strings.xml1
-rw-r--r--PermissionController/res/values-bg/strings.xml11
-rw-r--r--PermissionController/res/values-bn-v33/strings.xml1
-rw-r--r--PermissionController/res/values-bn/strings.xml9
-rw-r--r--PermissionController/res/values-bs-v33/strings.xml1
-rw-r--r--PermissionController/res/values-bs/strings.xml11
-rw-r--r--PermissionController/res/values-ca-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ca/strings.xml13
-rw-r--r--PermissionController/res/values-cs-v33/strings.xml3
-rw-r--r--PermissionController/res/values-cs/strings.xml13
-rw-r--r--PermissionController/res/values-da-v33/strings.xml1
-rw-r--r--PermissionController/res/values-da/strings.xml11
-rw-r--r--PermissionController/res/values-de-v33/strings.xml1
-rw-r--r--PermissionController/res/values-de/strings.xml15
-rw-r--r--PermissionController/res/values-el-v33/strings.xml1
-rw-r--r--PermissionController/res/values-el/strings.xml9
-rw-r--r--PermissionController/res/values-en-rAU-v33/strings.xml1
-rw-r--r--PermissionController/res/values-en-rAU/strings.xml9
-rw-r--r--PermissionController/res/values-en-rCA-v33/strings.xml1
-rw-r--r--PermissionController/res/values-en-rCA/strings.xml9
-rw-r--r--PermissionController/res/values-en-rGB-v33/strings.xml1
-rw-r--r--PermissionController/res/values-en-rGB/strings.xml9
-rw-r--r--PermissionController/res/values-en-rIN-v33/strings.xml1
-rw-r--r--PermissionController/res/values-en-rIN/strings.xml9
-rw-r--r--PermissionController/res/values-es-rUS-v33/strings.xml1
-rw-r--r--PermissionController/res/values-es-rUS/strings.xml9
-rw-r--r--PermissionController/res/values-es-v33/strings.xml3
-rw-r--r--PermissionController/res/values-es/strings.xml17
-rw-r--r--PermissionController/res/values-et-v33/strings.xml3
-rw-r--r--PermissionController/res/values-et/strings.xml13
-rw-r--r--PermissionController/res/values-eu-v33/strings.xml1
-rw-r--r--PermissionController/res/values-eu/strings.xml17
-rw-r--r--PermissionController/res/values-fa-v33/strings.xml1
-rw-r--r--PermissionController/res/values-fa-watch/strings.xml2
-rw-r--r--PermissionController/res/values-fa/strings.xml17
-rw-r--r--PermissionController/res/values-fi-v33/strings.xml5
-rw-r--r--PermissionController/res/values-fi/strings.xml11
-rw-r--r--PermissionController/res/values-fr-rCA-v33/strings.xml1
-rw-r--r--PermissionController/res/values-fr-rCA/strings.xml17
-rw-r--r--PermissionController/res/values-fr-v33/strings.xml1
-rw-r--r--PermissionController/res/values-fr/strings.xml9
-rw-r--r--PermissionController/res/values-gl-v33/strings.xml1
-rw-r--r--PermissionController/res/values-gl/strings.xml9
-rw-r--r--PermissionController/res/values-gu-v33/strings.xml1
-rw-r--r--PermissionController/res/values-gu/strings.xml15
-rw-r--r--PermissionController/res/values-hi-v33/strings.xml3
-rw-r--r--PermissionController/res/values-hi/strings.xml9
-rw-r--r--PermissionController/res/values-hr-v33/strings.xml1
-rw-r--r--PermissionController/res/values-hr/strings.xml21
-rw-r--r--PermissionController/res/values-hu-v33/strings.xml3
-rw-r--r--PermissionController/res/values-hu/strings.xml11
-rw-r--r--PermissionController/res/values-hy-v33/strings.xml1
-rw-r--r--PermissionController/res/values-hy/strings.xml11
-rw-r--r--PermissionController/res/values-in-v33/strings.xml1
-rw-r--r--PermissionController/res/values-in/strings.xml9
-rw-r--r--PermissionController/res/values-is-v33/strings.xml1
-rw-r--r--PermissionController/res/values-is/strings.xml9
-rw-r--r--PermissionController/res/values-it-v33/strings.xml1
-rw-r--r--PermissionController/res/values-it/strings.xml11
-rw-r--r--PermissionController/res/values-iw-v33/strings.xml1
-rw-r--r--PermissionController/res/values-iw/strings.xml37
-rw-r--r--PermissionController/res/values-ja-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ja/strings.xml17
-rw-r--r--PermissionController/res/values-ka-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ka/strings.xml9
-rw-r--r--PermissionController/res/values-kk-v33/strings.xml1
-rw-r--r--PermissionController/res/values-kk/strings.xml11
-rw-r--r--PermissionController/res/values-km-v33/strings.xml1
-rw-r--r--PermissionController/res/values-km/strings.xml9
-rw-r--r--PermissionController/res/values-kn-v33/strings.xml1
-rw-r--r--PermissionController/res/values-kn/strings.xml9
-rw-r--r--PermissionController/res/values-ko-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ko/strings.xml11
-rw-r--r--PermissionController/res/values-ky-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ky/strings.xml11
-rw-r--r--PermissionController/res/values-lo-v33/strings.xml1
-rw-r--r--PermissionController/res/values-lo/strings.xml9
-rw-r--r--PermissionController/res/values-lt-v33/strings.xml1
-rw-r--r--PermissionController/res/values-lt/strings.xml13
-rw-r--r--PermissionController/res/values-lv-v33/strings.xml1
-rw-r--r--PermissionController/res/values-lv/strings.xml11
-rw-r--r--PermissionController/res/values-mk-v33/strings.xml1
-rw-r--r--PermissionController/res/values-mk/strings.xml11
-rw-r--r--PermissionController/res/values-ml-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ml/strings.xml9
-rw-r--r--PermissionController/res/values-mn-v33/strings.xml1
-rw-r--r--PermissionController/res/values-mn/strings.xml9
-rw-r--r--PermissionController/res/values-mr-v33/strings.xml1
-rw-r--r--PermissionController/res/values-mr/strings.xml9
-rw-r--r--PermissionController/res/values-ms-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ms/strings.xml13
-rw-r--r--PermissionController/res/values-my-v33/strings.xml1
-rw-r--r--PermissionController/res/values-my/strings.xml9
-rw-r--r--PermissionController/res/values-nb-v33/strings.xml3
-rw-r--r--PermissionController/res/values-nb/strings.xml11
-rw-r--r--PermissionController/res/values-ne-v33/strings.xml3
-rw-r--r--PermissionController/res/values-ne/strings.xml23
-rw-r--r--PermissionController/res/values-night-v33/themes.xml2
-rw-r--r--PermissionController/res/values-nl-v33/strings.xml1
-rw-r--r--PermissionController/res/values-nl/strings.xml13
-rw-r--r--PermissionController/res/values-or-v33/strings.xml1
-rw-r--r--PermissionController/res/values-or/strings.xml13
-rw-r--r--PermissionController/res/values-pa-v33/strings.xml1
-rw-r--r--PermissionController/res/values-pa/strings.xml13
-rw-r--r--PermissionController/res/values-pl-v33/strings.xml1
-rw-r--r--PermissionController/res/values-pl/strings.xml15
-rw-r--r--PermissionController/res/values-pt-rBR-v33/strings.xml1
-rw-r--r--PermissionController/res/values-pt-rBR/strings.xml11
-rw-r--r--PermissionController/res/values-pt-rPT-v33/strings.xml1
-rw-r--r--PermissionController/res/values-pt-rPT/strings.xml15
-rw-r--r--PermissionController/res/values-pt-v33/strings.xml1
-rw-r--r--PermissionController/res/values-pt/strings.xml11
-rw-r--r--PermissionController/res/values-ro-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ro/strings.xml15
-rw-r--r--PermissionController/res/values-ru-v33/strings.xml3
-rw-r--r--PermissionController/res/values-ru/strings.xml15
-rw-r--r--PermissionController/res/values-si-v33/strings.xml1
-rw-r--r--PermissionController/res/values-si/strings.xml9
-rw-r--r--PermissionController/res/values-sk-v33/strings.xml1
-rw-r--r--PermissionController/res/values-sk-watch/strings.xml6
-rw-r--r--PermissionController/res/values-sk/strings.xml17
-rw-r--r--PermissionController/res/values-sl-v33/strings.xml1
-rw-r--r--PermissionController/res/values-sl-watch/strings.xml2
-rw-r--r--PermissionController/res/values-sl/strings.xml11
-rw-r--r--PermissionController/res/values-sq-v33/strings.xml1
-rw-r--r--PermissionController/res/values-sq/strings.xml11
-rw-r--r--PermissionController/res/values-sr-v33/strings.xml3
-rw-r--r--PermissionController/res/values-sr/strings.xml11
-rw-r--r--PermissionController/res/values-sv-v33/strings.xml1
-rw-r--r--PermissionController/res/values-sv/strings.xml13
-rw-r--r--PermissionController/res/values-sw-v33/strings.xml1
-rw-r--r--PermissionController/res/values-sw/strings.xml11
-rw-r--r--PermissionController/res/values-ta-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ta/strings.xml9
-rw-r--r--PermissionController/res/values-te-v33/strings.xml1
-rw-r--r--PermissionController/res/values-te/strings.xml9
-rw-r--r--PermissionController/res/values-th-v33/strings.xml1
-rw-r--r--PermissionController/res/values-th/strings.xml9
-rw-r--r--PermissionController/res/values-tl-v33/strings.xml1
-rw-r--r--PermissionController/res/values-tl/strings.xml9
-rw-r--r--PermissionController/res/values-tr-v33/strings.xml1
-rw-r--r--PermissionController/res/values-tr/strings.xml11
-rw-r--r--PermissionController/res/values-uk-v33/strings.xml1
-rw-r--r--PermissionController/res/values-uk/strings.xml15
-rw-r--r--PermissionController/res/values-ur-v33/strings.xml1
-rw-r--r--PermissionController/res/values-ur/strings.xml9
-rw-r--r--PermissionController/res/values-uz-v33/strings.xml1
-rw-r--r--PermissionController/res/values-uz/strings.xml9
-rw-r--r--PermissionController/res/values-v33/attrs.xml5
-rw-r--r--PermissionController/res/values-v33/strings.xml2
-rw-r--r--PermissionController/res/values-v33/styles.xml4
-rw-r--r--PermissionController/res/values-v33/themes.xml12
-rw-r--r--PermissionController/res/values-v35/styles.xml216
-rw-r--r--PermissionController/res/values-v35/themes.xml2
-rw-r--r--PermissionController/res/values-vi-v33/strings.xml1
-rw-r--r--PermissionController/res/values-vi/strings.xml11
-rw-r--r--PermissionController/res/values-watch/donottranslate.xml31
-rw-r--r--PermissionController/res/values-zh-rCN-v33/strings.xml1
-rw-r--r--PermissionController/res/values-zh-rCN/strings.xml17
-rw-r--r--PermissionController/res/values-zh-rHK-v33/strings.xml1
-rw-r--r--PermissionController/res/values-zh-rHK-v34/strings.xml2
-rw-r--r--PermissionController/res/values-zh-rHK/strings.xml13
-rw-r--r--PermissionController/res/values-zh-rTW-v33/strings.xml1
-rw-r--r--PermissionController/res/values-zh-rTW/strings.xml9
-rw-r--r--PermissionController/res/values-zu-v33/strings.xml1
-rw-r--r--PermissionController/res/values-zu/strings.xml9
-rw-r--r--PermissionController/res/values/bools.xml2
-rw-r--r--PermissionController/res/values/config.xml9
-rw-r--r--PermissionController/res/values/overlayable.xml56
-rw-r--r--PermissionController/res/values/strings.xml40
-rw-r--r--PermissionController/res/xml-v35/app_permission.xml (renamed from PermissionController/res/xml/app_permission.xml)32
-rw-r--r--PermissionController/res/xml/roles.xml232
-rw-r--r--PermissionController/role-controller/Android.bp8
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java36
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/v33/CompanionDeviceAppStreamingRoleBehavior.java8
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/v34/CompanionDeviceNearbyDeviceStreamingRoleBehavior.java38
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/v35/WalletRoleBehavior.java22
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/v36/ReservedForTestingProfileGroupExclusivityRoleBehavior.java91
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java8
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Permission.java4
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java108
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Role.java290
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java16
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java273
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java32
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/IntentCompat.java (renamed from PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/package-info.java)13
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/PackageUtils.java20
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java37
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/SignedPackage.java188
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/SignedPackageUtils.java106
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java51
-rw-r--r--PermissionController/src/com/android/permissioncontroller/appops/data/repository/v31/AppOpRepository.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt83
-rw-r--r--PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt31
-rw-r--r--PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt45
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java49
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/compat/AppPermissionFragmentCompat.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt44
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/LightAppPermGroupLiveData.kt73
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt147
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/domain/usecase/v31/GetPermissionGroupUsageDetailsUseCase.kt91
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt23
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/AppPermissionId.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt241
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt81
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/BackupHelper.java26
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java39
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt125
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionWrapperFragment.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageCustomPermissionsFragment.java14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java35
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionFooterPreference.kt9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.kt19
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreferenceCategory.kt19
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/max35/LegacyAppPermissionFragment.java9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionHistoryPreference.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionUsageDetailsFragment.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v35/SectionPreferenceGroupAdapter.kt6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/AppPermissionFooterLinkPreference.kt64
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/AppPermissionFragment.java20
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/PermissionSelectorWithWidgetPreference.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionSelectorWithWidgetPreference.kt)16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/PermissionTwoTargetPreference.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionTwoTargetPreference.kt)19
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageDetailsViewModelLegacy.kt596
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt25
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageCustomPermissionsViewModel.kt53
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt61
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/BasePermissionUsageDetailsViewModel.kt124
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt827
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModelV2.kt242
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt44
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionFragment.kt38
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsFragment.kt16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsHelper.kt5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt80
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt109
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationDialogFragment.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt46
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt60
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionScreen.kt19
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionsFragment.kt8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt44
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionsFragment.kt12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt67
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsFragment.kt9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt35
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt38
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsFragment.kt32
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt24
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUtils.kt9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt133
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/RemoteConnectionProgressIndicator.kt75
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Haptics.kt292
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Rotary.kt1232
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/RotaryVelocityTracker.kt47
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearGrantPermissionsViewModel.kt7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearLocationProviderInterceptDialogViewModel.kt17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt131
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTonalPalette.kt191
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt136
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt42
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java138
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/Role.md15
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING15
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/UserPackage.java104
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java213
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppConfirmationDialogFragment.java21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java53
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListViewModel.java57
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java59
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/ListLiveDataFilterFunction.java57
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/ListLiveDataSortFunction.java49
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/MergeRoleListLiveData.java (renamed from PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/MergeRoleListLiveData.java)14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/MergeRoleLiveData.java (renamed from PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/MergeRoleLiveData.java)21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java221
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationItem.java53
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationPreference.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleListSortFunction.java25
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleLiveData.java12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RolePreference.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleSortFunction.java34
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRadioPreference.java17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRolePreference.java17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSwitchPreference.java16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/AssistantRoleUiBehavior.java23
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/v35/WalletRoleUiBehavior.java78
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/v36/ReservedForTestingProfileGroupExclusivityRoleUiBehavior.java83
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRadioPreference.java17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRolePreference.java17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java37
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListViewModel.java1
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSwitchPreference.java16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppFragment.kt9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppHelper.kt51
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt22
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt81
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleFragment.kt129
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleHelper.kt69
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt133
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRoleApplicationPreference.kt16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRolePreference.kt19
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/model/WearRequestRoleViewModel.kt22
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java15
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java45
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterSearchIndexablesProvider.kt56
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/HighlightablePreferenceGroupAdapter.java141
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java32
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java29
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt23
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt50
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java63
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/IssueUiData.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt)18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt41
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt24
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/MoreIssuesHeaderView.kt36
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt25
-rw-r--r--PermissionController/tests/inprocess/Android.bp4
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/GetPermissionGroupInfoTest.kt4
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/data/AttributionLabelLiveDataTest.kt4
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/ui/model/PermissionUsageDetailsViewModelTest.kt9
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt79
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/ArrayUtilsTest.kt (renamed from PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt)7
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/CollectionUtilsTest.kt (renamed from PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt)7
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/KotlinUtilsTest.kt (renamed from PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt)11
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/PermissionMappingTest.kt171
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/UtilsTest.kt (renamed from PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt)75
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtilsTest.kt62
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationPolicyTest.kt66
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/domain/usecase/GetPermissionGroupUsageDetailsUseCaseTest.kt212
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/PermissionUsageDetailsViewModelTest.kt114
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/AdminRestrictedPermissionsUtilsTest.kt155
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt2
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/WorkPolicyInfoTest.kt3
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt136
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/SafetyCenterUiDataTest.kt212
-rw-r--r--PermissionController/tests/permissionui/Android.bp5
-rw-r--r--PermissionController/tests/permissionui/AndroidTest.xml4
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/Android.bp34
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/AndroidManifest.xml25
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/Android.bp34
-rw-r--r--PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/AndroidManifest.xml25
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt20
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt69
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt8
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageCustomPermissionsFragmentTest.kt49
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageStandardPermissionsFragmentTest.kt35
-rw-r--r--PermissionController/wear-permission-components/Android.bp67
-rw-r--r--PermissionController/wear-permission-components/AndroidManifest.xml18
-rw-r--r--PermissionController/wear-permission-components/res/drawable/ic_security_update_good.xml (renamed from PermissionController/res/drawable/ic_security_update_good.xml)0
-rw-r--r--PermissionController/wear-permission-components/res/values/donottranslate.xml82
-rw-r--r--PermissionController/wear-permission-components/res/values/overlayable.xml87
-rw-r--r--PermissionController/wear-permission-components/res/values/strings.xml7
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/AnnotatedText.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt)74
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/CheckYourPhone.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/CheckYourPhone.kt)74
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/DrawablePainter.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/DrawablePainter.kt)4
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/ScrollableScreen.kt153
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material2/AlertDialog.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt)119
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material2/Chip.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt)34
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material2/Icon.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt)27
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material2/ListFooter.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt)7
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material2/ListHeader.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt)18
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material2/ResponsiveDialog.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ResponsiveDialog.kt)107
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material2/ToggleChip.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt)87
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material2/Wear2Scaffold.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt)148
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material2/layout/ScalingLazyColumnDefaults.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnDefaults.kt)16
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material2/layout/ScalingLazyColumnState.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnState.kt)64
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionButton.kt141
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionButtonStyle.kt74
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionConfirmationDialog.kt163
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionIconBuilder.kt113
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionListFooter.kt53
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionListSubHeader.kt58
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionScaffold.kt397
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionScaffoldPaddingDefaults.kt82
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionToggleControl.kt184
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionToggleControlStyle.kt158
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/theme/ResourceHelper.kt86
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3ColorScheme.kt228
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3Shapes.kt66
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3TypeScaleTokens.kt109
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3Typography.kt240
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3VariableFontTokens.kt186
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/theme/WearMaterialBridgedLegacyTheme.kt78
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/theme/WearOverlayableMaterial3Theme.kt45
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/theme/WearPermissionTheme.kt59
-rw-r--r--PermissionController/wear-permission-components/src/wear.permission.components/theme/WearPermissionTonalPalette.kt216
-rw-r--r--SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigOverlayTest.kt15
-rw-r--r--SafetyCenter/Resources/res/raw-v36/safety_center_config.xml188
-rw-r--r--SafetyCenter/Resources/res/values-af-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-am-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ar-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-as-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-az-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-b+sr+Latn-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-be-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-bg-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-bn-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-bs-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ca-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-cs-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-da-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-de-v34/strings.xml2
-rw-r--r--SafetyCenter/Resources/res/values-de-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-el-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-en-rAU-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-en-rCA-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-en-rGB-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-en-rIN-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-es-rUS-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-es-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-et-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-eu-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-fa-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-fi-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-fr-rCA-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-fr-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-gl-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-gu-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-hi-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-hr-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-hu-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-hy-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-in-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-is-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-it-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-iw-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-iw/strings.xml2
-rw-r--r--SafetyCenter/Resources/res/values-ja-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ka-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-kk-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-km-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-kn-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ko-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ky-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-lo-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-lt-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-lv-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-mk-v35/strings.xml2
-rw-r--r--SafetyCenter/Resources/res/values-mk-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-mk/strings.xml4
-rw-r--r--SafetyCenter/Resources/res/values-ml-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-mn-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-mr-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ms-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-my-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-nb-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ne-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-nl-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-or-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-pa-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-pl-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-pt-rBR-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-pt-rPT-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-pt-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ro-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ru-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-si-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-sk-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-sk/strings.xml2
-rw-r--r--SafetyCenter/Resources/res/values-sl-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-sq-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-sr-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-sv-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-sw-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ta-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-te-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-th-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-tl-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-tr-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-uk-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-ur-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-uz-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-v36/config.xml21
-rw-r--r--SafetyCenter/Resources/res/values-v36/strings.xml33
-rw-r--r--SafetyCenter/Resources/res/values-vi-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-zh-rCN-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-zh-rHK-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-zh-rTW-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/res/values-zu-v36/strings.xml32
-rw-r--r--SafetyCenter/Resources/shared_res/values-ky/strings.xml2
-rw-r--r--TEST_MAPPING3
-rw-r--r--flags/Android.bp8
-rw-r--r--flags/flags.aconfig77
-rw-r--r--framework-s/api/system-current.txt8
-rw-r--r--framework-s/java/android/app/ecm/EnhancedConfirmationManager.java36
-rw-r--r--framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl2
-rw-r--r--framework-s/java/android/app/role/IRoleManager.aidl13
-rw-r--r--framework-s/java/android/app/role/RoleManager.java223
-rw-r--r--framework-s/java/android/app/role/TEST_MAPPING9
-rw-r--r--framework-s/java/android/permission/internal/compat/UserHandleCompat.java (renamed from service/java/com/android/permission/compat/UserHandleCompat.java)9
-rw-r--r--framework-s/java/android/permission/internal/compat/package-info.java (renamed from service/java/com/android/permission/compat/package-info.java)2
-rw-r--r--service/Android.bp1
-rw-r--r--service/api/system-server-current.txt14
-rw-r--r--service/jarjar-rules.txt12
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java80
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java56
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java59
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationService.java275
-rw-r--r--service/java/com/android/permission/util/UserUtils.java119
-rw-r--r--service/java/com/android/role/RoleService.java349
-rw-r--r--service/java/com/android/role/RoleShellCommand.java29
-rw-r--r--service/java/com/android/role/RoleUserState.java127
-rw-r--r--service/java/com/android/role/TEST_MAPPING9
-rw-r--r--service/java/com/android/role/persistence/RolesPersistenceImpl.java22
-rw-r--r--service/java/com/android/role/persistence/RolesState.java42
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterService.java3
-rw-r--r--service/java/com/android/safetycenter/UserProfileGroup.java63
-rw-r--r--service/lint-baseline.xml11
-rw-r--r--service/proto/role_service.proto3
-rw-r--r--tests/apex/Android.bp2
-rw-r--r--tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt48
-rw-r--r--tests/cts/permission/AndroidManifest.xml3
-rw-r--r--tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java9
-rw-r--r--tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java46
-rw-r--r--tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java35
-rw-r--r--tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt182
-rw-r--r--tests/cts/permission/src/android/permission/cts/NoWifiStatePermissionTest.java8
-rw-r--r--tests/cts/permission/src/android/permission/cts/OneTimePermissionTest.java36
-rw-r--r--tests/cts/permission/src/android/permission/cts/PermissionUpdateListenerTest.java67
-rwxr-xr-xtests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java73
-rw-r--r--tests/cts/permissionmultidevice/AndroidManifest.xml2
-rw-r--r--tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PermissionUtils.kt41
-rw-r--r--tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt4
-rw-r--r--tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt131
-rw-r--r--tests/cts/permissionmultiuser/Android.bp1
-rw-r--r--tests/cts/permissionmultiuser/AndroidTest.xml4
-rw-r--r--tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt5
-rw-r--r--tests/cts/permissionpolicy/Android.bp1
-rw-r--r--tests/cts/permissionpolicy/res/raw/android_manifest.xml941
-rw-r--r--tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml83
-rw-r--r--tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt15
-rw-r--r--tests/cts/permissionui/AndroidManifest.xml18
-rw-r--r--tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml2
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/AppDataSharingUpdatesTest.kt1
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt32
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt8
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt102
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt78
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt233
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt12
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt9
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt35
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt11
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt36
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt130
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt66
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt69
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt59
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt25
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/VoipCallHelper.kt171
-rw-r--r--tests/cts/role/Android.bp13
-rw-r--r--tests/cts/role/AndroidManifest.xml1
-rw-r--r--tests/cts/role/AndroidTest.xml3
-rw-r--r--tests/cts/role/src/android/app/role/cts/ChooseNoteRoleAppTest.kt70
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleManagerTest.java271
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleManagerUtil.kt62
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt45
-rw-r--r--tests/cts/rolemultiuser/Android.bp51
-rw-r--r--tests/cts/rolemultiuser/AndroidManifest.xml36
-rw-r--r--tests/cts/rolemultiuser/AndroidTest.xml53
-rw-r--r--tests/cts/rolemultiuser/TEST_MAPPING17
-rw-r--r--tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt2412
-rw-r--r--tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/WaitForResultActivity.kt67
-rw-r--r--tests/cts/safetycenter/AndroidTest.xml4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt26
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt34
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt2
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt18
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt236
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryGroupTest.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryTest.kt8
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt6
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt36
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt8
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt30
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt2
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt212
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt28
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt8
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt10
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt2
-rw-r--r--tests/functional/safetycenter/multiusers/Android.bp1
-rw-r--r--tests/functional/safetycenter/multiusers/AndroidTest.xml4
-rw-r--r--tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt330
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml4
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt289
-rw-r--r--tests/functional/safetycenter/singleuser/AndroidTest.xml4
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt832
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt92
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetySourceDataFixesTest.kt15
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt2
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt44
-rw-r--r--tests/functional/safetycenter/subpages/AndroidTest.xml4
-rw-r--r--tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt22
-rw-r--r--tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt44
-rw-r--r--tests/hostside/safetycenter/AndroidTest.xml4
-rw-r--r--tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt8
-rw-r--r--tests/utils/safetycenter/AndroidManifest.xml1
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt36
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt2
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/EqualsHashCodeToStringTester.kt10
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt12
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterActivityLauncher.kt6
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt10
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterEnabledChangedReceiver.kt6
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt53
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt16
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestData.kt100
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestHelper.kt10
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt4
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt2
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceIntentHandler.kt16
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceReceiver.kt20
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt72
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SettingsPackage.kt2
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/StatusBarNotificationWithChannel.kt2
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/TestActivity.kt2
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt18
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt3
-rw-r--r--tests/utils/safetycenter/res/layout/test_activity.xml1
-rw-r--r--tests/utils/safetycenter/res/values/styles.xml24
677 files changed, 22564 insertions, 9364 deletions
diff --git a/Android.bp b/Android.bp
index 9b1857741..f2aab3568 100644
--- a/Android.bp
+++ b/Android.bp
@@ -110,6 +110,7 @@ bootclasspath_fragment {
package_prefixes: [
"android.app.role",
"android.app.ecm",
+ "android.permission.internal",
"android.permission.jarjar",
"android.safetycenter",
"android.safetylabel",
diff --git a/PermissionController/Android.bp b/PermissionController/Android.bp
index 37a94cc61..63fb1a264 100644
--- a/PermissionController/Android.bp
+++ b/PermissionController/Android.bp
@@ -153,14 +153,16 @@ android_library {
"safety-label",
"role-controller",
"android.permission.flags-aconfig-java-export",
+ "android.xr.flags-aconfig-java-export",
"com.android.permission.flags-aconfig-java-export",
"androidx.compose.foundation_foundation",
"androidx.compose.runtime_runtime",
"androidx.compose.runtime_runtime-livedata",
"androidx.compose.ui_ui",
- "androidx.wear.compose_compose-material",
+ "androidx.wear.compose_compose-material3",
"android.content.pm.flags-aconfig-java-export",
"android.os.flags-aconfig-java-export",
+ "wear-permission-components",
],
lint: {
diff --git a/PermissionController/TEST_MAPPING b/PermissionController/TEST_MAPPING
index a34a16034..508105c46 100644
--- a/PermissionController/TEST_MAPPING
+++ b/PermissionController/TEST_MAPPING
@@ -13,6 +13,9 @@
"file_patterns": ["res/xml/roles\\.xml"]
},
{
+ "name": "CtsRoleMultiUserTestCases"
+ },
+ {
"name": "PermissionUiTestCases",
"options": [
{
@@ -49,6 +52,9 @@
"file_patterns": ["res/xml/roles\\.xml"]
},
{
+ "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]"
+ },
+ {
"name": "PermissionControllerMockingTests[com.google.android.permission.apex]",
"options": [
{
@@ -111,6 +117,9 @@
"file_patterns": ["res/xml/roles\\.xml"]
},
{
+ "name": "CtsRoleMultiUserTestCases"
+ },
+ {
"name": "PermissionControllerMockingTests",
"options": [
{
@@ -215,6 +224,24 @@
"name": "CtsPermissionUiTestCases[com.google.android.permission.apex]"
}
],
+ "wear-presubmit": [
+ {
+ "name": "CtsPermissionUiTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsRoleTestCases",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
"imports": [
{
"path": "vendor/xts/gts-tests/hostsidetests/permissioncontroller"
diff --git a/PermissionController/jarjar-rules.txt b/PermissionController/jarjar-rules.txt
index 7bfda1ab1..5ecb0241f 100644
--- a/PermissionController/jarjar-rules.txt
+++ b/PermissionController/jarjar-rules.txt
@@ -2,6 +2,10 @@
# RoleParser.applyJarjarTransform(), by adding NO_IFTTT=reason to your commit
# message.
# LINT.IfChange
+rule android.app.admin.flags.*FeatureFlags* com.android.permissioncontroller.jarjar.@0
+rule android.app.admin.flags.FeatureFlags* com.android.permissioncontroller.jarjar.@0
+rule android.app.admin.flags.FeatureFlags com.android.permissioncontroller.jarjar.@0
+rule android.app.admin.flags.Flags com.android.permissioncontroller.jarjar.@0
rule android.app.appfunctions.flags.*FeatureFlags* com.android.permissioncontroller.jarjar.@0
rule android.app.appfunctions.flags.FeatureFlags* com.android.permissioncontroller.jarjar.@0
rule android.app.appfunctions.flags.FeatureFlags com.android.permissioncontroller.jarjar.@0
@@ -22,4 +26,12 @@ rule android.os.*FeatureFlags* com.android.permissioncontroller.jarjar.@0
rule android.os.FeatureFlags* com.android.permissioncontroller.jarjar.@0
rule android.os.FeatureFlags com.android.permissioncontroller.jarjar.@0
rule android.os.Flags com.android.permissioncontroller.jarjar.@0
+rule com.android.permission.flags.*FeatureFlags* com.android.permissioncontroller.jarjar.@0
+rule com.android.permission.flags.FeatureFlags* com.android.permissioncontroller.jarjar.@0
+rule com.android.permission.flags.FeatureFlags com.android.permissioncontroller.jarjar.@0
+rule com.android.permission.flags.Flags com.android.permissioncontroller.jarjar.@0
+rule com.android.settingslib.flags.*FeatureFlags* com.android.permissioncontroller.jarjar.@0
+rule com.android.settingslib.flags.FeatureFlags* com.android.permissioncontroller.jarjar.@0
+rule com.android.settingslib.flags.FeatureFlags com.android.permissioncontroller.jarjar.@0
+rule com.android.settingslib.flags.Flags com.android.permissioncontroller.jarjar.@0
# LINT.ThenChange(PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java:applyJarjarTransform)
diff --git a/PermissionController/lint-baseline.xml b/PermissionController/lint-baseline.xml
index be77a0d18..b1570d0b7 100644
--- a/PermissionController/lint-baseline.xml
+++ b/PermissionController/lint-baseline.xml
@@ -1,16 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
-
- <issue
- id="NewApi"
- message="Class requires API level 34 (current min is 31): `android.app.AppOpsManager.OnOpNotedListener`"
- errorLine1=" AppOpsManager.OnOpNotedListener,"
- errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
- <location
- file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt"
- line="46"
- column="5"/>
- </issue>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name=""
+ variant="all" version="8.4.0-alpha01">
<issue
id="NewApi"
@@ -20,7 +10,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java"
line="504"
- column="31"/>
+ column="31" />
</issue>
<issue
@@ -31,7 +21,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java"
line="505"
- column="34"/>
+ column="34" />
</issue>
<issue
@@ -42,7 +32,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java"
line="509"
- column="41"/>
+ column="41" />
</issue>
<issue
@@ -53,7 +43,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java"
line="513"
- column="41"/>
+ column="41" />
</issue>
<issue
@@ -64,7 +54,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java"
line="515"
- column="59"/>
+ column="59" />
</issue>
<issue
@@ -75,7 +65,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java"
line="518"
- column="42"/>
+ column="42" />
</issue>
<issue
@@ -86,7 +76,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java"
line="519"
- column="42"/>
+ column="42" />
</issue>
<issue
@@ -97,7 +87,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java"
line="521"
- column="18"/>
+ column="18" />
</issue>
<issue
@@ -108,7 +98,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java"
line="522"
- column="29"/>
+ column="29" />
</issue>
<issue
@@ -119,7 +109,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java"
line="709"
- column="31"/>
+ column="31" />
</issue>
<issue
@@ -130,7 +120,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java"
line="710"
- column="34"/>
+ column="34" />
</issue>
<issue
@@ -141,7 +131,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java"
line="714"
- column="41"/>
+ column="41" />
</issue>
<issue
@@ -152,7 +142,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java"
line="718"
- column="41"/>
+ column="41" />
</issue>
<issue
@@ -163,7 +153,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java"
line="720"
- column="59"/>
+ column="59" />
</issue>
<issue
@@ -174,7 +164,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java"
line="722"
- column="67"/>
+ column="67" />
</issue>
<issue
@@ -185,7 +175,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java"
line="722"
- column="42"/>
+ column="42" />
</issue>
<issue
@@ -196,7 +186,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java"
line="724"
- column="18"/>
+ column="18" />
</issue>
<issue
@@ -207,7 +197,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java"
line="725"
- column="29"/>
+ column="29" />
</issue>
<issue
@@ -218,7 +208,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionsFragment.java"
line="159"
- column="35"/>
+ column="35" />
</issue>
<issue
@@ -229,7 +219,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionsFragment.java"
line="367"
- column="35"/>
+ column="35" />
</issue>
<issue
@@ -240,7 +230,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java"
line="463"
- column="31"/>
+ column="31" />
</issue>
<issue
@@ -251,7 +241,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java"
line="464"
- column="34"/>
+ column="34" />
</issue>
<issue
@@ -262,7 +252,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java"
line="468"
- column="41"/>
+ column="41" />
</issue>
<issue
@@ -273,7 +263,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java"
line="472"
- column="41"/>
+ column="41" />
</issue>
<issue
@@ -284,7 +274,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java"
line="474"
- column="59"/>
+ column="59" />
</issue>
<issue
@@ -295,7 +285,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java"
line="476"
- column="67"/>
+ column="67" />
</issue>
<issue
@@ -306,7 +296,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java"
line="476"
- column="42"/>
+ column="42" />
</issue>
<issue
@@ -317,7 +307,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java"
line="478"
- column="18"/>
+ column="18" />
</issue>
<issue
@@ -328,7 +318,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java"
line="479"
- column="29"/>
+ column="29" />
</issue>
<issue
@@ -339,7 +329,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt"
line="1059"
- column="45"/>
+ column="45" />
</issue>
<issue
@@ -350,7 +340,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/HibernatedPackagesLiveData.kt"
line="48"
- column="74"/>
+ column="74" />
</issue>
<issue
@@ -361,7 +351,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/data/HibernatedPackagesLiveData.kt"
line="51"
- column="44"/>
+ column="44" />
</issue>
<issue
@@ -372,7 +362,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardAnimator.kt"
line="47"
- column="37"/>
+ column="37" />
</issue>
<issue
@@ -383,7 +373,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt"
line="653"
- column="29"/>
+ column="29" />
</issue>
<issue
@@ -394,7 +384,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt"
line="662"
- column="18"/>
+ column="18" />
</issue>
<issue
@@ -405,7 +395,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt"
line="1689"
- column="48"/>
+ column="48" />
</issue>
<issue
@@ -416,7 +406,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt"
line="140"
- column="72"/>
+ column="72" />
</issue>
<issue
@@ -427,7 +417,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt"
line="140"
- column="62"/>
+ column="62" />
</issue>
<issue
@@ -438,7 +428,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt"
line="288"
- column="19"/>
+ column="19" />
</issue>
<issue
@@ -449,7 +439,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt"
line="291"
- column="48"/>
+ column="48" />
</issue>
<issue
@@ -460,7 +450,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt"
line="291"
- column="30"/>
+ column="30" />
</issue>
<issue
@@ -471,7 +461,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt"
line="292"
- column="30"/>
+ column="30" />
</issue>
<issue
@@ -482,7 +472,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt"
line="292"
- column="39"/>
+ column="39" />
</issue>
<issue
@@ -493,7 +483,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt"
line="298"
- column="5"/>
+ column="5" />
</issue>
<issue
@@ -504,7 +494,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt"
line="298"
- column="12"/>
+ column="12" />
</issue>
<issue
@@ -515,7 +505,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt"
line="301"
- column="34"/>
+ column="34" />
</issue>
<issue
@@ -526,7 +516,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt"
line="308"
- column="13"/>
+ column="13" />
</issue>
<issue
@@ -537,7 +527,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardAnimator.kt"
line="110"
- column="21"/>
+ column="21" />
</issue>
<issue
@@ -548,7 +538,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardAnimator.kt"
line="110"
- column="46"/>
+ column="46" />
</issue>
<issue
@@ -559,7 +549,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/PermissionAppsFragment.java"
line="114"
- column="35"/>
+ column="35" />
</issue>
<issue
@@ -570,7 +560,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/television/PermissionAppsFragment.java"
line="365"
- column="35"/>
+ column="35" />
</issue>
<issue
@@ -581,7 +571,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt"
line="100"
- column="43"/>
+ column="43" />
</issue>
<issue
@@ -592,7 +582,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java"
line="521"
- column="43"/>
+ column="43" />
</issue>
<issue
@@ -603,7 +593,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java"
line="521"
- column="75"/>
+ column="75" />
</issue>
<issue
@@ -614,7 +604,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java"
line="522"
- column="48"/>
+ column="48" />
</issue>
<issue
@@ -625,7 +615,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java"
line="522"
- column="24"/>
+ column="24" />
</issue>
<issue
@@ -636,7 +626,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java"
line="85"
- column="42"/>
+ column="42" />
</issue>
<issue
@@ -647,7 +637,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java"
line="97"
- column="34"/>
+ column="34" />
</issue>
<issue
@@ -658,7 +648,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java"
line="141"
- column="73"/>
+ column="73" />
</issue>
<issue
@@ -669,7 +659,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java"
line="146"
- column="34"/>
+ column="34" />
</issue>
<issue
@@ -680,7 +670,7 @@
<location
file="packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java"
line="152"
- column="29"/>
+ column="29" />
</issue>
</issues> \ No newline at end of file
diff --git a/PermissionController/res/anim/text_switcher_fade_in.xml b/PermissionController/res/anim/text_switcher_fade_in.xml
new file mode 100644
index 000000000..b9e2812aa
--- /dev/null
+++ b/PermissionController/res/anim/text_switcher_fade_in.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:startOffset="@android:integer/config_shortAnimTime"
+ android:duration="@android:integer/config_shortAnimTime" /> \ No newline at end of file
diff --git a/PermissionController/res/anim/text_switcher_fade_out.xml b/PermissionController/res/anim/text_switcher_fade_out.xml
new file mode 100644
index 000000000..4b7274707
--- /dev/null
+++ b/PermissionController/res/anim/text_switcher_fade_out.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/linear_interpolator"
+ android:fromAlpha="1.0" android:toAlpha="0.0"
+ android:duration="@android:integer/config_shortAnimTime" /> \ No newline at end of file
diff --git a/PermissionController/res/layout-v33/preference_issue_card.xml b/PermissionController/res/layout-v33/preference_issue_card.xml
index e6d749142..107c778a1 100644
--- a/PermissionController/res/layout-v33/preference_issue_card.xml
+++ b/PermissionController/res/layout-v33/preference_issue_card.xml
@@ -13,81 +13,84 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<androidx.constraintlayout.widget.ConstraintLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/issue_card"
- android:clickable="false"
- android:screenReaderFocusable="true"
- style="@style/SafetyCenterCard.Issue">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/issue_card"
+ android:clickable="false"
+ android:screenReaderFocusable="true"
+ style="@style/SafetyCenterCard.Issue">
- <ImageButton
- android:id="@+id/issue_card_dismiss_btn"
- android:src="@drawable/ic_safety_issue_dismiss"
- android:contentDescription="@string/safety_center_issue_card_dismiss_button"
- style="@style/SafetyCenterIssueDismiss" />
+ <ImageButton
+ android:id="@+id/issue_card_dismiss_btn"
+ android:src="@drawable/ic_safety_issue_dismiss"
+ android:contentDescription="@string/safety_center_issue_card_dismiss_button"
+ style="@style/SafetyCenterIssueDismiss" />
- <TextView
- android:id="@+id/issue_card_attribution_title"
- android:text="@string/summary_placeholder"
- android:screenReaderFocusable="false"
- style="@style/SafetyCenterIssueAttributionTitle" />
+ <TextView
+ android:id="@+id/issue_card_attribution_title"
+ android:text="@string/summary_placeholder"
+ android:screenReaderFocusable="false"
+ style="@style/SafetyCenterIssueAttributionTitle" />
- <TextView
- android:id="@+id/issue_card_title"
- android:text="@string/summary_placeholder"
- android:screenReaderFocusable="false"
- style="@style/SafetyCenterIssueTitle" />
+ <TextView
+ android:id="@+id/issue_card_title"
+ android:text="@string/summary_placeholder"
+ android:screenReaderFocusable="false"
+ style="@style/SafetyCenterIssueTitle" />
- <TextView
- android:id="@+id/issue_card_subtitle"
- android:text="@string/summary_placeholder"
- android:screenReaderFocusable="false"
- style="@style/SafetyCenterIssueSubtitle" />
+ <TextView
+ android:id="@+id/issue_card_subtitle"
+ android:text="@string/summary_placeholder"
+ android:screenReaderFocusable="false"
+ style="@style/SafetyCenterIssueSubtitle" />
- <TextView
- android:id="@+id/issue_card_summary"
- android:text="@string/summary_placeholder"
- android:screenReaderFocusable="false"
- style="@style/SafetyCenterIssueSummary" />
+ <TextView
+ android:id="@+id/issue_card_summary"
+ android:text="@string/summary_placeholder"
+ android:screenReaderFocusable="false"
+ style="@style/SafetyCenterIssueSummary" />
- <include
- android:id="@+id/issue_card_action_button_list"
- layout="?attr/scActionButtonListLayout"/>
+ <include
+ android:id="@+id/issue_card_action_button_list"
+ layout="?attr/scActionButtonListLayout"/>
- <com.android.permissioncontroller.permission.ui.v33.widget.SafetyProtectionSectionView
- android:id="@+id/issue_card_protected_by_android"
- android:importantForAccessibility="no"
- style="@style/SafetyCenterIssueSafetyProtectionSection" />
+ <com.android.permissioncontroller.permission.ui.v33.widget.SafetyProtectionSectionView
+ android:id="@+id/issue_card_protected_by_android"
+ android:importantForAccessibility="no"
+ style="@style/SafetyCenterIssueSafetyProtectionSection" />
- <ImageView
- android:id="@+id/resolved_issue_image"
- android:src="@drawable/safety_center_issue_resolved_avd"
- android:importantForAccessibility="no"
- style="@style/SafetyCenterIssueCardResolvedImage" />
+ <ImageView
+ android:id="@+id/resolved_issue_image"
+ android:src="@drawable/safety_center_issue_resolved_avd"
+ android:importantForAccessibility="no"
+ style="@style/SafetyCenterIssueCardResolvedImage" />
- <TextView
- android:id="@+id/resolved_issue_text"
- android:text="@string/safety_center_resolved_issue_fallback"
- style="@style/SafetyCenterIssueCardResolvedTitle" />
+ <TextView
+ android:id="@+id/resolved_issue_text"
+ android:text="@string/safety_center_resolved_issue_fallback"
+ style="@style/SafetyCenterIssueCardResolvedTitle" />
- <!-- This group doesn't contain issue_card_attribution_title, issue_card_dismiss_btn,
- issue_card_subtitle or issue_card_protected_by_android since the version of
- ConstraintLayout we're using doesn't allow us to override the group's visibility on
- individual group members. See b/242705351 for context. -->
- <androidx.constraintlayout.widget.Group
- android:id="@+id/default_issue_content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="visible"
- app:constraint_referenced_ids="issue_card_title,issue_card_summary,issue_card_action_button_list" />
+ <!-- This group doesn't contain issue_card_attribution_title, issue_card_dismiss_btn,
+ issue_card_subtitle or issue_card_protected_by_android since the version of
+ ConstraintLayout we're using doesn't allow us to override the group's visibility on
+ individual group members. See b/242705351 for context. -->
+ <androidx.constraintlayout.widget.Group
+ android:id="@+id/default_issue_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="visible"
+ app:constraint_referenced_ids="issue_card_title,issue_card_summary,issue_card_action_button_list" />
- <androidx.constraintlayout.widget.Group
- android:id="@+id/resolved_issue_content"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:visibility="gone"
- app:constraint_referenced_ids="resolved_issue_image,resolved_issue_text" />
+ <androidx.constraintlayout.widget.Group
+ android:id="@+id/resolved_issue_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ app:constraint_referenced_ids="resolved_issue_image,resolved_issue_text" />
-</androidx.constraintlayout.widget.ConstraintLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/PermissionController/res/layout-v33/preference_more_issues_card.xml b/PermissionController/res/layout-v33/preference_more_issues_card.xml
index c93125762..d0ac6b1f5 100644
--- a/PermissionController/res/layout-v33/preference_more_issues_card.xml
+++ b/PermissionController/res/layout-v33/preference_more_issues_card.xml
@@ -13,8 +13,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-
-<com.android.permissioncontroller.safetycenter.ui.view.MoreIssuesHeaderView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/issue_card"
- style="@style/SafetyCenterMoreIssuesCollapsed"/>
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.permissioncontroller.safetycenter.ui.view.MoreIssuesHeaderView
+ android:id="@+id/more_issues_card"
+ style="@style/SafetyCenterMoreIssuesCollapsed"/>
+</FrameLayout> \ No newline at end of file
diff --git a/PermissionController/res/layout-v33/preference_safety_status.xml b/PermissionController/res/layout-v33/preference_safety_status.xml
index 42bf1c22d..17adc035f 100644
--- a/PermissionController/res/layout-v33/preference_safety_status.xml
+++ b/PermissionController/res/layout-v33/preference_safety_status.xml
@@ -14,7 +14,12 @@
~ limitations under the License.
-->
-<com.android.permissioncontroller.safetycenter.ui.view.StatusCardView
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:clickable="false"
- style="@style/SafetyCenterCard.Status" /> \ No newline at end of file
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.permissioncontroller.safetycenter.ui.view.StatusCardView
+ android:id="@+id/status_card"
+ android:clickable="false"
+ style="@style/SafetyCenterCard.Status" />
+</FrameLayout> \ No newline at end of file
diff --git a/PermissionController/res/layout-v33/view_status_card.xml b/PermissionController/res/layout-v33/view_status_card.xml
index 4915347be..d8ca8b7ea 100644
--- a/PermissionController/res/layout-v33/view_status_card.xml
+++ b/PermissionController/res/layout-v33/view_status_card.xml
@@ -30,15 +30,34 @@
android:id="@+id/status_title_and_summary"
style="?attr/scStatusTitleAndSummaryContainerStyle">
- <TextView
+ <TextSwitcher
android:id="@+id/status_title"
- android:text="@string/summary_placeholder"
- style="@style/SafetyCenterStatusTitle" />
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inAnimation="@anim/text_switcher_fade_in"
+ android:outAnimation="@anim/text_switcher_fade_out">
+ <TextView
+ android:text="@string/summary_placeholder"
+ style="@style/SafetyCenterStatusTitle" />
+ <TextView
+ android:text="@string/summary_placeholder"
+ style="@style/SafetyCenterStatusTitle" />
+ </TextSwitcher>
- <TextView
+
+ <TextSwitcher
android:id="@+id/status_summary"
- android:text="@string/summary_placeholder"
- style="@style/SafetyCenterStatusSummary" />
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:inAnimation="@anim/text_switcher_fade_in"
+ android:outAnimation="@anim/text_switcher_fade_out">
+ <TextView
+ android:text="@string/summary_placeholder"
+ style="@style/SafetyCenterStatusSummary" />
+ <TextView
+ android:text="@string/summary_placeholder"
+ style="@style/SafetyCenterStatusSummary" />
+ </TextSwitcher>
</LinearLayout>
<androidx.constraintlayout.widget.Barrier
diff --git a/PermissionController/res/layout-v35/app_permission_footer_link_preference.xml b/PermissionController/res/layout-v35/app_permission_footer_link_preference.xml
new file mode 100644
index 000000000..75381e762
--- /dev/null
+++ b/PermissionController/res/layout-v35/app_permission_footer_link_preference.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ ~ A copy of PermissionPreference, with custom styles for some elements
+ -->
+<com.android.permissioncontroller.permission.ui.handheld.v35.DrawableStateLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/AppPermissionFooterLinkPreferenceRootLayoutStyle">
+
+ <RelativeLayout
+ style="@style/AppPermissionFooterLinkPreferenceTextLayoutStyle">
+
+ <TextView
+ android:id="@android:id/summary"
+ style="@style/AppPermissionFooterLinkPreferenceSummaryStyle"/>
+
+ </RelativeLayout>
+
+</com.android.permissioncontroller.permission.ui.handheld.v35.DrawableStateLinearLayout>
diff --git a/PermissionController/res/layout-v35/nav_host_fragment.xml b/PermissionController/res/layout-v35/nav_host_fragment.xml
new file mode 100644
index 000000000..5c2850b7a
--- /dev/null
+++ b/PermissionController/res/layout-v35/nav_host_fragment.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+
+ <fragment
+ android:id="@+id/nav_host_fragment"
+ android:name="androidx.navigation.fragment.NavHostFragment"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:defaultNavHost="true" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/PermissionController/res/layout-v35/permission_preference_selector_with_widget.xml b/PermissionController/res/layout-v35/permission_preference_selector_with_widget.xml
new file mode 100644
index 000000000..230e51fc3
--- /dev/null
+++ b/PermissionController/res/layout-v35/permission_preference_selector_with_widget.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<!-- A direct copy of the following layout, but with overlayable styles:
+ ~ SettingsLib/SelectorWithWidgetPreference/res/layout-v33/preference_selector_with_widget.xml
+ -->
+<com.android.permissioncontroller.permission.ui.handheld.v35.DrawableStateLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/PermissionSelectorWithWidgetPreferenceRootLayoutStyle">
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ style="@style/PermissionSelectorWithWidgetPreferenceWidgetFrameStyle" />
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ style="@style/PermissionSelectorWithWidgetPreferenceIconFrameStyle">
+
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ style="@style/PermissionSelectorWithWidgetPreferenceIconStyle" />
+ </LinearLayout>
+
+ <LinearLayout style="@style/PermissionSelectorWithWidgetPreferenceTextContainerStyle">
+
+ <TextView
+ android:id="@android:id/title"
+ style="@style/PermissionSelectorWithWidgetPreferenceTitleStyle" />
+
+ <LinearLayout
+ android:id="@+id/summary_container"
+ style="@style/PermissionSelectorWithWidgetPreferenceSummaryContainerStyle">
+
+ <TextView
+ android:id="@android:id/summary"
+ style="@style/PermissionSelectorWithWidgetPreferenceSummaryStyle" />
+
+ <TextView
+ android:id="@+id/appendix"
+ style="@style/PermissionSelectorWithWidgetPreferenceAppendixStyle" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/selector_extra_widget_container"
+ style="@style/PermissionSelectorWithWidgetPreferenceExtraWidgetContainerStyle">
+
+ <View style="@style/PermissionSelectorWithWidgetPreferenceExtraWidgetDividerStyle" />
+
+ <ImageView
+ android:id="@+id/selector_extra_widget"
+ android:contentDescription="@string/settings_label"
+ style="@style/PermissionSelectorWithWidgetPreferenceExtraWidgetImageStyle" />
+ </LinearLayout>
+</com.android.permissioncontroller.permission.ui.handheld.v35.DrawableStateLinearLayout>
diff --git a/PermissionController/res/layout-v35/permission_preference_two_target.xml b/PermissionController/res/layout-v35/permission_preference_two_target.xml
new file mode 100644
index 000000000..906393b3c
--- /dev/null
+++ b/PermissionController/res/layout-v35/permission_preference_two_target.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<!-- A direct copy of the following layout, but with overlayable styles:
+ ~ SettingsLib/TwoTargetPreference/res/layout-v33/preference_two_target.xml
+ -->
+
+<!-- Based off preference_material_settings.xml except that ripple on only on the left side. -->
+<com.android.permissioncontroller.permission.ui.handheld.v35.DrawableStateLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/PermissionTwoTargetPreferenceRootLayoutStyle">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ style="@style/PermissionTwoTargetPreferenceIconFrameStyle">
+
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ style="@style/PermissionTwoTargetPreferenceIconStyle"/>
+
+ </LinearLayout>
+
+ <RelativeLayout style="@style/PermissionTwoTargetPreferenceTextContainerStyle">
+
+ <TextView
+ android:id="@android:id/title"
+ style="@style/PermissionTwoTargetPreferenceTitleStyle"/>
+
+ <TextView
+ android:id="@android:id/summary"
+ style="@style/PermissionTwoTargetPreferenceSummaryStyle"/>
+
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@+id/two_target_divider"
+ style="@style/PermissionTwoTargetPreferenceDividerContainerStyle">
+ <View style="@style/PermissionTwoTargetPreferenceDividerStyle" />
+ </LinearLayout>
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ style="@style/PermissionTwoTargetPreferenceWidgetFrameStyle" />
+
+</com.android.permissioncontroller.permission.ui.handheld.v35.DrawableStateLinearLayout>
diff --git a/PermissionController/res/layout-v35/permission_preference_widget_radiobutton.xml b/PermissionController/res/layout-v35/permission_preference_widget_radiobutton.xml
new file mode 100644
index 000000000..10e311110
--- /dev/null
+++ b/PermissionController/res/layout-v35/permission_preference_widget_radiobutton.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<!-- A direct copy of the following layout, but with overlayable styles:
+ ~ SettingsLib/SelectorWithWidgetPreference/res/layout/preference_widget_radiobutton.xml
+ -->
+<RadioButton
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/checkbox"
+ style="@style/PermissionSelectorWithWidgetPreferenceWidgetRadioButton" />
diff --git a/PermissionController/res/layout/permission_history_widget.xml b/PermissionController/res/layout/permission_history_widget.xml
index 9bdef7200..f98a1c14b 100644
--- a/PermissionController/res/layout/permission_history_widget.xml
+++ b/PermissionController/res/layout/permission_history_widget.xml
@@ -28,6 +28,8 @@
android:layout_height="wrap_content"
android:minWidth="60dp"
android:layout_marginTop="19dp"
+ android:textAppearance="?android:attr/textAppearanceListItemSecondary"
+ android:textColor="?android:attr/textColorSecondary"
/>
<LinearLayout
diff --git a/PermissionController/res/values-af-v33/strings.xml b/PermissionController/res/values-af-v33/strings.xml
index 6d6a118cd..3ad0e7cb4 100644
--- a/PermissionController/res/values-af-v33/strings.xml
+++ b/PermissionController/res/values-af-v33/strings.xml
@@ -16,8 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="role_dialer_request_description" msgid="6188305064871543419">"Hierdie program sal toegelaat word om vir jou kennisgewings te stuur, en sal toegang tot jou kamera, kontakte, mikrofoon, foon en SMS\'e kry"</string>
- <string name="role_sms_request_description" msgid="1506966389698625395">"Hierdie program sal toegelaat word om vir jou kennisgewings te stuur, en sal toegang kry tot jou kamera, kontakte, lêers, mikrofoon, foon en SMS\'e"</string>
+ <string name="role_dialer_request_description" msgid="6188305064871543419">"Hierdie app sal toegelaat word om vir jou kennisgewings te stuur, en sal toegang tot jou kamera, kontakte, mikrofoon, foon en SMS\'e kry"</string>
+ <string name="role_sms_request_description" msgid="1506966389698625395">"Hierdie app sal toegelaat word om vir jou kennisgewings te stuur, en sal toegang kry tot jou kamera, kontakte, lêers, mikrofoon, foon en SMS\'e"</string>
<string name="permission_description_summary_storage" msgid="1917071243213043858">"Programme met hierdie toestemming het toegang tot alle lêers op hierdie toestel"</string>
<string name="work_policy_title" msgid="832967780713677409">"Jou werkbeleidinligting"</string>
<string name="work_policy_summary" msgid="3886113358084963931">"Instellings wat deur jou IT-admin bestuur word"</string>
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Meer waarskuwings"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Waarskuwings wat toegemaak is"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Vou uit om nog een waarskuwing te sien}other{Vou uit om nog # waarskuwings te sien}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Vou in"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Waarskuwing. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Handeling is voltooi"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Gaan instellings na wat jou toestel kan beskerm"</string>
diff --git a/PermissionController/res/values-af/strings.xml b/PermissionController/res/values-af/strings.xml
index 728ef11ef..fdbf4be1b 100644
--- a/PermissionController/res/values-af/strings.xml
+++ b/PermissionController/res/values-af/strings.xml
@@ -27,7 +27,7 @@
<string name="on" msgid="280241003226755921">"Aan"</string>
<string name="off" msgid="1438489226422866263">"Af"</string>
<string name="uninstall_or_disable" msgid="4496612999740858933">"Deïnstalleer of deaktiveer"</string>
- <string name="app_not_found_dlg_title" msgid="6029482906093859756">"Program nie gevind nie"</string>
+ <string name="app_not_found_dlg_title" msgid="6029482906093859756">"App nie gevind nie"</string>
<string name="grant_dialog_button_deny" msgid="88262611492697192">"Moenie toelaat nie"</string>
<string name="grant_dialog_button_deny_and_dont_ask_again" msgid="1748925431574312595">"Moenie toelaat nie en moenie weer vra nie"</string>
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Hou \"Terwyl die app gebruik word\""</string>
@@ -44,7 +44,7 @@
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> van <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
<string name="permission_warning_template" msgid="2247087781222679458">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om <xliff:g id="ACTION">%2$s</xliff:g>?"</string>
<string name="permission_add_background_warning_template" msgid="1812914855915092273">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; altyd toe om <xliff:g id="ACTION">%2$s</xliff:g>?"</string>
- <string name="allow_permission_foreground_only" msgid="116465816039675404">"Net terwyl program gebruik word"</string>
+ <string name="allow_permission_foreground_only" msgid="116465816039675404">"Net terwyl app gebruik word"</string>
<string name="allow_permission_always" msgid="5194342531206054051">"Altyd"</string>
<string name="deny_permission_deny_and_dont_ask_again" msgid="6106035221490102341">"Moenie toelaat nie en moenie weer vra nie"</string>
<string name="permission_revoked_count" msgid="4785082705441547086">"<xliff:g id="COUNT">%1$d</xliff:g> is gedeaktiveer"</string>
@@ -80,9 +80,9 @@
<string name="additional_permissions" msgid="5801285469338873430">"Bykomende toestemmings"</string>
<string name="app_permissions_info_button_label" msgid="7633312050729974623">"Maak programinligting oop"</string>
<string name="additional_permissions_more" msgid="5681220714755304407">"{count,plural, =1{Nog #}other{Nog #}}"</string>
- <string name="old_sdk_deny_warning" msgid="2382236998845153919">"Hierdie program is vir \'n ouer weergawe van Android ontwerp. As toestemming geweier word, kan dit veroorsaak dat dit dalk nie meer soos bedoel werk nie."</string>
- <string name="storage_supergroup_warning_allow" msgid="103093462784523190">"Hierdie program is vir \'n ouer weergawe van Android ontwerp. As jy hierdie toestemming toelaat, sal toegang tot alle berging (insluitend foto\'s, video\'s, musiek, oudio en ander lêers) toegelaat word."</string>
- <string name="storage_supergroup_warning_deny" msgid="6420765672683284347">"Hierdie program is vir \'n ouer weergawe van Android ontwerp. As jy hierdie toestemming weier, sal toegang tot alle berging (insluitend foto\'s, video\'s, musiek, oudio en ander lêers) geweier word."</string>
+ <string name="old_sdk_deny_warning" msgid="2382236998845153919">"Hierdie app is vir \'n ouer weergawe van Android ontwerp. As toestemming geweier word, kan dit veroorsaak dat dit dalk nie meer soos bedoel werk nie."</string>
+ <string name="storage_supergroup_warning_allow" msgid="103093462784523190">"Hierdie app is vir \'n ouer weergawe van Android ontwerp. As jy hierdie toestemming toelaat, sal toegang tot alle berging (insluitend foto\'s, video\'s, musiek, oudio en ander lêers) toegelaat word."</string>
+ <string name="storage_supergroup_warning_deny" msgid="6420765672683284347">"Hierdie app is vir \'n ouer weergawe van Android ontwerp. As jy hierdie toestemming weier, sal toegang tot alle berging (insluitend foto\'s, video\'s, musiek, oudio en ander lêers) geweier word."</string>
<string name="default_permission_description" msgid="4624464917726285203">"voer \'n onbekende handeling uit"</string>
<string name="app_permissions_group_summary" msgid="8788419008958284002">"<xliff:g id="COUNT_0">%1$d</xliff:g> van <xliff:g id="COUNT_1">%2$d</xliff:g> programme toegelaat"</string>
<string name="app_permissions_group_summary2" msgid="4329922444840521150">"<xliff:g id="COUNT_0">%1$d</xliff:g>/<xliff:g id="COUNT_1">%2$d</xliff:g> programme toegelaat"</string>
@@ -95,9 +95,9 @@
<string name="location_settings" msgid="3624412509133422562">"Ligginginstellings"</string>
<string name="location_warning" msgid="2381649060929040962">"<xliff:g id="APP_NAME">%1$s</xliff:g> is \'n verskaffer van liggingdienste vir hierdie toestel. Liggingtoegang kan vanuit ligginginstellings gewysig word."</string>
<string name="system_warning" msgid="1173400963234358816">"As jy hierdie toestemming weier, sal basiese kenmerke van jou toestel dalk nie meer soos bedoel werk nie."</string>
- <string name="deny_read_media_visual_warning" msgid="3982586279917232827">"Hierdie program is vir \'n ouer weergawe van Android ontwerp. As jy nie vir hierdie program toegang tot foto\'s en video\'s gee nie, sal dit ook nie toegang tot musiek en ander oudio hê nie."</string>
- <string name="deny_read_media_aural_warning" msgid="8928699919508646732">"Hierdie program is vir \'n ouer weergawe van Android ontwerp. As jy nie vir hierdie program toegang tot musiek en ander oudio gee nie, sal dit ook nie toegang tot foto\'s en video\'s hê nie."</string>
- <string name="cdm_profile_revoke_warning" msgid="4443893270719106700">"As jy hierdie toestemming weier, sal sommige kenmerke van jou toestel wat deur hierdie program bestuur word dalk nie meer soos bedoel werk nie."</string>
+ <string name="deny_read_media_visual_warning" msgid="3982586279917232827">"Hierdie app is vir \'n ouer weergawe van Android ontwerp. As jy nie vir hierdie app toegang tot foto\'s en video\'s gee nie, sal dit ook nie toegang tot musiek en ander oudio hê nie."</string>
+ <string name="deny_read_media_aural_warning" msgid="8928699919508646732">"Hierdie app is vir \'n ouer weergawe van Android ontwerp. As jy nie vir hierdie app toegang tot musiek en ander oudio gee nie, sal dit ook nie toegang tot foto\'s en video\'s hê nie."</string>
+ <string name="cdm_profile_revoke_warning" msgid="4443893270719106700">"As jy hierdie toestemming weier, sal sommige kenmerke van jou toestel wat deur hierdie app bestuur word dalk nie meer soos bedoel werk nie."</string>
<string name="permission_summary_enforced_by_policy" msgid="4443598170942950519">"Afgedwing deur beleid"</string>
<string name="permission_summary_disabled_by_policy_background_only" msgid="221995005556362660">"Agtergrondtoegang is gedeaktiveer volgens beleid"</string>
<string name="permission_summary_enabled_by_policy_background_only" msgid="8287675974767104279">"Agtergrondtoegang is geaktiveer volgens beleid"</string>
@@ -118,12 +118,12 @@
<string name="other_permissions" msgid="2901186127193849594">"Ander programvermoëns"</string>
<string name="permission_request_title" msgid="8790310151025020126">"Toestemmingsversoek"</string>
<string name="permission_review_title_template_install" msgid="1284337937156289081">"Kies waartoe &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang mag kry"</string>
- <string name="permission_review_title_template_update" msgid="3232333580548588657">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; is opgedateer. Kies waartoe hierdie program toegang mag kry."</string>
+ <string name="permission_review_title_template_update" msgid="3232333580548588657">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; is opgedateer. Kies waartoe hierdie app toegang mag kry."</string>
<string name="review_button_cancel" msgid="2191147944056548886">"Kanselleer"</string>
<string name="review_button_continue" msgid="2527918375047602199">"Gaan voort"</string>
<string name="new_permissions_category" msgid="552995090178417611">"Nuwe toestemmings"</string>
<string name="current_permissions_category" msgid="4292990083585728880">"Huidige toestemmings"</string>
- <string name="message_staging" msgid="9110563899955511866">"Voer tans program uit …"</string>
+ <string name="message_staging" msgid="9110563899955511866">"Voer tans app uit …"</string>
<string name="app_name_unknown" msgid="1319665005754048952">"Onbekend"</string>
<string name="permission_usage_title" msgid="1568233336351734538">"Privaatheidkontroleskerm"</string>
<string name="auto_permission_usage_summary" msgid="7335667266743337075">"Bekyk watter programme onlangs toestemmings gebruik het"</string>
@@ -133,7 +133,7 @@
<string name="perm_usage_adv_info_summary_more_items" msgid="949055326299562218">"<xliff:g id="PERMGROUP_0">%1$s</xliff:g>, <xliff:g id="PERMGROUP_1">%2$s</xliff:g> en nog <xliff:g id="NUM">%3$s</xliff:g>"</string>
<string name="permission_group_usage_subtitle_24h" msgid="5120155996322114181">"Tydlyn van wanneer programme jou <xliff:g id="PERMGROUP">%1$s</xliff:g> in die afgelope 24 uur gebruik het"</string>
<string name="permission_group_usage_subtitle_7d" msgid="1465828402260324654">"Tydlyn van wanneer programme jou <xliff:g id="PERMGROUP">%1$s</xliff:g> in die afgelope 7 dae gebruik het"</string>
- <string name="permission_usage_access_dialog_subtitle" msgid="4171772805196955753">"Wanneer hierdie program jou <xliff:g id="PERMGROUP">%1$s</xliff:g>-toestemming gebruik het"</string>
+ <string name="permission_usage_access_dialog_subtitle" msgid="4171772805196955753">"Wanneer hierdie app jou <xliff:g id="PERMGROUP">%1$s</xliff:g>-toestemming gebruik het"</string>
<string name="permission_usage_access_dialog_learn_more" msgid="7121468469493184613">"Kom meer te wete"</string>
<string name="learn_more_content_description" msgid="8673699744544502539">"Kom meer te wete oor <xliff:g id="PERMGROUP">%1$s</xliff:g>"</string>
<string name="manage_permission_summary" msgid="4117555482684114317">"Beheer apptoegang tot jou <xliff:g id="PERMGROUP">%1$s</xliff:g>"</string>
@@ -202,19 +202,21 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g>toegang vir hierdie app op <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Sien al <xliff:g id="APP">%1$s</xliff:g> se toestemmings"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Sien alle apps met hierdie toestemming"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Inligting"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Instellings"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Wys Assistent-mikrofoongebruik"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Instellings vir ongebruikte apps"</string>
- <string name="auto_revoke_label" msgid="5068393642936571656">"Verwyder toestemmings as program nie gebruik word nie"</string>
+ <string name="auto_revoke_label" msgid="5068393642936571656">"Verwyder toestemmings as app nie gebruik word nie"</string>
<string name="unused_apps_label" msgid="2595428768404901064">"Verwyder toestemmings en maak spasie beskikbaar"</string>
<string name="unused_apps_label_v2" msgid="7058776770056517980">"Onderbreek appaktiwiteit as ongebruik"</string>
<string name="unused_apps_label_v3" msgid="693340578642156657">"Bestuur app indien ongebruik"</string>
<string name="unused_apps_summary" msgid="8839466950318403115">"Verwyder toestemmings, vee tydelike lêers uit, en stop kennisgewings"</string>
<string name="unused_apps_summary_v2" msgid="5011313200815115802">"Verwyder toestemmings, vee tydelike lêers uit, stop kennisgewings en argiveer die app"</string>
- <string name="auto_revoke_summary" msgid="5867548789805911683">"Om jou data te beskerm, sal toestemmings vir hierdie program verwyder word as die program \'n paar maande nie gebruik word nie."</string>
+ <string name="auto_revoke_summary" msgid="5867548789805911683">"Om jou data te beskerm, sal toestemmings vir hierdie app verwyder word as die app \'n paar maande nie gebruik word nie."</string>
<string name="auto_revoke_summary_with_permissions" msgid="389712086597285013">"Om jou data te beskerm, sal die volgende toestemmings verwyder word as dit vir \'n paar maande nie gebruik word nie: <xliff:g id="PERMS">%1$s</xliff:g>."</string>
<string name="auto_revoked_apps_page_summary" msgid="6594753657893756536">"Om jou data te beskerm, is toestemmings verwyder van programme wat jy \'n paar maande gelede laas gebruik het."</string>
- <string name="auto_revoke_open_app_message" msgid="8075556291711205039">"Maak die program oop as jy toestemmings weer wil toelaat."</string>
- <string name="auto_revoke_disabled" msgid="8697684442991567188">"Outomatiese verwydering is tans gedeaktiveer vir hierdie program."</string>
+ <string name="auto_revoke_open_app_message" msgid="8075556291711205039">"Maak die app oop as jy toestemmings weer wil toelaat."</string>
+ <string name="auto_revoke_disabled" msgid="8697684442991567188">"Outomatiese verwydering is tans gedeaktiveer vir hierdie app."</string>
<string name="auto_revocable_permissions_none" msgid="8334929619113991466">"Geen toestemmings wat outomaties herroep kan word, is tans verleen nie"</string>
<string name="auto_revocable_permissions_one" msgid="5299112369449458176">"<xliff:g id="PERM">%1$s</xliff:g> toestemming sal verwyder word."</string>
<string name="auto_revocable_permissions_two" msgid="4874067408752041716">"<xliff:g id="PERM_0">%1$s</xliff:g> en <xliff:g id="PERM_1">%2$s</xliff:g> toestemmings sal verwyder word."</string>
@@ -227,10 +229,10 @@
<string name="unused_apps_page_summary" msgid="1867593913217272155">"As \'n app vir \'n paar maande nie gebruik word nie:\n\n• Word toestemmings verwyder om jou privaatheid te beskerm\n• Word kennisgewings gestop om batterykrag te bespaar\n• Word tydelike lêers verwyder om spasie beskikbaar te maak\n\nMaak die app oop om weer toestemmings en kennisgewings toe te laat."</string>
<string name="unused_apps_page_tv_summary" msgid="2624911608663778308">"As ’n app ’n maand lank nie gebruik is nie:\n\n• Toestemmings word verwyder om jou data te beskerm\n• Tydelike lêers word verwyder om spasie beskikbaar te maak\n\nMaak die app oop om weer toestemmings te gee."</string>
<string name="last_opened_category_title" msgid="8796557894614236128">"{count,plural, =1{Meer as # maand gelede laas oopgemaak}other{Meer as # maande gelede laas oopgemaak}}"</string>
- <string name="last_opened_summary" msgid="5248984030024968808">"Program is <xliff:g id="DATE">%s</xliff:g> laas oopgemaak"</string>
+ <string name="last_opened_summary" msgid="5248984030024968808">"App is <xliff:g id="DATE">%s</xliff:g> laas oopgemaak"</string>
<string name="last_opened_summary_short" msgid="1646067226191176825">"<xliff:g id="DATE">%s</xliff:g> laas oopgemaak"</string>
- <string name="app_permission_footer_special_file_access" msgid="1884202176147657788">"As jy toelaat dat alle lêers bestuur word, kan hierdie program enige lêers in gedeelde bergingspasie op hierdie toestel en gekoppelde bergingtoestelle kry, wysig en uitvee. Die program kan toegang tot lêers kry sonder om jou te vra."</string>
- <string name="special_file_access_dialog" msgid="583804114020740610">"Laat hierdie program toe om lêers op die toestel en enige gekoppelde bergingtoestelle te kry, te wysig en uit te vee? Hierdie program kan toegang tot lêers kry sonder om jou te vra."</string>
+ <string name="app_permission_footer_special_file_access" msgid="1884202176147657788">"As jy toelaat dat alle lêers bestuur word, kan hierdie app enige lêers in gedeelde bergingspasie op hierdie toestel en gekoppelde bergingtoestelle kry, wysig en uitvee. Die app kan toegang tot lêers kry sonder om jou te vra."</string>
+ <string name="special_file_access_dialog" msgid="583804114020740610">"Laat hierdie app toe om lêers op die toestel en enige gekoppelde bergingtoestelle te kry, te wysig en uit te vee? Hierdie app kan toegang tot lêers kry sonder om jou te vra."</string>
<string name="permission_description_summary_generic" msgid="5401399408814903391">"Apps met hierdie toestemming <xliff:g id="DESCRIPTION">%1$s</xliff:g>"</string>
<string name="permission_description_summary_activity_recognition" msgid="2652850576497070146">"Programme met hierdie toestemming kan ingaan by fisieke aktiwiteit, soos stap, fietsry, ry, treëtelling, en meer"</string>
<string name="permission_description_summary_calendar" msgid="103329982944411010">"Programme met hierdie toestemming kan toegang tot jou kalender kry"</string>
@@ -264,12 +266,12 @@
<string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuut}other{# minute}}"</string>
<string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekonde}other{# sekondes}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Toestemmingonthounotas"</string>
- <string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ongebruikte program"</string>
+ <string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ongebruikte app"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ongebruikte programme"</string>
<string name="auto_revoke_permission_reminder_notification_content" msgid="4492228990462107487">"Toestemmings is verwyder om jou privaatheid te beskerm. Tik om na te gaan"</string>
<string name="auto_revoke_permission_notification_title" msgid="2629844160853454657">"Toestemmings vir ongebruikte programme verwyder"</string>
<string name="auto_revoke_permission_notification_content" msgid="5125990886047799375">"Sommige programme is \'n paar maande laas gebruik. Tik om te na te gaan."</string>
- <string name="unused_apps_notification_title" msgid="4314832015894238019">"{count,plural, =1{# ongebruikte program}other{# ongebruikte programme}}"</string>
+ <string name="unused_apps_notification_title" msgid="4314832015894238019">"{count,plural, =1{# ongebruikte app}other{# ongebruikte apps}}"</string>
<string name="unused_apps_notification_content" msgid="9195026773244581246">"Toestemmings en tydelike lêers is verwyder en kennisgewings is gestop. Tik om na te gaan."</string>
<string name="unused_apps_safety_center_card_title" msgid="5638409355530099149">"Gaan programme na waarvoor toestemmings verwyder is"</string>
<string name="unused_apps_safety_center_card_content" msgid="1088557243627427820">"Toestemmings en tydelike lêers is verwyder en kennisgewings is gestop vir programme wat jy ’n ruk lank nie gebruik het nie."</string>
@@ -278,7 +280,7 @@
<string name="post_drive_permission_decision_reminder_summary_1_app_1_permission" msgid="670521503734140711">"Terwyl jy bestuur het, het jy <xliff:g id="APP">%1$s</xliff:g> toegang gegee tot <xliff:g id="PERMISSION">%2$s</xliff:g>"</string>
<string name="post_drive_permission_decision_reminder_summary_1_app_2_permissions" msgid="671791184670801301">"Terwyl jy bestuur het, het jy <xliff:g id="APP">%1$s</xliff:g> toegang gegee tot <xliff:g id="PERMISSION_1">%2$s</xliff:g> en <xliff:g id="PERMISSION_2">%3$s</xliff:g>"</string>
<string name="post_drive_permission_decision_reminder_summary_1_app_multi_permission" msgid="4080701771111456927">"Terwyl jy bestuur het, het jy <xliff:g id="COUNT">%1$d</xliff:g> toestemmings aan <xliff:g id="APP">%2$s</xliff:g> gegee"</string>
- <string name="post_drive_permission_decision_reminder_summary_multi_apps" msgid="5253882771252863902">"{count,plural, =1{Terwyl jy bestuur het, het jy <xliff:g id="APP_0">%1$s</xliff:g> en # ander program toegang gegee}other{Terwyl jy bestuur het, het jy <xliff:g id="APP_1">%1$s</xliff:g> en # ander programme toegang gegee}}"</string>
+ <string name="post_drive_permission_decision_reminder_summary_multi_apps" msgid="5253882771252863902">"{count,plural, =1{Terwyl jy bestuur het, het jy <xliff:g id="APP_0">%1$s</xliff:g> en # ander app toegang gegee}other{Terwyl jy bestuur het, het jy <xliff:g id="APP_1">%1$s</xliff:g> en # ander apps toegang gegee}}"</string>
<string name="go_to_settings" msgid="1053735612211228335">"Gaan na Instellings"</string>
<string name="auto_revoke_setting_subtitle" msgid="8631720570723050460">"Sommige programme is \'n paar maande laas gebruik"</string>
<string name="permissions_removed_category_title" msgid="1064754271178447643">"Toestemmings wat verwyder is"</string>
@@ -287,7 +289,7 @@
<string name="months_ago" msgid="1766026492610646354">"<xliff:g id="COUNT">%1$d</xliff:g> maande gelede"</string>
<string name="auto_revoke_preference_summary" msgid="5517958331781391481">"Toestemmings is verwyder om jou privaatheid te beskerm"</string>
<string name="background_location_access_reminder_notification_title" msgid="1140797924301941262">"<xliff:g id="APP_NAME">%s</xliff:g> het jou ligging op die agtergrond gekry"</string>
- <string name="background_location_access_reminder_notification_content" msgid="7787084707336546245">"Hierdie program het altyd toegang tot jou ligging. Tik om te verander."</string>
+ <string name="background_location_access_reminder_notification_content" msgid="7787084707336546245">"Hierdie app het altyd toegang tot jou ligging. Tik om te verander."</string>
<string name="notification_listener_reminder_notification_title" msgid="3747210460187479091">"Gaan app met toegang tot jou kennisgewings na"</string>
<string name="notification_listener_reminder_notification_content" msgid="831476101108863427">"<xliff:g id="APP_NAME">%s</xliff:g> kan inhoud in jou kennisgewings toemaak, daarop reageer en toegang daartoe kry"</string>
<string name="notification_listener_warning_card_content" msgid="7840973324284115893">"Hierdie app kan inhoud in jou kennisgewings toemaak, daarop reageer en toegang daartoe kry. Sommige apps het hierdie toegang nodig om te werk soos hulle moet."</string>
@@ -303,7 +305,7 @@
<string name="safety_center_notification_app_label" msgid="2457720616141926534">"Android-stelsel"</string>
<string name="auto_revoke_after_notification_title" msgid="5417761027669887431">"Programtoestemmings is verwyder om privaatheid te beskerm"</string>
<string name="auto_revoke_after_notification_content_one" msgid="6804038707453662753">"<xliff:g id="APP_NAME">%s</xliff:g> is vir \'n paar maande nie gebruik nie. Tik om te kontroleer."</string>
- <string name="auto_revoke_after_notification_content_two" msgid="9108709764831425172">"<xliff:g id="APP_NAME">%s</xliff:g> en 1 ander program is vir \'n paar maande nie gebruik nie. Tik om te kontroleer."</string>
+ <string name="auto_revoke_after_notification_content_two" msgid="9108709764831425172">"<xliff:g id="APP_NAME">%s</xliff:g> en 1 ander app is vir \'n paar maande nie gebruik nie. Tik om te kontroleer."</string>
<string name="auto_revoke_after_notification_content_many" msgid="4774106206289751220">"<xliff:g id="APP_NAME">%1$s</xliff:g> en <xliff:g id="NUMBER_OF_APPS">%2$s</xliff:g> ander programme is vir \'n paar maande nie gebruik nie. Tik om te kontroleer."</string>
<string name="auto_revoke_before_notification_title_one" msgid="6758024954464359876">"1 app is ongebruik"</string>
<string name="auto_revoke_before_notification_title_many" msgid="4415543943846385685">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> programme is ongebruik"</string>
@@ -315,7 +317,7 @@
<string name="unused_permissions_subtitle_many" msgid="4387289202207450238">"<xliff:g id="PERM_NAME_0">%1$s</xliff:g>, <xliff:g id="PERM_NAME_1">%2$s</xliff:g> en nog <xliff:g id="NUMBER_OF_PERMISSIONS">%3$s</xliff:g>"</string>
<string name="unused_app_permissions_removed_summary" msgid="6779039455326071033">"Toestemmings is verwyder uit programme wat jy vir \'n paar maande nie gebruik het nie om jou data te beskerm"</string>
<string name="unused_app_permissions_removed_summary_some" msgid="5080490037831563441">"Om jou data te beskerm, is toestemmings verwyder uit sekere programme wat jy \'n paar maande gelede laas gebruik het"</string>
- <string name="one_unused_app_summary" msgid="7831913934488881991">"1 program is \'n paar maande gelede laas gebruik"</string>
+ <string name="one_unused_app_summary" msgid="7831913934488881991">"1 app is \'n paar maande gelede laas gebruik"</string>
<string name="num_unused_apps_summary" msgid="1870719749940571227">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> programme is \'n paar maande gelede laas gebruik."</string>
<string name="permission_subtitle_only_in_foreground" msgid="9068389431267377564">"Net terwyl die app gebruik word"</string>
<string name="permission_subtitle_media_only" msgid="8917869683764720717">"Media"</string>
@@ -365,13 +367,13 @@
<string name="role_dialer_short_label" msgid="7186888549465352489">"Foonapp"</string>
<string name="role_dialer_description" msgid="8768708633696539612">"Apps wat jou toelaat om telefoonoproepe op jou toestel te maak en te ontvang"</string>
<string name="role_dialer_request_title" msgid="5959618560705912058">"Stel <xliff:g id="APP_NAME">%1$s</xliff:g> as jou verstekfoonprogram?"</string>
- <string name="role_dialer_request_description" msgid="6288839625724909320">"Hierdie program sal toegang tot jou kamera, kontakte, mikrofoon, foon en SMS\'e kry"</string>
+ <string name="role_dialer_request_description" msgid="6288839625724909320">"Hierdie app sal toegang tot jou kamera, kontakte, mikrofoon, foon en SMS\'e kry"</string>
<string name="role_dialer_search_keywords" msgid="3324448983559188087">"beller"</string>
<string name="role_sms_label" msgid="8456999857547686640">"Verstek-SMS-app"</string>
<string name="role_sms_short_label" msgid="4371444488034692243">"SMS-app"</string>
<string name="role_sms_description" msgid="3424020199148153513">"Apps wat jou toelaat om jou foonnommer te gebruik om kort SMS\'e, foto\'s, video\'s en meer te stuur en te ontvang"</string>
- <string name="role_sms_request_title" msgid="7953552109601185602">"Stel <xliff:g id="APP_NAME">%1$s</xliff:g> as jou verstek-SMS-program?"</string>
- <string name="role_sms_request_description" msgid="2691004766132144886">"Hierdie program sal toegang tot jou kamera, kontakte, lêers en media, mikrofoon, foon en SMS\'e kry"</string>
+ <string name="role_sms_request_title" msgid="7953552109601185602">"Stel <xliff:g id="APP_NAME">%1$s</xliff:g> as jou verstek-SMS-app?"</string>
+ <string name="role_sms_request_description" msgid="2691004766132144886">"Hierdie app sal toegang tot jou kamera, kontakte, lêers en media, mikrofoon, foon en SMS\'e kry"</string>
<string name="role_sms_search_keywords" msgid="8022048144395047352">"sms, teksboodskappe, boodskappe, boodskappe"</string>
<string name="role_emergency_label" msgid="7028825857206842366">"Versteknoodprogram"</string>
<string name="role_emergency_short_label" msgid="2388431453335350348">"Noodprogram"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Oopmaak van skakels"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Verstek vir werk"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Verstek vir privaat ruimte"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Geoptimeer vir toestel"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Ander"</string>
<string name="default_app_none" msgid="9084592086808194457">"Geen"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Stelselverstek)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Geen apps nie"</string>
@@ -449,7 +453,7 @@
<string name="no_special_app_access" msgid="6950277571805106247">"Geen spesiale apptoegang nie"</string>
<string name="special_app_access_no_apps" msgid="4102911722787886970">"Geen apps nie"</string>
<string name="home_missing_work_profile_support" msgid="1756855847669387977">"Steun nie werkprofiel nie"</string>
- <string name="encryption_unaware_confirmation_message" msgid="8274491794636402484">"Let wel: As jy jou toestel herbegin en \'n skermslot is gestel, kan hierdie program nie begin totdat jy jou toestel ontsluit nie."</string>
+ <string name="encryption_unaware_confirmation_message" msgid="8274491794636402484">"Let wel: As jy jou toestel herbegin en \'n skermslot is gestel, kan hierdie app nie begin totdat jy jou toestel ontsluit nie."</string>
<string name="assistant_confirmation_message" msgid="7476540402884416212">"Die assistent sal inligting oor programme wat tans op jou stelsel gebruik word, kan lees, insluitend inligting wat op jou skerm sigbaar is of toeganklik is binne die programme."</string>
<string name="incident_report_channel_name" msgid="3144954065936288440">"Deel ontfoutingsdata"</string>
<string name="incident_report_notification_title" msgid="4635984625656519773">"Deel gedetailleerde ontfoutingsdata?"</string>
@@ -473,13 +477,13 @@
<string name="permgrouprequest_device_aware_contacts" msgid="731025863972535928">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot jou kontakte op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_location" msgid="6990232580121067883">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot hierdie toestel se ligging?"</string>
<string name="permgrouprequest_device_aware_location" msgid="6075412127429878638">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt; se ligging?"</string>
- <string name="permgrouprequestdetail_location" msgid="2635935335778429894">"Die program sal net toegang tot die ligging hê terwyl jy die program gebruik"</string>
+ <string name="permgrouprequestdetail_location" msgid="2635935335778429894">"Die app sal net toegang tot die ligging hê terwyl jy die app gebruik"</string>
<string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot hierdie toestel se ligging?"</string>
<string name="permgroupbackgroundrequest_device_aware_location" msgid="1264484517831380016">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt; se ligging?"</string>
- <string name="permgroupbackgroundrequestdetail_location" msgid="8021219324989662957">"Hierdie program wil dalk die hele tyd toegang tot jou ligging hê, selfs wanneer jy nie die program gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
+ <string name="permgroupbackgroundrequestdetail_location" msgid="8021219324989662957">"Hierdie app wil dalk die hele tyd toegang tot jou ligging hê, selfs wanneer jy nie die app gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
<string name="permgroupupgraderequest_location" msgid="8328408946822691636">"Verander liggingtoegang vir &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgroupupgraderequest_device_aware_location" msgid="1812338666887726191">"Verander liggingtoegang vir &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupupgraderequestdetail_location" msgid="1550899076845189165">"Hierdie program wil die hele tyd toegang tot jou ligging hê, selfs wanneer jy nie die program gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
+ <string name="permgroupupgraderequestdetail_location" msgid="1550899076845189165">"Hierdie app wil die hele tyd toegang tot jou ligging hê, selfs wanneer jy nie die app gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
<string name="permgrouprequest_nearby_devices" msgid="2272829282660436700">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om toestelle in die omtrek te soek, aan hulle te koppel en hul relatiewe posisie te bepaal?"</string>
<string name="permgrouprequest_device_aware_nearby_devices" msgid="5293478278408567442">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toestelle in die omtrek op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt; soek, aan hulle koppel, en hul relatiewe posisie bepaal?"</string>
<string name="permgroupupgraderequestdetail_nearby_devices" msgid="6877531270654738614">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om toestelle in die omtrek te soek, aan hulle te koppel en hul relatiewe posisie te bepaal? "<annotation id="link">"Laat toe in Instellings."</annotation></string>
@@ -505,24 +509,24 @@
<string name="permgrouprequest_device_aware_more_photos" msgid="1703469013613723053">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot meer foto’s en video’s op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om oudio op te neem?"</string>
<string name="permgrouprequest_device_aware_microphone" msgid="8821701550505437951">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang om oudio op te neem op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Die program sal net kan oudio opneem terwyl jy die program gebruik"</string>
+ <string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Die app sal net kan oudio opneem terwyl jy die app gebruik"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om oudio op te neem?"</string>
<string name="permgroupbackgroundrequest_device_aware_microphone" msgid="3321823187623762958">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang om oudio op te neem op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupbackgroundrequestdetail_microphone" msgid="553702902263681838">"Hierdie program wil dalk die hele tyd oudio opneem, selfs wanneer jy nie die program gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
+ <string name="permgroupbackgroundrequestdetail_microphone" msgid="553702902263681838">"Hierdie app wil dalk die hele tyd oudio opneem, selfs wanneer jy nie die app gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
<string name="permgroupupgraderequest_microphone" msgid="1362781696161233341">"Verander mikrofoontoegang vir &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgroupupgraderequest_device_aware_microphone" msgid="8722411173971679806">"Verander mikrofoontoegang vir &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupupgraderequestdetail_microphone" msgid="2870497719571464239">"Hierdie program wil die hele tyd oudio opneem, selfs wanneer jy nie die program gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
+ <string name="permgroupupgraderequestdetail_microphone" msgid="2870497719571464239">"Hierdie app wil die hele tyd oudio opneem, selfs wanneer jy nie die app gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
<string name="permgrouprequest_activityRecognition" msgid="5415121592794230330">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot jou fisieke aktiwiteit?"</string>
<string name="permgrouprequest_device_aware_activityRecognition" msgid="1243869530588745374">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot jou fisieke aktiwiteit op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_camera" msgid="5123097035410002594">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om foto\'s te neem en video\'s op te neem?"</string>
<string name="permgrouprequest_device_aware_camera" msgid="5340173564041615494">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang om foto’s te neem en video’s op te neem op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgrouprequestdetail_camera" msgid="9085323239764667883">"Die program sal net kan foto\'s neem en video\'s opneem terwyl jy die program gebruik"</string>
+ <string name="permgrouprequestdetail_camera" msgid="9085323239764667883">"Die app sal net kan foto\'s neem en video\'s opneem terwyl jy die app gebruik"</string>
<string name="permgroupbackgroundrequest_camera" msgid="1274286575704213875">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om foto\'s te neem en video\'s op te neem?"</string>
<string name="permgroupbackgroundrequest_device_aware_camera" msgid="8533353179594971475">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang om foto’s te neem en video’s op te neem op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupbackgroundrequestdetail_camera" msgid="4458783509089859078">"Hierdie program wil dalk die hele tyd foto\'s neem en video\'s opneem, selfs wanneer jy nie die program gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
+ <string name="permgroupbackgroundrequestdetail_camera" msgid="4458783509089859078">"Hierdie app wil dalk die hele tyd foto\'s neem en video\'s opneem, selfs wanneer jy nie die app gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
<string name="permgroupupgraderequest_camera" msgid="640758449200241582">"Verander kameratoegang vir &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgroupupgraderequest_device_aware_camera" msgid="3290160912843715236">"Verander kameratoegang vir &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupupgraderequestdetail_camera" msgid="6642747548010962597">"Hierdie program wil die hele tyd foto\'s neem en video\'s opneem, selfs wanneer jy nie die program gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
+ <string name="permgroupupgraderequestdetail_camera" msgid="6642747548010962597">"Hierdie app wil die hele tyd foto\'s neem en video\'s opneem, selfs wanneer jy nie die app gebruik nie. "<annotation id="link">"Laat toe in instellings."</annotation></string>
<string name="permgrouprequest_calllog" msgid="2065327180175371397">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot jou foonoproeprekords?"</string>
<string name="permgrouprequest_device_aware_calllog" msgid="8220927190376843309">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot jou foon se oproeprekords op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_phone" msgid="1829234136997316752">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om foonoproepe te maak en te bestuur?"</string>
@@ -532,8 +536,8 @@
<string name="permgroupupgraderequestdetail_sensors" msgid="6651914048792092835">"Dié app wil dalk deurentyd toegang tot sensordata oor jou lewenstekens hê, selfs wanneer jy nie die app gebruik nie. "<annotation id="link">"Gaan na instellings"</annotation>" om hierdie verandering te maak."</string>
<string name="permgroupbackgroundrequest_sensors" msgid="5661924322018503886">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot die sensordata oor jou lewenstekens?"</string>
<string name="permgroupbackgroundrequest_device_aware_sensors" msgid="3687673359121603824">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot die sensordata oor jou lewenstekens op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupbackgroundrequestdetail_sensors" msgid="7726767635834043501"><annotation id="link">"Gaan na instellings"</annotation>" om altyd vir hierdie program toegang tot liggaamsensordata te gee, selfs wanneer jy nie die program gebruik nie."</string>
- <string name="permgroupupgraderequest_sensors" msgid="7576527638411370468">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; steeds toegang tot liggaamsensordata terwyl die program gebruik word?"</string>
+ <string name="permgroupbackgroundrequestdetail_sensors" msgid="7726767635834043501"><annotation id="link">"Gaan na instellings"</annotation>" om altyd vir hierdie app toegang tot liggaamsensordata te gee, selfs wanneer jy nie die app gebruik nie."</string>
+ <string name="permgroupupgraderequest_sensors" msgid="7576527638411370468">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; steeds toegang tot liggaamsensordata terwyl die app gebruik word?"</string>
<string name="permgroupupgraderequest_device_aware_sensors" msgid="5542771499929819675">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; steeds toegang tot liggaamsensordata op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt; terwyl die app gebruik word?"</string>
<string name="permgrouprequest_notifications" msgid="6396739062335106181">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om vir jou kennisgewings te stuur?"</string>
<string name="permgrouprequest_device_aware_notifications" msgid="857671638951040514">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang om vir jou kennisgewings te stuur op &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
@@ -553,8 +557,8 @@
<string name="privdash_label_24h" msgid="1512532123865375319">"Afgelope\n24 uur"</string>
<string name="privdash_label_7d" msgid="5645301995348656931">"Afgelope\n7 dae"</string>
<string name="privdash_usage_percent" msgid="6893824766124414127">"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g> <xliff:g id="PERCENT">%2$d</xliff:g> persent"</string>
- <string name="exempt_mic_camera_info_label" msgid="6273581737010902815">"<xliff:g id="APP_NAME">%1$s</xliff:g> word deur Android beskerm. Hierdie program se toestemminggebruik word nie op die statusbalk of jou privaatheidkontroleskerm gewys nie omdat jou data op hierdie toestel verwerk word."</string>
- <string name="exempt_info_label" msgid="6286190981253476699">"<xliff:g id="APP_NAME">%1$s</xliff:g> word deur Android beskerm. Hierdie program se toestemminggebruik word nie op jou privaatheidkontroleskerm gewys nie omdat jou data op hierdie toestel verwerk word."</string>
+ <string name="exempt_mic_camera_info_label" msgid="6273581737010902815">"<xliff:g id="APP_NAME">%1$s</xliff:g> word deur Android beskerm. Hierdie app se toestemminggebruik word nie op die statusbalk of jou privaatheidkontroleskerm gewys nie omdat jou data op hierdie toestel verwerk word."</string>
+ <string name="exempt_info_label" msgid="6286190981253476699">"<xliff:g id="APP_NAME">%1$s</xliff:g> word deur Android beskerm. Hierdie app se toestemminggebruik word nie op jou privaatheidkontroleskerm gewys nie omdat jou data op hierdie toestel verwerk word."</string>
<string name="blocked_camera_title" msgid="1128510551791284384">"Toestelkamera is geblokkeer"</string>
<string name="blocked_microphone_title" msgid="1631517143648232585">"Toestelmikrofoon is geblokkeer"</string>
<string name="blocked_location_title" msgid="2005608279812892383">"Toestelligging is af"</string>
@@ -588,8 +592,8 @@
<string name="permissions_removed_qs" msgid="8957319130625294572">"Toestemming is verwyder"</string>
<string name="camera_usage_qs" msgid="4394233566086665994">"Sien onlangse kameragebruik"</string>
<string name="microphone_usage_qs" msgid="8527666682168170417">"Sien onlangse mikrofoongebruik"</string>
- <string name="remove_camera_qs" msgid="3649996161066883350">"Verwyder toestemming vir hierdie program"</string>
- <string name="remove_microphone_qs" msgid="1276551965129953198">"Verwyder toestemming vir hierdie program"</string>
+ <string name="remove_camera_qs" msgid="3649996161066883350">"Verwyder toestemming vir hierdie app"</string>
+ <string name="remove_microphone_qs" msgid="1276551965129953198">"Verwyder toestemming vir hierdie app"</string>
<string name="manage_service_qs" msgid="7862555549364153805">"Bestuur diens"</string>
<string name="manage_permissions_qs" msgid="3780541819763475434">"Bestuur toestemmings"</string>
<string name="active_call_usage_qs" msgid="8559974395932523391">"Word tans gebruik deur foonoproep"</string>
@@ -610,14 +614,14 @@
<string name="media_confirm_dialog_title_q_to_s_aural_deny" msgid="3128147568953297969">"Toegang tot foto\'s en video\'s sal ook nie toegelaat word"</string>
<string name="media_confirm_dialog_title_q_to_s_visual_allow" msgid="6310682466493330434">"Toegang tot musiek- en oudiolêers sal ook toegelaat word"</string>
<string name="media_confirm_dialog_title_q_to_s_visual_deny" msgid="1123845663785900471">"Toegang tot musiek- en oudiolêers sal ook nie toegelaat word nie"</string>
- <string name="media_confirm_dialog_message_a_to_p_aural_allow" msgid="7865167246140107623">"Hierdie program steun nie die jongste weergawe van Android nie. As hierdie program toegang tot musiek- en oudiolêers het, sal dit ook toegelaat word om toegang tot foto\'s, video\'s en ander lêers te kry."</string>
- <string name="media_confirm_dialog_message_a_to_p_aural_deny" msgid="287502523664804786">"Hierdie program steun nie die jongste weergawe van Android nie. As hierdie program nie toegang tot musiek- en oudiolêers het nie, sal dit ook nie toegelaat word om toegang tot foto\'s, video\'s en ander lêers te kry nie."</string>
- <string name="media_confirm_dialog_message_a_to_p_visual_allow" msgid="4952410892939590487">"Hierdie program steun nie die jongste weergawe van Android nie. As hierdie program toegang tot foto\'s en video\'s het, sal dit ook toegelaat word om toegang tot musiek-, oudio- en ander lêers te kry."</string>
- <string name="media_confirm_dialog_message_a_to_p_visual_deny" msgid="6609500525590757681">"Hierdie program steun nie die jongste weergawe van Android nie. As hierdie program nie toegang tot foto\'s en video\'s het nie, sal dit ook nie toegelaat word om toegang tot musiek-, oudio- en ander lêers te kry nie."</string>
- <string name="media_confirm_dialog_message_q_to_s_aural_allow" msgid="1702402580147536160">"Hierdie program steun nie die jongste weergawe van Android nie. As hierdie program toegang tot musiek en oudiolêers het, sal dit ook toegelaat word om toegang tot foto\'s en video\'s te kry."</string>
- <string name="media_confirm_dialog_message_q_to_s_aural_deny" msgid="6832087393653561911">"Hierdie program steun nie die jongste weergawe van Android nie. As hierdie program nie toegang tot musiek- en oudiolêers het nie, sal dit ook nie toegelaat word om toegang tot foto\'s en video\'s te kry nie."</string>
- <string name="media_confirm_dialog_message_q_to_s_visual_allow" msgid="3504335060843147760">"Hierdie program steun nie die jongste weergawe van Android nie. As hierdie program toegang tot foto\'s en video\'s het, sal dit ook toegelaat word toegang tot musiek-, oudio- en ander lêers te kry."</string>
- <string name="media_confirm_dialog_message_q_to_s_visual_deny" msgid="2145973462806481992">"Hierdie program steun nie die jongste weergawe van Android nie. As hierdie program nie toegang tot musiek- en oudiolêers het nie, sal dit ook nie toegelaat word om toegang tot foto\'s en video\'s te kry nie."</string>
+ <string name="media_confirm_dialog_message_a_to_p_aural_allow" msgid="7865167246140107623">"Hierdie app steun nie die jongste weergawe van Android nie. As hierdie app toegang tot musiek- en oudiolêers het, sal dit ook toegelaat word om toegang tot foto\'s, video\'s en ander lêers te kry."</string>
+ <string name="media_confirm_dialog_message_a_to_p_aural_deny" msgid="287502523664804786">"Hierdie app steun nie die jongste weergawe van Android nie. As hierdie app nie toegang tot musiek- en oudiolêers het nie, sal dit ook nie toegelaat word om toegang tot foto\'s, video\'s en ander lêers te kry nie."</string>
+ <string name="media_confirm_dialog_message_a_to_p_visual_allow" msgid="4952410892939590487">"Hierdie app steun nie die jongste weergawe van Android nie. As hierdie app toegang tot foto\'s en video\'s het, sal dit ook toegelaat word om toegang tot musiek-, oudio- en ander lêers te kry."</string>
+ <string name="media_confirm_dialog_message_a_to_p_visual_deny" msgid="6609500525590757681">"Hierdie app steun nie die jongste weergawe van Android nie. As hierdie app nie toegang tot foto\'s en video\'s het nie, sal dit ook nie toegelaat word om toegang tot musiek-, oudio- en ander lêers te kry nie."</string>
+ <string name="media_confirm_dialog_message_q_to_s_aural_allow" msgid="1702402580147536160">"Hierdie app steun nie die jongste weergawe van Android nie. As hierdie app toegang tot musiek en oudiolêers het, sal dit ook toegelaat word om toegang tot foto\'s en video\'s te kry."</string>
+ <string name="media_confirm_dialog_message_q_to_s_aural_deny" msgid="6832087393653561911">"Hierdie app steun nie die jongste weergawe van Android nie. As hierdie app nie toegang tot musiek- en oudiolêers het nie, sal dit ook nie toegelaat word om toegang tot foto\'s en video\'s te kry nie."</string>
+ <string name="media_confirm_dialog_message_q_to_s_visual_allow" msgid="3504335060843147760">"Hierdie app steun nie die jongste weergawe van Android nie. As hierdie app toegang tot foto\'s en video\'s het, sal dit ook toegelaat word toegang tot musiek-, oudio- en ander lêers te kry."</string>
+ <string name="media_confirm_dialog_message_q_to_s_visual_deny" msgid="2145973462806481992">"Hierdie app steun nie die jongste weergawe van Android nie. As hierdie app nie toegang tot musiek- en oudiolêers het nie, sal dit ook nie toegelaat word om toegang tot foto\'s en video\'s te kry nie."</string>
<string name="safety_center_background_location_access_notification_title" msgid="8933610618810588237">"Gaan app met agtergrondliggingtoegang na"</string>
<string name="safety_center_background_location_access_reminder_notification_content" msgid="4066560182507301022">"<xliff:g id="APP_NAME">%s</xliff:g> het altyd toegang tot jou ligging, selfs wanneer die app toe is"</string>
<string name="safety_center_background_location_access_reminder_title" msgid="5477847038103863843">"Gaan app met agtergrondliggingtoegang na"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Laat beperkte instellings toe"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Beperkte instelling"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Hierdie instelling is vir jou veiligheid tans onbeskikbaar."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Kan nie handeling tydens oproep voltooi nie"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Hierdie instelling is geblokkeer om jou toestel en data te beskerm"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Swendelaars kan probeer om skadelike apps te installeer deur jou te vra om onbekende apps van ’n nuwe bron af te installeer."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Swendelaars kan probeer om beheer oor jou toestel te neem deur jou te vra om toeganklikheidtoegang vir ’n app toe te laat."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Swendelaars kan probeer om jou toestel met hierdie instelling te beskadig."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Toegang tot <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> is geweier vir die app"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Die app het toegang tot toestemming vir sensitiewe inligting versoek wat jou persoonlike en finansiële inligting in gevaar kan stel.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Dit is moontlik dat die app nie reg sal werk sonder hierdie beperkte toestemming nie. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Kry meer inligting oor hoe om toegang toe te laat&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Toegang is geweier vir app om die verstek <xliff:g id="ROLE_NAME">%1$s</xliff:g> te wees"</string>
diff --git a/PermissionController/res/values-am-v33/strings.xml b/PermissionController/res/values-am-v33/strings.xml
index e2a599a3c..edd6dac3f 100644
--- a/PermissionController/res/values-am-v33/strings.xml
+++ b/PermissionController/res/values-am-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"ተጨማሪ ማንቂያዎች"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"የተሰናበቱ ማንቂያዎች"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{ይዘርጉ እና አንድ ተጨማሪ ማንቂያ ይመልከቱ}one{ይዘርጉ እና # ተጨማሪ ማንቂያ ይመልከቱ}other{ይዘርጉ እና # ተጨማሪ ማንቂያዎችን ይመልከቱ}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"ሰብስብ"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"ማንቂያ። <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"እርምጃ ተጠናቅቋል"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"የእርስዎ መሣሪያ ላይ ጥበቃ ማከል የሚችሉ ቅንብሮችን ይፈትሹ"</string>
diff --git a/PermissionController/res/values-am/strings.xml b/PermissionController/res/values-am/strings.xml
index f49db7957..8b4cffe19 100644
--- a/PermissionController/res/values-am/strings.xml
+++ b/PermissionController/res/values-am/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> ላይ ለዚህ መተግበሪያ የ<xliff:g id="PERM">%1$s</xliff:g> መዳረሻ"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"ሁሉንም <xliff:g id="APP">%1$s</xliff:g> ፈቃዶች ይመልከቱ"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ከዚህ መተግበሪያ ጋር ሁሉንም መተግበሪያዎች ይመልከቱ"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"መረጃ"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"ቅንብሮች"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"የረዳት ማይክሮፎን አጠቃቀምን አሳይ"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ሥራ ላይ ያልዋሉ የመተግበሪያ ቅንብሮች"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"መተግበሪያ ጥቅም ላይ ካልዋለ ፈቃዶችን አስወግድ"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"አገናኞችን በመክፈት ላይ"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"ለሥራ ነባሪ"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"ለግል ቦታ ነባሪ"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ለመሣሪያ የተባ"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"ሌሎች"</string>
<string name="default_app_none" msgid="9084592086808194457">"ምንም"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(የሥርዓት ነባሪ)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"መተግበሪያዎች የሉም"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"የተገደቡ ቅንብሮችን ፍቀድ"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"የተገደበ ቅንብር"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"ለእርስዎ ደህንነት ሲባል ይህ ቅንብር በአሁኑ ጊዜ አይገኝም።"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"በጥሪ ወቅት እርምጃን ማጠናቀቅ አይቻልም"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n የእርስዎን መሣሪያ እና ውሂብ ለመጠበቅ ይህ ቅንብር ታግዷል"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"አጭበርባሪዎች እርስዎ ከአዲስ ምንጭ ላይ የማይታወቁ መተግበሪያዎችን እንዲጭኑ በመጠየቅ ጎጂ መተግበሪያዎችን ለመጫን ሊሞክሩ ይችላሉ።"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"አጭበርባሪዎች እርስዎ ለአንድ መተግበሪያ የተደራሽነት መዳረሻ እንዲፈቅዱ በመጠየቅ የመሣሪያዎን ቁጥጥር ሊወስዱ ይችላሉ።"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"አጭበርባሪዎች በዚህ ቅንብር የእርስዎን መሣሪያ ለመጉዳት ሊሞክሩ ይችላሉ።"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"መተግበሪያ የ<xliff:g id="PERMISSION_NAME">%1$s</xliff:g> መዳረሻ ተከልክሏል"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"መተግበሪያው የእርስዎን የግል እና የፋይናንስ መረጃ አደጋ ላይ የሚጥል አደገኛ ፈቃድ እንዲደርስ ጠይቋል።<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>ያለዚህ የተገደበ ፈቃድ መተግበሪያው በትክክል ላይሰራ ይችላል። &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;እንዴት መዳረሻን እንደሚፈቅዱ ይረዱ&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"መተግበሪያ ነባሪ <xliff:g id="ROLE_NAME">%1$s</xliff:g> የመሆን መዳረሻ ተከልክሏል"</string>
diff --git a/PermissionController/res/values-ar-v33/strings.xml b/PermissionController/res/values-ar-v33/strings.xml
index 6b881f064..3c86f5ffd 100644
--- a/PermissionController/res/values-ar-v33/strings.xml
+++ b/PermissionController/res/values-ar-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"المزيد من التنبيهات"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"التنبيهات التي تم إغلاقها"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{التوسيع لعرض تنبيه واحد إضافي}zero{التوسيع لعرض # تنبيه إضافي}two{التوسيع لعرض تنبيهين إضافيَين}few{التوسيع لعرض # تنبيهات إضافية}many{التوسيع لعرض # تنبيهًا إضافيًا}other{التوسيع لعرض # تنبيه إضافي}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"تصغير"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"تنبيه: <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"اكتمل الإجراء"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"التحقّق من الإعدادات التي يمكن أن تعزّز حماية جهازك"</string>
diff --git a/PermissionController/res/values-ar/strings.xml b/PermissionController/res/values-ar/strings.xml
index 90fce2d06..e22ec0182 100644
--- a/PermissionController/res/values-ar/strings.xml
+++ b/PermissionController/res/values-ar/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"إذن \"<xliff:g id="PERM">%1$s</xliff:g>\" لهذا التطبيق على \"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>\""</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"الاطّلاع على جميع أذونات تطبيق \"<xliff:g id="APP">%1$s</xliff:g>\""</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"الاطّلاع على جميع التطبيقات التي لديها هذا الإذن"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"معلومات"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"الإعدادات"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"‏عرض أذونات استخدام ميكروفون \"مساعد Google\""</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"إعدادات التطبيقات غير المُستخدَمة"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"إزالة الأذونات في حال عدم استخدام التطبيق"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"فتح الروابط"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"التطبيقات التلقائية للعمل"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"التطبيقات التلقائية في المساحة الخاصّة"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"متوافقة مع الجهاز"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"غير ذلك"</string>
<string name="default_app_none" msgid="9084592086808194457">"غير محدَّد"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(الإعداد التلقائي للنظام)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"ليست هناك تطبيقات."</string>
@@ -471,7 +475,7 @@
<string name="permgrouprequest_device_aware_storage_isolated" msgid="6463062962458809752">"‏هل تريد السماح لـ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى الصور والوسائط على &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;؟"</string>
<string name="permgrouprequest_contacts" msgid="8391550064551053695">"‏هل تريد السماح لتطبيق &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى جهات الاتصال؟"</string>
<string name="permgrouprequest_device_aware_contacts" msgid="731025863972535928">"‏هل تريد السماح لـ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى جهات اتصالك على &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;؟"</string>
- <string name="permgrouprequest_location" msgid="6990232580121067883">"‏هل تريد السماح لتطبيق &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى الموقع الجغرافي لهذا الجهاز؟"</string>
+ <string name="permgrouprequest_location" msgid="6990232580121067883">"‏هل مطلوب السماح لتطبيق &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى الموقع الجغرافي لهذا الجهاز؟"</string>
<string name="permgrouprequest_device_aware_location" msgid="6075412127429878638">"‏هل تريد السماح لـ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى الموقع الجغرافي الخاص بـ &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;؟"</string>
<string name="permgrouprequestdetail_location" msgid="2635935335778429894">"لن يكون بإمكان التطبيق الوصول إلى الموقع الجغرافي إلا عند استخدامك لهذا التطبيق."</string>
<string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"‏هل تريد السماح لتطبيق &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى الموقع الجغرافي لهذا الجهاز؟"</string>
@@ -491,7 +495,7 @@
<string name="permgrouprequest_coarselocation_imagetext" msgid="8650605041483025297">"تقريبي"</string>
<string name="permgrouprequest_calendar" msgid="1493150855673603806">"‏هل تريد السماح لتطبيق &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى التقويم؟"</string>
<string name="permgrouprequest_device_aware_calendar" msgid="7161929851377463612">"‏هل تريد السماح لـ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى تقويمك على &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;؟"</string>
- <string name="permgrouprequest_sms" msgid="5672063688745420991">"‏هل تريد السماح لتطبيق &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بإرسال رسائل SMS وعرضها؟"</string>
+ <string name="permgrouprequest_sms" msgid="5672063688745420991">"‏هل مطلوب السماح لتطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> بإرسال رسائل SMS وعرضها؟"</string>
<string name="permgrouprequest_device_aware_sms" msgid="6639977653040502291">"‏هل تريد السماح لـ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بإرسال الرسائل القصيرة وعرضها على &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;؟"</string>
<string name="permgrouprequest_storage" msgid="8717773092518621602">"‏هل تريد السماح لتطبيق &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى الصور والوسائط والملفات على جهازك؟"</string>
<string name="permgrouprequest_device_aware_storage" msgid="6933251810928606636">"‏هل تريد السماح لـ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بالوصول إلى الصور والوسائط والملفات على &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;؟"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"السماح بالإعدادات المحظورة"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"الإعداد محظور"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"للحفاظ على أمانك، هذا الإعداد غير متوفِّر حاليًا."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"يتعذّر إكمال الإجراء أثناء إجراء مكالمة"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n تم حظر هذا الإعداد لحماية الجهاز والبيانات"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"قد يحاول المخادِعون تثبيت تطبيقات ضارّة من خلال طلب تثبيت تطبيقات غير معروفة من مصدر جديد."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"قد يحاول المخادِعون السيطرة على الجهاز من خلال طلب السماح بوصول أحد التطبيقات إلى ميزات تسهيل الاستخدام."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"قد يحاول المخادِعون إلحاق الضرر بجهازك من خلال هذا الإعداد."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"تم منع التطبيق من الوصول إلى <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"‏طلب التطبيق أحد أذونات الوصول إلى المعلومات الحساسة، ما قد يعرّض معلوماتك الشخصية والمالية للخطر.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>من المحتمل ألا يعمل التطبيق بشكل صحيح إذا لم يحصل على هذا الإذن المقيَّد. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;مزيد من المعلومات حول كيفية منح الأذونات&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"لم يُسمَح للتطبيق بأن يكون <xliff:g id="ROLE_NAME">%1$s</xliff:g> التلقائي"</string>
diff --git a/PermissionController/res/values-as-v33/strings.xml b/PermissionController/res/values-as-v33/strings.xml
index db8b95d3e..08f877f7a 100644
--- a/PermissionController/res/values-as-v33/strings.xml
+++ b/PermissionController/res/values-as-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"অধিক সতৰ্কবাৰ্তা"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"অগ্ৰাহ্য কৰা সতৰ্কবাৰ্তা"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{বিস্তাৰ কৰক আৰু এটা সতৰ্কবাৰ্তা চাওক}one{বিস্তাৰ কৰক আৰু # টা সতৰ্কবাৰ্তা চাওক}other{বিস্তাৰ কৰক আৰু # টা সতৰ্কবাৰ্তা চাওক}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"সংকোচন কৰক"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"সতৰ্কতা। <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"কার্য সম্পূর্ণ হ\'ল"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"আপোনাৰ ডিভাইচ অধিক সুৰক্ষিত কৰিব পৰা ছেটিংসমূহ পৰীক্ষা কৰক"</string>
diff --git a/PermissionController/res/values-as/strings.xml b/PermissionController/res/values-as/strings.xml
index ef3d28e5a..12ba5014a 100644
--- a/PermissionController/res/values-as/strings.xml
+++ b/PermissionController/res/values-as/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>ত এই এপৰ <xliff:g id="PERM">%1$s</xliff:g>ৰ এক্সেছ"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"আটাইবোৰ <xliff:g id="APP">%1$s</xliff:g> অনুমতি চাওক"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"এই অনুমতি থকা আটাইবোৰ এপ্ চাওক"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"তথ্য"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"ছেটিং"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"সহায়ক মাইক্ৰ’ফ’নৰ ব্যৱহাৰ দেখুৱাওক"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ব্যৱহাৰ নকৰা এপৰ ছেটিং"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"যদি এপ্‌টো ব্যৱহাৰ কৰা নাই অনুমতিসমূহ আঁতৰাওক"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"লিংকসমূহ খুলি থকা হৈছে"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"কৰ্মস্থানৰ বাবে ডিফ’ল্ট"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"প্ৰাইভেট স্পে’চৰ বাবে ডিফ’ল্ট"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ডিভাইচৰ বাবে অপ্টিমাইজ কৰা হৈছে"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"অন্য"</string>
<string name="default_app_none" msgid="9084592086808194457">"নাই"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(ছিষ্টেম ডিফ\'ল্ট)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"কোনো এপ্‌ নাই"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"প্ৰতিবন্ধিত ছেটিঙৰ অনুমতি দিয়ক"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"প্ৰতিবন্ধিত ছেটিং"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"আপোনাৰ সুৰক্ষাৰ বাবে, এই ছেটিংটো বৰ্তমান উপলব্ধ নহয়।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"কলৰ সময়ত কাৰ্য সম্পূৰ্ণ কৰিব নোৱাৰি"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n আপোনাৰ ডিভাইচ আৰু ডেটা সুৰক্ষিত কৰিবলৈ এই ছেটিং অৱৰোধ কৰা হৈছে"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"স্কেমাৰসকলে আপোনাক এটা নতুন উৎসৰ পৰা অজ্ঞাত এপ্‌সমূহ ইনষ্টল কৰিবলৈ কৈ ক্ষতিকাৰক এপ্‌সমূহ ইনষ্টল কৰিবলৈ চেষ্টা কৰিব পাৰে।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"স্কেমাৰসকলে আপোনাক এপৰ বাবে সাধ্য সুবিধাৰ এক্সেছৰ অনুমতি দিবলৈ কৈ আপোনাৰ ডিভাইচৰ নিয়ন্ত্ৰণ ল\'বলৈ চেষ্টা কৰিব পাৰে।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"স্কেমাৰসকলে এই ছেটিঙৰ জৰিয়তে আপোনাৰ ডিভাইচৰ ক্ষতি কৰাৰ চেষ্টা কৰিব পাৰে।"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"এপ্‌টোক <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>ৰ এক্সেছ প্ৰত্যাখ্যান কৰা হৈছে"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"এপ্‌টোৱে সংবেদনশীল অনুমতি এক্সেছ কৰিবলৈ অনুৰোধ কৰিছে, যিটোৱে আপোনাৰ ব্যক্তিগত আৰু বিত্তীয় তথ্য বিপদাপন্ন কৰিব পাৰে।<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>এপ্‌টোৱে এই প্ৰতিবন্ধিত অনুমতিটোৰ অবিহনে সঠিককৈ কাম নকৰাটো সম্ভৱ হ’ব পাৰে। &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;কেনেকৈ এক্সেছৰ অনুমতি দিব লাগে জানক&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"এপ্‌টোক ডিফ’ল্ট <xliff:g id="ROLE_NAME">%1$s</xliff:g>ৰ এক্সেছ প্ৰত্যাখ্যান কৰা হৈছে"</string>
diff --git a/PermissionController/res/values-az-v33/strings.xml b/PermissionController/res/values-az-v33/strings.xml
index da3e73e74..79b215907 100644
--- a/PermissionController/res/values-az-v33/strings.xml
+++ b/PermissionController/res/values-az-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Daha çox siqnal"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Qapadılmış xəbərdarlıqlar"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Genişləndirin və daha bir xəbərdarlığa baxın}other{Genişləndirin və daha # xəbərdarlığa baxın}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Yığcamlaşdırın"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Xəbərdarlıq. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Əməliyyat tamamlandı"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Cihazınıza qoruma əlavə edə biləcək ayarları yoxlayın"</string>
diff --git a/PermissionController/res/values-az/strings.xml b/PermissionController/res/values-az/strings.xml
index a33e4c27e..14d93b2aa 100644
--- a/PermissionController/res/values-az/strings.xml
+++ b/PermissionController/res/values-az/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> cihazında bu tətbiq üçün <xliff:g id="PERM">%1$s</xliff:g> girişi"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Bütün <xliff:g id="APP">%1$s</xliff:g> icazələrinə baxın"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Bu icazəyə sahib olan bütün tətbiqlərə baxın"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Məlumat"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Ayarlar"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Assistent üçün mikrofon istifadəsini göstərin"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"İstifadə edilməyən tətbiq ayarları"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Tətbiq işlənməyəndə icazə ləğv edilsin"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Linklərin açılması"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"İş üçün defolt"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Şəxsi sahə üçün defolt"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Cihaz üçün optimallaşdırılıb"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Digərləri"</string>
<string name="default_app_none" msgid="9084592086808194457">"Yoxdur"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Sistem defoltu)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Tətbiq yoxdur"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Məhdudlaşdırılmış ayarlara icazə verin"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Məhdudlaşdırılmış ayar"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Güvənlik üçün bu ayar əlçatan deyil."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Zəng zamanı əməliyyatı tamamlamaq olmur"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Cihaz və datanızı qorumaq üçün bu ayar bloklanıb"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Fırıldaqçılar yeni mənbədən naməlum tətbiqlər quraşdırmağınızı istəyərək zərərli tətbiqlər quraşdırmağa cəhd edə bilərlər."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Fırıldaqçılar tətbiq üçün xüsusi imkanlara giriş imkanı verməyinizi istəyərək cihazınızı idarə etməyə cəhd edə bilərlər."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Fırıldaqçılar bu ayar ilə cihazınıza zərər vurmağa cəhd edə bilərlər."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Tətbiqə <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> icazəsinə giriş verilmədi"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Tətbiq şəxsi və maliyyə məlumatlarınızı riskə ata biləcək həssas məlumat icazəsinə giriş istədi.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Bu məhdud icazə olmadan tətbiq düzgün işləməyə bilər. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Girişə icazə vermək haqqında ətraflı&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Tətbiqə defolt <xliff:g id="ROLE_NAME">%1$s</xliff:g> roluna giriş verilmədi"</string>
diff --git a/PermissionController/res/values-b+sr+Latn-v33/strings.xml b/PermissionController/res/values-b+sr+Latn-v33/strings.xml
index acc79ac84..8da4acd5c 100644
--- a/PermissionController/res/values-b+sr+Latn-v33/strings.xml
+++ b/PermissionController/res/values-b+sr+Latn-v33/strings.xml
@@ -30,9 +30,10 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Još obaveštenja"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Odbačena obaveštenja"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Proširite i vidite još jedno obaveštenje}one{Proširite i vidite još # obaveštenje}few{Proširite i vidite još # obaveštenja}other{Proširite i vidite još # obaveštenja}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Skupi"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Obaveštenje. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Radnja je dovršena"</string>
- <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Proverite podešavanja koja mogu da dodaju zaštitu uređaju"</string>
+ <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Proverite podešavanja koja mogu da doprinesu boljoj zaštiti uređaja"</string>
<string name="safety_center_qs_page_landing" msgid="1717368301679228128">"Brza podešavanja bezbednosti i privatnosti"</string>
<string name="safety_center_qs_close_button" msgid="1352313308176244599">"Zatvori"</string>
<string name="safety_center_qs_expand_action" msgid="2193190557696484169">"Proširi i prikaži opcije"</string>
diff --git a/PermissionController/res/values-b+sr+Latn/strings.xml b/PermissionController/res/values-b+sr+Latn/strings.xml
index 75e5a94d3..0b12b0044 100644
--- a/PermissionController/res/values-b+sr+Latn/strings.xml
+++ b/PermissionController/res/values-b+sr+Latn/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Ova aplikacija ima pristup za: <xliff:g id="PERM">%1$s</xliff:g> na uređaju <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Prikaži sve dozvole za: <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Prikaži sve aplikacije sa ovom dozvolom"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informacije"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Podešavanja"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Prikaži kako Pomoćnik koristi mikrofon"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Podešavanja nekorišćenih aplikacija"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Ukloni dozvole ako se aplikacija ne koristi"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Otvaranje linkova"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Podrazumevana za posao"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Podrazumevano za privatan prostor"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizovano za uređaj"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Drugo"</string>
<string name="default_app_none" msgid="9084592086808194457">"Ništa"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Podrazumevana sistemska)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nema aplikacija"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> želi da otpremi informacije za otklanjanje grešaka."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Želite da delite podatke o otklanjanju grešaka?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Sistem je otkrio problem."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> traži da otpremi izveštaj o greškama sa ovog uređaja koji je napravljen <xliff:g id="DATE">%2$s</xliff:g> u <xliff:g id="TIME">%3$s</xliff:g>. Izveštaji o greškama obuhvataju lične podatke o uređaju ili podatke koje su evidentirale aplikacije, na primer, korisnička imena, podatke o lokaciji, identifikatore uređaja i informacije o mreži. Delite izveštaje o greškama samo sa ljudima i aplikacijama kojima možete da poverite te informacije. Želite li da dozvolite da <xliff:g id="APP_NAME_1">%4$s</xliff:g> otpremi izveštaj o grešci?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> traži da otpremi izveštaj o greškama sa ovog uređaja koji je napravljen <xliff:g id="DATE">%2$s</xliff:g> u <xliff:g id="TIME">%3$s</xliff:g>. Izveštaji o greškama obuhvataju lične podatke o uređaju ili podatke koje su evidentirale aplikacije, na primer, korisnička imena, podatke o lokaciji, identifikatore uređaja i informacije o mreži. Delite izveštaje o greškama samo sa ljudima i aplikacijama koje smatrate pouzdanim za te informacije. Želite li da dozvolite da <xliff:g id="APP_NAME_1">%4$s</xliff:g> otpremi izveštaj o grešci?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Došlo je do greške pri obradi izveštaja o grešci za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>. Zato je odbijeno deljenje detaljnih podataka o otklanjanju grešaka. Izvinjavamo se zbog prekida."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Dozvoli"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Odbij"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Dozvoli ograničena podešavanja"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Ograničeno podešavanje"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Ovo podešavanje je trenutno nedostupno radi vaše bezbednosti."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Radnja ne može da se završi tokom poziva"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Ovo podešavanje je blokirano da bi se zaštitili uređaj i podaci"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Prevaranti mogu da pokušaju da instaliraju štetne aplikacije tako što će tražiti da instalirate nepoznate aplikacije iz novog izvora."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Prevaranti mogu da pokušaju da preuzmu kontrolu nad uređajem tako što će tražiti da za aplikaciju dozvolite pristup za pristupačnost."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Prevaranti mogu da pokušaju da naškode uređaju pomoću ovog podešavanja."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Aplikaciji nije dozvoljen pristup dozvoli: <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Aplikacija je zatražila pristup osetljivoj dozvoli, što može da ugrozi bezbednost ličnih i finansijskih podataka.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Aplikacija možda neće raditi ispravno bez ove ograničene dozvole. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Saznajte kako da dozvolite pristup&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikaciji nije dozvoljen pristup da postane podrazumevana: <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-be-v33/strings.xml b/PermissionController/res/values-be-v33/strings.xml
index 511ed49c6..0742aa8d7 100644
--- a/PermissionController/res/values-be-v33/strings.xml
+++ b/PermissionController/res/values-be-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Іншыя абвесткі"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Адхіленыя абвесткі"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Разгарніце, каб убачыць яшчэ адну абвестку}one{Разгарніце, каб убачыць яшчэ # абвестку}few{Разгарніце, каб убачыць яшчэ # абвесткі}many{Разгарніце, каб убачыць яшчэ # абвестак}other{Разгарніце, каб убачыць яшчэ # абвесткі}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Згарнуць"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Абвестка. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Дзеянне завершана"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Праверце налады, якія могуць павысіць бяспеку вашай прылады"</string>
diff --git a/PermissionController/res/values-be/strings.xml b/PermissionController/res/values-be/strings.xml
index b009b68d3..26ea7e101 100644
--- a/PermissionController/res/values-be/strings.xml
+++ b/PermissionController/res/values-be/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Дазвол \"<xliff:g id="PERM">%1$s</xliff:g>\" для гэтай праграмы на прыладзе \"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>\""</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Паказаць усе дазволы праграмы \"<xliff:g id="APP">%1$s</xliff:g>\""</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Паказаць усе праграмы з гэтым дазволам"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Інфармацыя"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Налады"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Паказваць выкарыстанне мікрафона памочнікам"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Налады неактыўных праграм"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Выдаляць дазволы, калі праграма не выкарыстоўваецца"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Адкрыццё спасылак"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Стандартныя для працы"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Стандартныя праграмы для прыватнай прасторы"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Аптымізаваныя для прылады"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Іншыя"</string>
<string name="default_app_none" msgid="9084592086808194457">"Няма"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Стандартная сістэмная)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Няма праграм"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Дазволіць абмежаваныя налады"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Налада з абмежаваным доступам"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"У мэтах бяспекі гэта налада цяпер недаступная."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Нельга выканаць гэта дзеянне падчас выкліку"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Гэта налада заблакіравана для абароны вашай прылады і даных"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Каб паспрабаваць усталяваць шкодныя праграмы, махляры могуць прасіць вас усталяваць невядомыя праграмы з новай крыніцы."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Каб атрымаць кантроль над вашай прыладай, махляры могуць запытаць для праграмы доступ да спецыяльных магчымасцей."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Махляры могуць выкарыстаць гэту наладу, каб нанесці шкоду прыладзе."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Праграме адмоўлена ў дазволе \"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>\""</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Праграма запытала дазвол на доступ да канфідэнцыяльнай інфармацыі. Калі вы яго дасце, ваша асабістая і фінансавая інфармацыя можа аказацца ў небяспецы.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Магчыма, без гэтага абмежаванага дазволу праграма не будзе працаваць належным чынам. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Даведацца, як дазволіць доступ&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Праграме адмоўлена ў дазволе стандартна выконваць наступную ролю: <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-bg-v33/strings.xml b/PermissionController/res/values-bg-v33/strings.xml
index 6080b29b8..8c9466fe0 100644
--- a/PermissionController/res/values-bg-v33/strings.xml
+++ b/PermissionController/res/values-bg-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Още сигнали"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Отхвърлени сигнали"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Разгънете и вижте още един сигнал}other{Разгънете и вижте още # сигнала}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Свиване"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Сигнал. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Действието е завършено"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Проверете настройките, които могат да подобрят защитата на устройството ви"</string>
diff --git a/PermissionController/res/values-bg/strings.xml b/PermissionController/res/values-bg/strings.xml
index 9bd171db2..ce273f567 100644
--- a/PermissionController/res/values-bg/strings.xml
+++ b/PermissionController/res/values-bg/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Достъп до <xliff:g id="PERM">%1$s</xliff:g> на <xliff:g id="DEVICE_NAME">%2$s</xliff:g> за това приложение"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Преглед на всички разрешения, предоставени за: <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Преглед на всички приложения с това разрешение"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Информация"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Настройки"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Показване на употребата на микрофона за Асистент"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Настройки за неизползваните приложения"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Премахване на разрешенията, ако приложението не се използва"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Отваряне на връзки"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"По подразбиране за работа"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Стандартни за частното пространство"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Оптимизирано за устройството"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Други"</string>
<string name="default_app_none" msgid="9084592086808194457">"Няма"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Стандартно за системата)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Няма приложения"</string>
@@ -674,9 +678,14 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Разрешаване на ограничените настройки"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Ограничена настройка"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"От съображения за сигурност понастоящем тази настройка не е налице."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Действието не е възможно по време на обаждане"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Тази настройка е блокирана с цел защита на устройството и данните ви"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Измамници може да се опитат да инсталират опасни приложения, като ви помолят да инсталирате неизвестни приложения от нов източник."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Измамници може да се опитат да поемат контрола над устройството ви, като ви помолят да разрешите на дадено приложение да осъществява достъп до услугите за достъпност."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Измамници може да се опитат да навредят на устройството ви посредством тази настройка."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"На приложението бе отказан достъп до <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Приложението поиска разрешение за достъп до поверителни данни, което може да изложи на риск личната или финансовата ви информация.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Възможно е приложението да не работи правилно без това ограничено разрешение. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Научете как да разрешите достъпа&lt;/a&gt;"</string>
- <string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"На приложението бе забранено да изпълнява функциите на основно <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
+ <string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Приложението не получи разрешение да бъде по подразбиране: <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_role" msgid="6369601947905234551">"Приложението поиска разрешения за достъп до поверителни данни, които може да изложат на риск личната или финансовата ви информация.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Възможно е приложението да не работи правилно без тези ограничени разрешения. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Научете как да разрешите достъпа&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_settings_default" msgid="1858092969721041576">"На приложението бе отказан достъп"</string>
<string name="enhanced_confirmation_dialog_desc_settings_default" msgid="6911632348359332981">"Достъпът до това разрешение може да изложи на риск личната и финансовата ви информация.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Възможно е приложението да не работи правилно без това ограничено разрешение. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Научете как да разрешите достъпа&lt;/a&gt;"</string>
diff --git a/PermissionController/res/values-bn-v33/strings.xml b/PermissionController/res/values-bn-v33/strings.xml
index 6bef7e90d..9019de1de 100644
--- a/PermissionController/res/values-bn-v33/strings.xml
+++ b/PermissionController/res/values-bn-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"আরও সতর্কতা"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"বাতিল করা সতর্কতা"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{বড় করুন ও আরও একটি সতর্কতা দেখুন}one{বড় করুন ও আরও #টি সতর্কতা দেখুন}other{বড় করুন ও আরও #টি সতর্কতা দেখুন}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"আড়াল করুন"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"সতর্কতা। <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"অ্যাকশন সম্পূর্ণ হয়েছে"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"আপনার ডিভাইসকে আরও সুরক্ষিত করতে পারে এমন সেটিংস দেখুন"</string>
diff --git a/PermissionController/res/values-bn/strings.xml b/PermissionController/res/values-bn/strings.xml
index 4d7e6a55e..6572fa2c7 100644
--- a/PermissionController/res/values-bn/strings.xml
+++ b/PermissionController/res/values-bn/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>-এ এই অ্যাপের জন্য <xliff:g id="PERM">%1$s</xliff:g>-এর অ্যাক্সেস"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"<xliff:g id="APP">%1$s</xliff:g>-এর ক্ষেত্রে দেওয়া সব অনুমতি দেখুন"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"যেসব অ্যাপের এই অনুমতি আছে সেগুলি দেখুন"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"তথ্য"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"সেটিংস"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Assistant-এর মাইক্রোফোন ব্যবহার সম্পর্কিত ডেটা দেখুন"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ব্যবহার করা হয়নি এমন অ্যাপ সেটিংস"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"অ্যাপ ব্যবহার করা না হলে সেটি থেকে অনুমতি প্রত্যাহার করে নিন"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"লিঙ্ক খোলা"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"অফিসের জন্য ডিফল্ট"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"প্রাইভেট স্পেসের জন্য ডিফল্ট"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ডিভাইসের জন্য অপ্টিমাইজ করা হয়েছে"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"অন্যান্য"</string>
<string name="default_app_none" msgid="9084592086808194457">"কোনওটিই নয়"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(সিস্টেম ডিফল্ট)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"কোনও অ্যাপ নেই"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"বিধিনিষেধযুক্ত সেটিংসের অনুমতি দিন"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"বিধিনিষেধযুক্ত সেটিংস"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"আপনার নিরাপত্তার জন্য, এই সেটিং বর্তমানে উপলভ্য নেই।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"কল চলাকালীন অ্যাকশন সম্পূর্ণ করা যাচ্ছে না"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n আপনার ডিভাইস ও ডেটা সুরক্ষিত রাখতে এই সেটিং ব্লক করা হয়েছে"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"স্ক্যামাররা নতুন সোর্স থেকে অজানা অ্যাপ ইনস্টল করতে বলার মাধ্যমে, ক্ষতিকর অ্যাপ ইনস্টল করার চেষ্টা করতে পারে।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"স্ক্যামাররা কোনও অ্যাপের জন্য অ্যাক্সেসিবিলিটি অ্যাক্সেস করার অনুমতি দিতে বলার মাধ্যমে, আপনার ডিভাইসের কন্ট্রোল নেওয়ার চেষ্টা করতে পারে।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"স্ক্যামাররা এই সেটিংয়ের মাধ্যমে আপনার ডিভাইসের ক্ষতি করার চেষ্টা করতে পারে।"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"অ্যাপকে <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> অ্যাক্সেস করতে দেওয়া হয়নি"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"অ্যাপটি সংবেদনশীল অনুমতি অ্যাক্সেস করার অনুরোধ জানিয়েছে, যার জন্য আপনার ব্যক্তিগত ও আর্থিক অবস্থা সম্পর্কিত তথ্যের ক্ষেত্রে ঝুঁকির সম্ভাবনা থাকতে পারে।<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>এই সীমাবদ্ধ অনুমতি ছাড়া অ্যাপটি সঠিকভাবে কাজ না করার সম্ভাবনাও আছে। &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;কীভাবে অ্যাক্সেস করার অনুমতি দেবেন সেই সম্পর্কে জানুন&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"অ্যাপকে ডিফল্ট <xliff:g id="ROLE_NAME">%1$s</xliff:g> হিসেবে কাজ করার অ্যাক্সেস দেওয়া হয়নি"</string>
diff --git a/PermissionController/res/values-bs-v33/strings.xml b/PermissionController/res/values-bs-v33/strings.xml
index 598dc6a6c..fb802c7df 100644
--- a/PermissionController/res/values-bs-v33/strings.xml
+++ b/PermissionController/res/values-bs-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Više upozorenja"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Odbačena upozorenja"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Proširite i vidite još jedno upozorenje}one{Proširite i vidite još # upozorenje}few{Proširite i vidite još # upozorenja}other{Proširite i vidite još # upozorenja}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Suzi"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Upozorenje. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Radnja je završena"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Provjerite postavke koje mogu dodatno zaštiti vaš uređaj"</string>
diff --git a/PermissionController/res/values-bs/strings.xml b/PermissionController/res/values-bs/strings.xml
index fe036961d..71c236fe9 100644
--- a/PermissionController/res/values-bs/strings.xml
+++ b/PermissionController/res/values-bs/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Pristup aplikaciji \"<xliff:g id="PERM">%1$s</xliff:g>\" za ovu aplikaciju na uređaju \"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>\""</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Prikaži sva odobrenja aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Prikaži sve aplikacije s ovim odobrenjem"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informacije"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Postavke"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Prikaži korištenje mikrofona asistenta"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Postavke za nekorištene aplikacije"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Ukloni odobrenja ako se aplikacija ne koristi"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Otvaranje linkova"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Uobičajeno za rad"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Zadano za privatni prostor"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizirano za uređaj"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Drugo"</string>
<string name="default_app_none" msgid="9084592086808194457">"Nema"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Sistemski zadano)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nema aplikacija"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> želi otpremiti informacije o otklanjanju grešaka."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Podijeliti podatke o otklanjanju grešaka?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Sistem je otkrio problem."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> traži da s ovog uređaja otpremi izvještaj o greškama koji je izvršen <xliff:g id="DATE">%2$s</xliff:g> u <xliff:g id="TIME">%3$s</xliff:g>. Izvještaji o greškama uključuju lične informacije o vašem uređaju ili akreditivima za prijave u aplikacije, naprimjer korisnička imena, podatke o lokaciji, identifikatore uređaja i informacije o mreži. Izvještaje o greškama dijelite samo s osobama i aplikacijama kojim vjerujete. Dozvoliti aplikaciji <xliff:g id="APP_NAME_1">%4$s</xliff:g> da otpremi izvještaj o greškama?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> traži da s ovog uređaja otpremi izvještaj o greškama napravljen <xliff:g id="DATE">%2$s</xliff:g> u <xliff:g id="TIME">%3$s</xliff:g>. Izvještaji o greškama uključuju lične informacije o vašem uređaju ili koje su zabilježile aplikacije, npr. korisnička imena, podatke o lokaciji, identifikatore uređaja i informacije o mreži. Izvještaje o greškama dijelite samo s osobama i aplikacijama kojim vjerujete. Dozvoliti aplikaciji <xliff:g id="APP_NAME_1">%4$s</xliff:g> da otpremi izvještaj o greškama?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Došlo je do greške prilikom obrade izvještaja o greškama za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>. Stoga je odbijeno dijeljenje detaljnih podataka o otklanjanju grešaka. Izvinjavamo se na prekidu."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Dozvoli"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Odbij"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Dozvoli ograničene postavke"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Ograničena postavka"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Radi vaše sigurnosti postavka trenutno nije dostupna."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Nije moguće izvršiti radnju tokom poziva"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Blokirali smo postavku da zaštitimo vaš uređaj i podatke"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Prevaranti mogu pokušati instalirati štetne aplikacije traženjem da instalirate nepoznate aplikacije iz novog izvora."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Prevaranti mogu pokušati preuzeti kontrolu nad uređajem traženjem da dozvolite pristup funkcijama pristupačnosti za aplikaciju."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Prevaranti mogu pokušati naštetiti uređaju putem ove postavke."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Aplikaciji je odbijen pristup odobrenju <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Aplikacija je zatražila pristup osjetljivom odobrenju, što može ugroziti vaše lične i finansijske informacije.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Moguće je da aplikacija neće ispravno raditi bez ovog ograničenog odobrenja. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Saznajte kako dozvoliti pristup&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikaciji je odbijen pristup da bude zadana <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-ca-v33/strings.xml b/PermissionController/res/values-ca-v33/strings.xml
index 0526fffbc..6b7313973 100644
--- a/PermissionController/res/values-ca-v33/strings.xml
+++ b/PermissionController/res/values-ca-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Més alertes"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Alertes ignorades"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Desplega i consulta 1 alerta més}many{Desplega i consulta # alertes més}other{Desplega i consulta # alertes més}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Replega"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerta. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Acció completa"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Comprova les opcions de configuració que puguin afegir protecció al dispositiu"</string>
diff --git a/PermissionController/res/values-ca/strings.xml b/PermissionController/res/values-ca/strings.xml
index 1de8e07e6..6a10b3879 100644
--- a/PermissionController/res/values-ca/strings.xml
+++ b/PermissionController/res/values-ca/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Accés de <xliff:g id="PERM">%1$s</xliff:g> a aquesta aplicació (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>)"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Mostra tots els permisos per a <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Mostra totes les aplicacions que tenen aquest permís"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informació"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Configuració"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Mostra l\'ús del micròfon de l\'Assistent"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Configuració d\'aplicació no utilitzada"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Suprimeix els permisos si no s\'utilitza l\'aplicació"</string>
@@ -346,7 +348,7 @@
<string name="no_apps_allowed" msgid="7718822655254468631">"Cap aplicació amb permís"</string>
<string name="no_apps_allowed_full" msgid="8011716991498934104">"Cap aplicació té permís per accedir a tots els fitxers"</string>
<string name="no_apps_allowed_scoped" msgid="4908850477787659501">"Cap aplicació té permís per accedir només a fitxers multimèdia"</string>
- <string name="no_apps_denied" msgid="7663435886986784743">"Cap aplicació amb permís denegat"</string>
+ <string name="no_apps_denied" msgid="7663435886986784743">"Cap aplicació denegada"</string>
<string name="car_permission_selected" msgid="180837028920791596">"Seleccionat"</string>
<string name="settings" msgid="5409109923158713323">"Configuració"</string>
<string name="accessibility_service_dialog_title_single" msgid="7956432823014102366">"<xliff:g id="SERVICE_NAME">%s</xliff:g> té accés complet al dispositiu"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Obertura d\'enllaços"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Predeterminada per a la feina"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Predeterminades per a l\'espai privat"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimitzades per al dispositiu"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Altres"</string>
<string name="default_app_none" msgid="9084592086808194457">"Cap"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Opció predeterminada del sistema)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Cap aplicació"</string>
@@ -674,12 +678,17 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Permet la configuració restringida"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Opció de configuració restringida"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Per a la teva seguretat, aquesta opció de configuració no està disponible en aquests moments."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"No es pot completar l\'acció durant la trucada"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Aquesta opció de configuració està bloquejada per protegir el dispositiu i les dades"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Els estafadors poden intentar instal·lar aplicacions perjudicials demanant-te que instal·lis aplicacions desconegudes d\'una font nova."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"És possible que els estafadors intentin prendre el control del teu dispositiu demanant-te que donis a una aplicació accés a funcions d\'accessibilitat."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"És possible que els estafadors intentin malmetre el teu dispositiu amb aquesta opció de configuració."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"A l\'aplicació se li ha denegat l\'accés a <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"L\'aplicació ha demanat accés a un permís sensible, el qual pot posar en risc la teva informació personal o financera.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>És possible que l\'aplicació no funcioni correctament sense aquest permís restringit. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Més informació sobre com pots permetre l\'accés&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"A l\'aplicació se li ha denegat l\'accés per ser <xliff:g id="ROLE_NAME">%1$s</xliff:g> de manera predeterminada"</string>
<string name="enhanced_confirmation_dialog_desc_role" msgid="6369601947905234551">"L\'aplicació ha demanat accés a permisos sensibles, els quals poden posar en risc la teva informació personal o financera.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>És possible que l\'aplicació no funcioni correctament sense aquests permisos restringits. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Més informació sobre com pots permetre l\'accés&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_settings_default" msgid="1858092969721041576">"A l\'aplicació se li ha denegat l\'accés"</string>
- <string name="enhanced_confirmation_dialog_desc_settings_default" msgid="6911632348359332981">"L\'accés a aquest permís pot posar en risc la teva informació personal i financera.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>És possible que l\'aplicació no funcioni correctament sense aquest permís. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Més informació sobre com pots permetre l\'accés&lt;/a&gt;"</string>
+ <string name="enhanced_confirmation_dialog_desc_settings_default" msgid="6911632348359332981">"L\'accés a aquest permís pot posar en risc la teva informació personal i financera.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>És possible que l\'aplicació no funcioni correctament sense aquest permís restringit. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Més informació sobre com pots permetre l\'accés&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_learn_more" msgid="5226619861379095709">"Més informació"</string>
<string name="enhanced_confirmation_dialog_ok" msgid="8560373821598619924">"D\'acord"</string>
<string name="permission_grant_dialog_streaming_blocked_title" msgid="8905241017017043649">"S\'ha suprimit la sol·licitud de permís"</string>
diff --git a/PermissionController/res/values-cs-v33/strings.xml b/PermissionController/res/values-cs-v33/strings.xml
index 84a0f9fd1..0f17ff2d3 100644
--- a/PermissionController/res/values-cs-v33/strings.xml
+++ b/PermissionController/res/values-cs-v33/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="role_dialer_request_description" msgid="6188305064871543419">"Tato aplikace vám bude moci zasílat oznámení a bude mít přístup k fotoaparátu, kontaktům, mikrofonu, telefonu a SMS"</string>
- <string name="role_sms_request_description" msgid="1506966389698625395">"Tato aplikace vám bude moci zasílat oznámení a bude mít přístup k fotoaparátu, kontaktům, souborům, mikrofonu, telefonu a SMS"</string>
+ <string name="role_sms_request_description" msgid="1506966389698625395">"Tato aplikace vám bude moct zasílat oznámení a bude mít přístup k fotoaparátu, kontaktům, souborům, mikrofonu, telefonu a SMS"</string>
<string name="permission_description_summary_storage" msgid="1917071243213043858">"Aplikace s tímto oprávněním mají přístup ke všem souborům v tomto zařízení"</string>
<string name="work_policy_title" msgid="832967780713677409">"Informace o vašich pracovních zásadách"</string>
<string name="work_policy_summary" msgid="3886113358084963931">"Nastavení spravuje IT administrátor"</string>
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Další upozornění"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Zavřená upozornění"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Rozbalením zobrazíte jedno další upozornění}few{Rozbalením zobrazíte # další upozornění}many{Rozbalením zobrazíte # dalšího upozornění}other{Rozbalením zobrazíte # dalších upozornění}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Sbalit"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Upozornění. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Akce dokončena"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Zkontrolujte nastavení, která mohou zvýšit ochranu vašeho zařízení"</string>
diff --git a/PermissionController/res/values-cs/strings.xml b/PermissionController/res/values-cs/strings.xml
index 48996724a..a48d5afb9 100644
--- a/PermissionController/res/values-cs/strings.xml
+++ b/PermissionController/res/values-cs/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Přístup k <xliff:g id="PERM">%1$s</xliff:g> pro tuto aplikaci v zařízení <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Zobrazit všechna oprávnění aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Zobrazit všechny aplikace s tímto oprávněním"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informace"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Nastavení"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Zobrazit používání mikrofonu asistentem"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Nastavení nepoužívaných aplikací"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Odebrat oprávnění, pokud se aplikace nepoužívá"</string>
@@ -381,7 +383,7 @@
<string name="role_emergency_search_keywords" msgid="1920007722599213358">"v případě nouze"</string>
<string name="role_home_label" msgid="3871847846649769412">"Výchozí aplikace pro domácnost"</string>
<string name="role_home_short_label" msgid="8544733747952272337">"Vstupní aplikace"</string>
- <string name="role_home_description" msgid="7997371519626556675">"Aplikace (tzv. spouštěče), které nahrazují plochu na zařízení Android a dávají vám přístup k obsahu a funkcím zařízení."</string>
+ <string name="role_home_description" msgid="7997371519626556675">"Aplikace (tzv. spouštěče), které nahrazují plochu na zařízení Android a dávají vám přístup k obsahu a funkcím zařízení."</string>
<string name="role_home_request_title" msgid="738136983453341081">"Nastavit <xliff:g id="APP_NAME">%1$s</xliff:g> jako výchozí aplikaci pro domácnost?"</string>
<string name="role_home_request_description" msgid="2658833966716057673">"Není potřeba žádné oprávnění"</string>
<string name="role_home_search_keywords" msgid="3830755001192666285">"spouštěč"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Otevírání odkazů"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Výchozí pracovní"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Výchozí pro soukromý prostor"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimalizováno pro zařízení"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Jiné"</string>
<string name="default_app_none" msgid="9084592086808194457">"Žádná aplikace"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Výchozí nastavení systému)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Žádné aplikace"</string>
@@ -450,7 +454,7 @@
<string name="special_app_access_no_apps" msgid="4102911722787886970">"Žádné aplikace"</string>
<string name="home_missing_work_profile_support" msgid="1756855847669387977">"Nepodporuje pracovní profil"</string>
<string name="encryption_unaware_confirmation_message" msgid="8274491794636402484">"Poznámka: Pokud restartujete zařízení a máte nastavený zámek obrazovky, tato aplikace se nespustí, dokud zařízení neodemknete."</string>
- <string name="assistant_confirmation_message" msgid="7476540402884416212">"Asistent bude moci číst informace o aplikacích používaných v systému, včetně údajů viditelných na obrazovce a přístupných v rámci aplikací."</string>
+ <string name="assistant_confirmation_message" msgid="7476540402884416212">"Asistent bude moct číst informace o aplikacích používaných v systému, včetně údajů viditelných na obrazovce a přístupných v rámci aplikací."</string>
<string name="incident_report_channel_name" msgid="3144954065936288440">"Sdílet data ladění"</string>
<string name="incident_report_notification_title" msgid="4635984625656519773">"Sdílet podrobná data pro ladění?"</string>
<string name="incident_report_notification_text" msgid="3376480583513587923">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> chce nahrát informace pro ladění."</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Povolit omezená nastavení"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Blokované nastavení"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Z důvodu vaší bezpečnosti toto nastavení momentálně není dostupné."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Akci nelze dokončit během hovoru"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Toto nastavení je z důvodu ochrany zařízení a dat zablokováno"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Podvodníci se můžou pokusit nainstalovat škodlivé aplikace tím, že vás požádají o instalaci neznámých aplikací z nového zdroje."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Podvodníci se můžou pokusit převzít nad vaším zařízením kontrolu tím, že vás požádají, abyste aplikaci povolili přístup ke službám pro usnadnění."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Podvodníci se tímto nastavením můžou pokusit poškodit zařízení."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Aplikaci byl odepřen přístup k oprávnění <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Aplikace požádala o přístup k citlivému oprávnění, které může ohrozit vaše osobní a finanční údaje.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Je možné, že aplikace bez tohoto oprávnění nebude fungovat správně. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Jak povolit přístup&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikaci byla odepřena role výchozí <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-da-v33/strings.xml b/PermissionController/res/values-da-v33/strings.xml
index 3e532459f..a1caec0e7 100644
--- a/PermissionController/res/values-da-v33/strings.xml
+++ b/PermissionController/res/values-da-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Flere underretninger"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Lukkede underretninger"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Udvid for at se én yderligere advarsel}one{Udvid for at se # yderligere advarsel}other{Udvid for at se # yderligere advarsler}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Skjul"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Underretning. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Handlingen er udført"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Tjek indstillinger, der kan øge sikkerheden på din enhed"</string>
diff --git a/PermissionController/res/values-da/strings.xml b/PermissionController/res/values-da/strings.xml
index 6a2456bd6..67617fad9 100644
--- a/PermissionController/res/values-da/strings.xml
+++ b/PermissionController/res/values-da/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"adgang til <xliff:g id="PERM">%1$s</xliff:g> for denne app på <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Se alle tilladelser for <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Se alle apps med denne tilladelse"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Oplysninger"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Indstillinger"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Vis brug af Assistent-mikrofonen"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Ubrugte appindstillinger"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Fjern tilladelser, hvis appen ikke bruges"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Åbning af links"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Standard til arbejde"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Standard for privat område"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimeret til enheden"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Andre"</string>
<string name="default_app_none" msgid="9084592086808194457">"Ingen"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Systemstandard)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Ingen apps"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> vil gerne uploade fejretningsoplysninger."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Vil du dele fejlretningsdata?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Systemet har registreret et problem."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> anmoder om at uploade en fejlrapport fra denne enhed, som stammer fra <xliff:g id="DATE">%2$s</xliff:g> kl. <xliff:g id="TIME">%3$s</xliff:g>. Fejlrapporter indeholder personlige oplysninger om din enhed eller registreres af apps, f.eks. brugernavne, lokationsdata, enheds-id\'er og netværksoplysninger. Oplysningerne i fejlrapporterne må kun deles med personer og apps, du har tillid til. Vil du give <xliff:g id="APP_NAME_1">%4$s</xliff:g> tilladelse til at uploade en fejlrapport?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> anmoder om at uploade en fejlrapport fra denne enhed, som stammer fra <xliff:g id="DATE">%2$s</xliff:g> kl. <xliff:g id="TIME">%3$s</xliff:g>. Fejlrapporter indeholder personlige oplysninger om din enhed, eller som er registreret af apps, f.eks. brugernavne, lokationsdata, enheds-id\'er og netværksoplysninger. Oplysningerne i fejlrapporterne må kun deles med personer og apps, du har tillid til. Vil du give <xliff:g id="APP_NAME_1">%4$s</xliff:g> tilladelse til at uploade en fejlrapport?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Der opstod en fejl i behandlingen af fejlrapporten for <xliff:g id="APP_NAME">%1$s</xliff:g>. Det er derfor ikke muligt at dele detaljerede fejlretningsdata. Vi beklager ulejligheden."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Tillad"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Afvis"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Tillad begrænsede indstillinger"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Begrænset indstilling"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Af hensyn til din sikkerhed er denne indstilling i øjeblikket ikke tilgængelig."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Handlingen kan ikke udføres under opkald"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Denne indstilling er blokeret for at beskytte din enhed og dine data"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Svindlere kan forsøge at installere skadelige apps ved at bede dig om at installere ukendte apps fra en ny kilde."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Svindlere kan forsøge at tage kontrollen over din enhed ved at bede dig om at give en app adgang til hjælpefunktioner."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Svindlere kan forsøge at skade din enhed med denne indstilling."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Appen blev nægtet adgang til <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Appen anmodede om adgang til en følsom tilladelse, der kan kompromittere dine personlige eller økonomiske oplysninger.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Appen fungerer muligvis ikke korrekt uden denne begrænsede tilladelse. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Se, hvordan du giver tilladelse&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Appen blev nægtet adgang til at være <xliff:g id="ROLE_NAME">%1$s</xliff:g> som standard"</string>
diff --git a/PermissionController/res/values-de-v33/strings.xml b/PermissionController/res/values-de-v33/strings.xml
index d82bd4619..d67d9c20a 100644
--- a/PermissionController/res/values-de-v33/strings.xml
+++ b/PermissionController/res/values-de-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Weitere Benachrichtigungen"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Ausgeblendete Benachrichtigungen"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Maximieren und eine weitere Warnung anzeigen}other{Maximieren und # weitere Warnungen anzeigen}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Minimieren"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Warnung. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Aktion abgeschlossen"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Einstellungen aufrufen, mit denen sich der Schutz meines Geräts verbessern lässt"</string>
diff --git a/PermissionController/res/values-de/strings.xml b/PermissionController/res/values-de/strings.xml
index efb3062d6..75b4f7f46 100644
--- a/PermissionController/res/values-de/strings.xml
+++ b/PermissionController/res/values-de/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g>-Zugriff für diese App auf dem <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Alle Berechtigungen der App „<xliff:g id="APP">%1$s</xliff:g>“ anzeigen"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Alle Apps mit dieser Berechtigung anzeigen"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informationen"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Einstellungen"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Nutzung der Berechtigung \"Mikrofon\" für Assistant anzeigen"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Nicht verwendete App-Einstellungen"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Berechtigungen entfernen, wenn die App nicht verwendet wird"</string>
@@ -362,7 +364,7 @@
<string name="role_browser_request_title" msgid="2895200507835937192">"<xliff:g id="APP_NAME">%1$s</xliff:g> als Standard-Browser-App festlegen?"</string>
<string name="role_browser_request_description" msgid="5888803407905985941">"Keine Berechtigungen erforderlich"</string>
<string name="role_dialer_label" msgid="1100224146343237968">"Standard-App zum Telefonieren"</string>
- <string name="role_dialer_short_label" msgid="7186888549465352489">"Telefon"</string>
+ <string name="role_dialer_short_label" msgid="7186888549465352489">"Telefon App"</string>
<string name="role_dialer_description" msgid="8768708633696539612">"Apps, mit denen du dein Gerät zum Telefonieren benutzen kannst"</string>
<string name="role_dialer_request_title" msgid="5959618560705912058">"<xliff:g id="APP_NAME">%1$s</xliff:g> als Standard-App zum Telefonieren festlegen?"</string>
<string name="role_dialer_request_description" msgid="6288839625724909320">"Diese App erhält Zugriff auf Folgendes: Kamera, Kontakte, Mikrofon, Telefon und SMS"</string>
@@ -370,7 +372,7 @@
<string name="role_sms_label" msgid="8456999857547686640">"Standard-SMS-App"</string>
<string name="role_sms_short_label" msgid="4371444488034692243">"SMS-App"</string>
<string name="role_sms_description" msgid="3424020199148153513">"Apps, mit denen du über deine Telefonnummer unter anderem SMS, Fotos oder Videos senden und empfangen kannst"</string>
- <string name="role_sms_request_title" msgid="7953552109601185602">"<xliff:g id="APP_NAME">%1$s</xliff:g>als Standard-SMS-App festlegen?"</string>
+ <string name="role_sms_request_title" msgid="7953552109601185602">"<xliff:g id="APP_NAME">%1$s</xliff:g> als Standard-SMS-App festlegen?"</string>
<string name="role_sms_request_description" msgid="2691004766132144886">"Diese App erhält Zugriff auf Folgendes: Kamera, Kontakte, Mikrofon, Dateien und Medien, Telefon und SMS"</string>
<string name="role_sms_search_keywords" msgid="8022048144395047352">"textnachricht, sms, sms schicken, sms senden, nachrichten, mms"</string>
<string name="role_emergency_label" msgid="7028825857206842366">"Standardmäßige Notfall-App"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Links öffnen"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Standard-Apps für Arbeit"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Standard-Apps für das vertrauliche Profil"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Für dein Gerät optimiert"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Sonstige"</string>
<string name="default_app_none" msgid="9084592086808194457">"Keine"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(System-Standard­einstellung)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Keine Apps"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> möchte Informationen zur Fehlerbehebung hochladen."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Daten zur Fehlerbehebung teilen?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Das System hat ein Problem erkannt."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> möchte einen Fehlerbericht von diesem Gerät hochladen, der am <xliff:g id="DATE">%2$s</xliff:g> um <xliff:g id="TIME">%3$s</xliff:g> erstellt wurde. Fehlerberichte enthalten Informationen zu deinem Gerät, die du persönlich eingegeben hast oder die von Apps aufgezeichnet werden, z. B. Nutzernamen, Standortdaten, Geräte-IDs und Netzwerkinformationen. Teile Fehlerberichte nur mit Personen und Apps, denen du vertraust. Darf <xliff:g id="APP_NAME_1">%4$s</xliff:g> einen Fehlerbericht hochladen?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> möchte einen Fehlerbericht von diesem Gerät hochladen, der am <xliff:g id="DATE">%2$s</xliff:g> um <xliff:g id="TIME">%3$s</xliff:g> erstellt wurde. Fehlerberichte enthalten Informationen zu deinem Gerät, die du persönlich eingegeben hast oder die von Apps aufgezeichnet werden, z. B. Nutzernamen, Standortdaten, Geräte-IDs und Netzwerk­informationen. Teile Fehlerberichte nur mit Personen und Apps, denen du vertraust. Darf <xliff:g id="APP_NAME_1">%4$s</xliff:g> einen Fehlerbericht hochladen?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Bei der Verarbeitung des Fehlerberichts für <xliff:g id="APP_NAME">%1$s</xliff:g> ist ein Fehler aufgetreten. Die detaillierten Daten zur Fehlerbehebung wurden daher nicht geteilt. Wir entschuldigen uns für die Störung."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Zulassen"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Ablehnen"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Eingeschränkte Einstellungen zulassen"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Eingeschränkte Einstellung"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Aus Sicherheitsgründen ist diese Einstellung derzeit nicht verfügbar."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Aktion während eines Anrufs nicht möglich"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Diese Einstellung ist zum Schutz deines Geräts und deiner Daten blockiert"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Betrüger könnten versuchen, schädliche Apps zu installieren, indem sie dich bitten, unbekannte Apps aus einer neuen Quelle zu installieren."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Betrüger könnten versuchen, die Kontrolle über dein Gerät zu übernehmen, indem sie dich bitten, einer App Zugriff auf Bedienungshilfen zu gewähren."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Betrüger könnten versuchen, mit dieser Einstellung Schäden auf deinem Gerät zu verursachen."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"App wurde Zugriff auf „<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>“ verweigert"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Die App hat Zugriff auf eine vertrauliche Berechtigung angefordert. Wenn du diesen zulässt, sind deine privaten Daten und Finanzdaten eventuell gefährdet.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Die App funktioniert ohne diese eingeschränkte Berechtigung aber möglicherweise nicht richtig. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Informationen dazu, wie du den Zugriff erlaubst&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"App wurde der Zugriff verweigert, die standardmäßige <xliff:g id="ROLE_NAME">%1$s</xliff:g> zu sein"</string>
diff --git a/PermissionController/res/values-el-v33/strings.xml b/PermissionController/res/values-el-v33/strings.xml
index 4cefe7f04..33dff873a 100644
--- a/PermissionController/res/values-el-v33/strings.xml
+++ b/PermissionController/res/values-el-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Περισσότερες ειδοποιήσεις"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Ειδοποιήσεις που παραβλέφθηκαν"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Αναπτύξτε και δείτε μία ακόμη ειδοποίηση}other{Αναπτύξτε και δείτε # ακόμη ειδοποιήσεις}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Σύμπτυξη"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Ειδοποίηση. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Η ενέργεια ολοκληρώθηκε"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Ελέγξτε ρυθμίσεις που μπορούν να προσθέσουν προστασία στη συσκευή σας"</string>
diff --git a/PermissionController/res/values-el/strings.xml b/PermissionController/res/values-el/strings.xml
index e000a2ac2..d361acbf7 100644
--- a/PermissionController/res/values-el/strings.xml
+++ b/PermissionController/res/values-el/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Πρόσβαση στην άδεια <xliff:g id="PERM">%1$s</xliff:g> για αυτή την εφαρμογή στη συσκευή <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Εμφάνιση όλων των αδειών της εφαρμογής <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Εμφάνιση όλων των εφαρμογών με αυτή την άδεια"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Πληροφορίες"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Ρυθμίσεις"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Εμφάνιση χρήσης μικροφώνου βοηθού"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Ρυθμίσεις μη χρησιμοποιούμενων εφαρμογών"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Καταργήστε τις άδειες, εάν η εφαρμογή δεν χρησιμοποιείται."</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Άνοιγμα συνδέσμων"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Προεπιλογή για εργασία"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Προεπιλογή για ιδιωτικό χώρο"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Βελτιστοποιημένες για τη συσκευή"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Άλλες"</string>
<string name="default_app_none" msgid="9084592086808194457">"Καμία"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Προεπιλογή συστήματος)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Δεν υπάρχουν εφαρμογές"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Επιτρέπονται περιορισμένες ρυθμίσεις"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Περιορισμένη ρύθμιση"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Για την ασφάλειά σας, αυτή η ρύθμιση δεν είναι διαθέσιμη αυτή τη στιγμή."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Αδύνατη ολοκλήρωση της ενέργειας κατά την κλήση"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Αυτή η ρύθμιση έχει αποκλειστεί για την προστασία της συσκευής και των δεδομένων σας"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Οι απατεώνες μπορεί να προσπαθήσουν να εγκαταστήσουν επιβλαβείς εφαρμογές ζητώντας να εγκαταστήσετε άγνωστες εφαρμογές από μια νέα πηγή."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Οι απατεώνες μπορεί να προσπαθήσουν να αποκτήσουν τον έλεγχο της συσκευής σας ζητώντας να επιτρέψετε την πρόσβαση σε λειτουργίες προσβασιμότητας για μια εφαρμογή."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Οι απατεώνες μπορεί να επιχειρήσουν να βλάψουν τη συσκευή σας με αυτή τη ρύθμιση."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Απορρίφθηκε η πρόσβαση της εφαρμογής στην άδεια <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Η εφαρμογή ζήτησε πρόσβαση σε μια άδεια πρόσβασης σε ευαίσθητες πληροφορίες, γεγονός που μπορεί να θέσει σε κίνδυνο τα προσωπικά και οικονομικά στοιχεία σας.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Είναι πιθανό η εφαρμογή να μην λειτουργεί σωστά χωρίς αυτή την περιορισμένη άδεια. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Μάθετε πώς μπορείτε να επιτρέψετε την πρόσβαση&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Δεν επιτράπηκε στην εφαρμογή να οριστεί ως η προεπιλεγμένη <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-en-rAU-v33/strings.xml b/PermissionController/res/values-en-rAU-v33/strings.xml
index 498c090f3..bf2e7692a 100644
--- a/PermissionController/res/values-en-rAU-v33/strings.xml
+++ b/PermissionController/res/values-en-rAU-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"More alerts"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Dismissed alerts"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Expand and see one more alert}other{Expand and see # more alerts}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Collapse"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alert. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Action complete"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Check settings that can add protection to your device"</string>
diff --git a/PermissionController/res/values-en-rAU/strings.xml b/PermissionController/res/values-en-rAU/strings.xml
index d15cb526b..e57c8beec 100644
--- a/PermissionController/res/values-en-rAU/strings.xml
+++ b/PermissionController/res/values-en-rAU/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g> access for this app on <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"See all <xliff:g id="APP">%1$s</xliff:g> permissions"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"See all apps with this permission"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Information"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Settings"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Show Assistant microphone usage"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Unused app settings"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Remove permissions if app isn’t used"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Opening links"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Default for work"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Default for private space"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimised for device"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Others"</string>
<string name="default_app_none" msgid="9084592086808194457">"None"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(System default)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"No apps"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Allow restricted settings"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Restricted setting"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"For your security, this setting is currently unavailable."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Can\'t complete action during call"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n This setting is blocked to protect your device and data"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Scammers may try to install harmful apps by asking you to install unknown apps from a new source."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Scammers may try to take control of your device by asking you to allow accessibility access for an app."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Scammers may attempt to harm your device with this setting."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"App was denied access to <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"The app requested access to a sensitive permission which can put your personal and financial info at risk.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>It\'s possible that the app won\'t work properly without this restricted permission. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Learn how to allow access&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"App was denied access to be default <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-en-rCA-v33/strings.xml b/PermissionController/res/values-en-rCA-v33/strings.xml
index 84fda13e2..8fbcf7e8c 100644
--- a/PermissionController/res/values-en-rCA-v33/strings.xml
+++ b/PermissionController/res/values-en-rCA-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"More alerts"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Dismissed alerts"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Expand and see one more alert}other{Expand and see # more alerts}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Collapse"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alert. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Action complete"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Check settings that can add protection to your device"</string>
diff --git a/PermissionController/res/values-en-rCA/strings.xml b/PermissionController/res/values-en-rCA/strings.xml
index bf9058a8c..6749e0e82 100644
--- a/PermissionController/res/values-en-rCA/strings.xml
+++ b/PermissionController/res/values-en-rCA/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g> access for this app on <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"See all <xliff:g id="APP">%1$s</xliff:g> permissions"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"See all apps with this permission"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Information"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Settings"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Show assistant microphone usage"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Unused app settings"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Remove permissions if app isn’t used"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Opening links"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Default for work"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Default for private space"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimized for device"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Others"</string>
<string name="default_app_none" msgid="9084592086808194457">"None"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(System default)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"No apps"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Allow restricted settings"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Restricted setting"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"For your security, this setting is currently unavailable."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Can’t complete action during call"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n This setting is blocked to protect your device and data"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Scammers may try to install harmful apps by asking you to install unknown apps from a new source."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Scammers may try to take control of your device by asking you to allow accessibility access for an app."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Scammers may attempt to harm your device with this setting."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"App was denied access to <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"The app requested access to a sensitive permission which can put your personal and financial info at risk.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>It\'s possible the app won\'t work properly without this restricted permission. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Learn how to allow access&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"App was denied access to be default <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-en-rGB-v33/strings.xml b/PermissionController/res/values-en-rGB-v33/strings.xml
index 498c090f3..bf2e7692a 100644
--- a/PermissionController/res/values-en-rGB-v33/strings.xml
+++ b/PermissionController/res/values-en-rGB-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"More alerts"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Dismissed alerts"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Expand and see one more alert}other{Expand and see # more alerts}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Collapse"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alert. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Action complete"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Check settings that can add protection to your device"</string>
diff --git a/PermissionController/res/values-en-rGB/strings.xml b/PermissionController/res/values-en-rGB/strings.xml
index 93508b04a..bce85d49c 100644
--- a/PermissionController/res/values-en-rGB/strings.xml
+++ b/PermissionController/res/values-en-rGB/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g> access for this app on <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"See all <xliff:g id="APP">%1$s</xliff:g> permissions"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"See all apps with this permission"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Information"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Settings"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Show Assistant microphone usage"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Unused app settings"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Remove permissions if app isn’t used"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Opening links"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Default for work"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Default for private space"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimised for device"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Others"</string>
<string name="default_app_none" msgid="9084592086808194457">"None"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(System default)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"No apps"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Allow restricted settings"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Restricted setting"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"For your security, this setting is currently unavailable."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Can\'t complete action during call"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n This setting is blocked to protect your device and data"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Scammers may try to install harmful apps by asking you to install unknown apps from a new source."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Scammers may try to take control of your device by asking you to allow accessibility access for an app."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Scammers may attempt to harm your device with this setting."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"App was denied access to <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"The app requested access to a sensitive permission which can put your personal and financial info at risk.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>It\'s possible that the app won\'t work properly without this restricted permission. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Learn how to allow access&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"App was denied access to be default <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-en-rIN-v33/strings.xml b/PermissionController/res/values-en-rIN-v33/strings.xml
index 498c090f3..bf2e7692a 100644
--- a/PermissionController/res/values-en-rIN-v33/strings.xml
+++ b/PermissionController/res/values-en-rIN-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"More alerts"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Dismissed alerts"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Expand and see one more alert}other{Expand and see # more alerts}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Collapse"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alert. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Action complete"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Check settings that can add protection to your device"</string>
diff --git a/PermissionController/res/values-en-rIN/strings.xml b/PermissionController/res/values-en-rIN/strings.xml
index 93508b04a..bce85d49c 100644
--- a/PermissionController/res/values-en-rIN/strings.xml
+++ b/PermissionController/res/values-en-rIN/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g> access for this app on <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"See all <xliff:g id="APP">%1$s</xliff:g> permissions"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"See all apps with this permission"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Information"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Settings"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Show Assistant microphone usage"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Unused app settings"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Remove permissions if app isn’t used"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Opening links"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Default for work"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Default for private space"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimised for device"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Others"</string>
<string name="default_app_none" msgid="9084592086808194457">"None"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(System default)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"No apps"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Allow restricted settings"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Restricted setting"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"For your security, this setting is currently unavailable."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Can\'t complete action during call"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n This setting is blocked to protect your device and data"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Scammers may try to install harmful apps by asking you to install unknown apps from a new source."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Scammers may try to take control of your device by asking you to allow accessibility access for an app."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Scammers may attempt to harm your device with this setting."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"App was denied access to <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"The app requested access to a sensitive permission which can put your personal and financial info at risk.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>It\'s possible that the app won\'t work properly without this restricted permission. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Learn how to allow access&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"App was denied access to be default <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-es-rUS-v33/strings.xml b/PermissionController/res/values-es-rUS-v33/strings.xml
index 0205fca80..52b8b9497 100644
--- a/PermissionController/res/values-es-rUS-v33/strings.xml
+++ b/PermissionController/res/values-es-rUS-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Más alertas"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Alertas descartadas"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Expande y ve una alerta más}many{Expande y ve # de alertas más}other{Expande y ve # alertas más}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Contraer"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerta: <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Se completó la acción"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Revisa los parámetros de configuración que pueden proteger aún más tu dispositivo"</string>
diff --git a/PermissionController/res/values-es-rUS/strings.xml b/PermissionController/res/values-es-rUS/strings.xml
index 1247b2d3e..cecd1771a 100644
--- a/PermissionController/res/values-es-rUS/strings.xml
+++ b/PermissionController/res/values-es-rUS/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Acceso a <xliff:g id="PERM">%1$s</xliff:g> para esta app en <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Ver todos los permisos de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Ver todas las apps que tienen este permiso"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Información"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Configuración"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Mostrar el uso del micrófono del Asistente"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Configuración de apps sin usar"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Quitar los permisos si la app no se usa"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Abrir vínculos"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Predeterminadas de trabajo"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Configuración predeterminada del espacio privado"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizadas para el dispositivo"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Otros"</string>
<string name="default_app_none" msgid="9084592086808194457">"Ninguna"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Predeterminada del sistema)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Sin apps"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Permitir configuración restringida"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Parámetro restringido"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Por seguridad, este parámetro de configuración no está disponible actualmente."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"No se puede completar la acción durante la llamada"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Se bloqueó este parámetro de configuración para proteger tu dispositivo y tus datos"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Los estafadores pueden solicitarte que instales apps desconocidas de una fuente nueva para instalar apps dañinas."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Los estafadores pueden solicitarte acceso a los parámetros de accesibilidad de una app para intentar tomar el control de tu dispositivo."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Los estafadores pueden intentar dañar tu dispositivo con este parámetro de configuración."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"A la app se le negó el acceso a <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"La app solicitó acceso a un permiso sensible, lo que puede poner en riesgo tu información financiera y personal.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Es posible que la app no funcione como corresponde sin este permiso restringido. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Descubre cómo permitir el acceso&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"A la app se le negó el acceso para usarse como <xliff:g id="ROLE_NAME">%1$s</xliff:g> de forma predeterminada"</string>
diff --git a/PermissionController/res/values-es-v33/strings.xml b/PermissionController/res/values-es-v33/strings.xml
index 0104103fe..fec5ccaee 100644
--- a/PermissionController/res/values-es-v33/strings.xml
+++ b/PermissionController/res/values-es-v33/strings.xml
@@ -30,9 +30,10 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Más alertas"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Alertas ignoradas"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Amplía para ver una alerta más}many{Amplía para ver # alertas más}other{Amplía para ver # alertas más}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Ocultar"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerta. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Acción completada"</string>
- <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Comprueba los ajustes que pueden proteger más tu dispositivo"</string>
+ <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Comprueba los ajustes que pueden ofrecer una mayor protección a tu dispositivo"</string>
<string name="safety_center_qs_page_landing" msgid="1717368301679228128">"Ajustes rápidos de seguridad y privacidad"</string>
<string name="safety_center_qs_close_button" msgid="1352313308176244599">"Cerrar"</string>
<string name="safety_center_qs_expand_action" msgid="2193190557696484169">"Desplegar y mostrar opciones"</string>
diff --git a/PermissionController/res/values-es/strings.xml b/PermissionController/res/values-es/strings.xml
index 402bd5214..8078fbf87 100644
--- a/PermissionController/res/values-es/strings.xml
+++ b/PermissionController/res/values-es/strings.xml
@@ -195,13 +195,15 @@
<string name="app_permission_button_allow_limited_access" msgid="8824410215149764113">"Permitir acceso limitado"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Ubicación precisa"</string>
<string name="approximate_image_description" msgid="938803699637069884">"Ubicación aproximada"</string>
- <string name="app_permission_location_accuracy" msgid="7166912915040018669">"Usar ubic. precisa"</string>
+ <string name="app_permission_location_accuracy" msgid="7166912915040018669">"Ubicación precisa"</string>
<string name="app_permission_location_accuracy_subtitle" msgid="2654077606404987210">"Cuando la ubicación precisa está desactivada, las aplicaciones pueden consultar tu ubicación aproximada"</string>
<string name="app_permission_title" msgid="2090897901051370711">"Permiso de <xliff:g id="PERM">%1$s</xliff:g>"</string>
<string name="app_permission_header" msgid="2951363137032603806">"Acceso a <xliff:g id="PERM">%1$s</xliff:g> para esta aplicación"</string>
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Acceso de <xliff:g id="PERM">%1$s</xliff:g> a esta aplicación en <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Ver todos los permisos de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Ver todas las aplicaciones con este permiso"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Información"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Ajustes"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Mostrar el uso del micrófono del Asistente"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Ajustes de aplicaciones sin usar"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Quitar permisos si la aplicación no se usa"</string>
@@ -250,13 +252,13 @@
<string name="app_permission_most_recent_denied_summary" msgid="7659497197737708112">"Actualmente denegado / Último acceso: <xliff:g id="TIME_DATE">%1$s</xliff:g>"</string>
<string name="app_permission_never_accessed_summary" msgid="401346181461975090">"No ha accedido nunca"</string>
<string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"Denegado / Último acceso: Nunca"</string>
- <string name="allowed_header" msgid="7769277978004790414">"Permitido"</string>
- <string name="allowed_always_header" msgid="6455903312589013545">"Permitidas siempre"</string>
+ <string name="allowed_header" msgid="7769277978004790414">"Permitidas"</string>
+ <string name="allowed_always_header" msgid="6455903312589013545">"Con permiso siempre"</string>
<string name="allowed_foreground_header" msgid="6845655788447833353">"Permitidas solo mientras se usan"</string>
<string name="allowed_storage_scoped" msgid="5383645873719086975">"Pueden acceder solo al contenido multimedia"</string>
<string name="allowed_storage_full" msgid="5356699280625693530">"Pueden gestionar todos los archivos"</string>
<string name="ask_header" msgid="2633816846459944376">"Preguntar siempre"</string>
- <string name="denied_header" msgid="903209608358177654">"No permitidas"</string>
+ <string name="denied_header" msgid="903209608358177654">"No permitido"</string>
<string name="permission_group_name_with_device_name" msgid="8798741850536024820">"<xliff:g id="PERM_GROUP_NAME">%1$s</xliff:g> en <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Ver más aplicaciones que pueden acceder a todos los archivos"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 día}many{# días}other{# días}}"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Abrir enlaces"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Predeterminadas para trabajo"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Predeterminadas para el espacio privado"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizadas para el dispositivo"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Otras"</string>
<string name="default_app_none" msgid="9084592086808194457">"Ninguna"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Predeterminado del sistema)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"No hay aplicaciones"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Permitir ajustes restringidos"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Ajuste restringido"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Por seguridad, este ajuste no está disponible actualmente."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"No se puede completar la acción durante la llamada"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Este ajuste está bloqueado para proteger tu dispositivo y tus datos"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Los estafadores pueden pedirte que instales aplicaciones de una fuente nueva para instalar aplicaciones dañinas."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Los estafadores pueden solicitarte acceso a los ajustes de accesibilidad de una aplicación para intentar tomar el control de tu dispositivo."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Los estafadores pueden intentar dañar tu dispositivo con este ajuste."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Se ha denegado el acceso a <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> a la aplicación"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"La aplicación ha solicitado acceso a un permiso sensible que puede poner en riesgo tu información personal y financiera.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Es posible que la aplicación no funcione correctamente sin este permiso restringido. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Consulta cómo permitir el acceso&lt;/a&gt;."</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Se ha denegado el acceso para que la aplicación sea <xliff:g id="ROLE_NAME">%1$s</xliff:g> predeterminada"</string>
diff --git a/PermissionController/res/values-et-v33/strings.xml b/PermissionController/res/values-et-v33/strings.xml
index e5a6edf90..56dd683ca 100644
--- a/PermissionController/res/values-et-v33/strings.xml
+++ b/PermissionController/res/values-et-v33/strings.xml
@@ -30,9 +30,10 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Rohkem teavitusi"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Hoiatused, millest on loobutud"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Laiendage ja vaadake veel ühte hoiatust}other{Laiendage ja vaadake veel # hoiatust}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Ahenda"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Hoiatus. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Toiming on lõpetatud"</string>
- <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Kontrollige seadeid, mis võivad teie seadme kaitset tõhusamaks muuta"</string>
+ <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Kontrollige seadeid, mis võivad teie seadme kaitset tõhusamaks muuta."</string>
<string name="safety_center_qs_page_landing" msgid="1717368301679228128">"Turvalisuse ja privaatsuse kiirseaded"</string>
<string name="safety_center_qs_close_button" msgid="1352313308176244599">"Sule"</string>
<string name="safety_center_qs_expand_action" msgid="2193190557696484169">"Laienda ja kuva valikud"</string>
diff --git a/PermissionController/res/values-et/strings.xml b/PermissionController/res/values-et/strings.xml
index 4d93ac4c0..4cd1e007a 100644
--- a/PermissionController/res/values-et/strings.xml
+++ b/PermissionController/res/values-et/strings.xml
@@ -202,12 +202,14 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g> juurdepääs sellele rakendusele seadmes <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Kuva rakenduse <xliff:g id="APP">%1$s</xliff:g> kõik load"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Kuva kõik selle loaga rakendused"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Teave"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Seaded"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Kuva assistendi mikrofoni kasutamine"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Kasutamata rakenduse seaded"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Eemalda load, kui rakendust ei kasutata"</string>
<string name="unused_apps_label" msgid="2595428768404901064">"Eemalda load ja vabasta ruumi"</string>
<string name="unused_apps_label_v2" msgid="7058776770056517980">"Kasutamata rakenduse tegevuste peatamine"</string>
- <string name="unused_apps_label_v3" msgid="693340578642156657">"Halda kasutamata rakendusi"</string>
+ <string name="unused_apps_label_v3" msgid="693340578642156657">"Kasutamata rakenduste haldamine"</string>
<string name="unused_apps_summary" msgid="8839466950318403115">"Eemaldatakse load, kustutatakse ajutised failid ja peatatakse märguanded"</string>
<string name="unused_apps_summary_v2" msgid="5011313200815115802">"Eemalda load, kustuta ajutised failid, peata märguanded ja arhiivi rakendus"</string>
<string name="auto_revoke_summary" msgid="5867548789805911683">"Teie andmete kaitsmiseks eemaldatakse selle rakenduse load, kui seda mõne kuu jooksul ei kasutata."</string>
@@ -300,7 +302,7 @@
<string name="accessibility_remove_access_button_label" msgid="44145801526711640">"Eemalda juurdepääs"</string>
<string name="accessibility_show_all_apps_button_label" msgid="960067249326392280">"Kuva täieliku juurdepääsuga rakendused"</string>
<string name="accessibility_remove_access_success_label" msgid="4380995302917014670">"Juurdepääs eemaldati"</string>
- <string name="safety_center_notification_app_label" msgid="2457720616141926534">"Android-süsteem"</string>
+ <string name="safety_center_notification_app_label" msgid="2457720616141926534">"Androidi süsteem"</string>
<string name="auto_revoke_after_notification_title" msgid="5417761027669887431">"Rakenduse load eemaldati privaatsuse kaitsmiseks"</string>
<string name="auto_revoke_after_notification_content_one" msgid="6804038707453662753">"Rakendust <xliff:g id="APP_NAME">%s</xliff:g> ei ole mõne kuu jooksul kasutatud. Puudutage ülevaatamiseks."</string>
<string name="auto_revoke_after_notification_content_two" msgid="9108709764831425172">"Rakendust <xliff:g id="APP_NAME">%s</xliff:g> ja veel 1 rakendust ei ole mõne kuu jooksul kasutatud. Puudutage ülevaatamiseks."</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Linkide avamine"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Vaikerakendused töö jaoks"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Privaatse ruumi vaikerakendused"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Seadme jaoks optimeeritud"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Muud"</string>
<string name="default_app_none" msgid="9084592086808194457">"Puudub"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Süsteemi vaikeseade)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Rakendusi pole"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Piiratud seadete lubamine"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Piiratud seade"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Teie turvalisuse huvides pole see seade praegu saadaval."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Seda toimingut ei saa kõne ajal teha"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n See seade on teie seadme ja andmete kaitsmiseks blokeeritud"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Petised võivad proovida installida kahjulikke rakendusi, paludes teil installida uuest allikast tundmatuid rakendusi."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Petised võivad proovida teie seadet oma kontrolli alla haarata, paludes teil lubada rakenduse jaoks juurdepääsu juurdepääsetavusfunktsioonidele."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Petised võivad proovida selle seade abil teie seadmele kahju teha."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Rakendusele ei antud luba <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Rakendus taotles tundlikku luba, mis võib teie isikuandmed ja finantsteabe ohtu seada.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Võimalik, et rakendus ei tööta ilma selle piiratud loata korralikult. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt; Teave juurdepääsu andmise kohta&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Rakendusele ei antud luba olla vaikimisi <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-eu-v33/strings.xml b/PermissionController/res/values-eu-v33/strings.xml
index 424feaf4a..c50fe66c9 100644
--- a/PermissionController/res/values-eu-v33/strings.xml
+++ b/PermissionController/res/values-eu-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Alerta gehiago"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Baztertutako alertak"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Zabaldu eta ikusi beste alerta bat}other{Zabaldu eta ikusi # alerta gehiago}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Tolestu"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerta. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Osatu da ekintza"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Egiaztatu gailuaren segurtasuna hobe dezaketen ezarpenak"</string>
diff --git a/PermissionController/res/values-eu/strings.xml b/PermissionController/res/values-eu/strings.xml
index 139f41668..5b60129e9 100644
--- a/PermissionController/res/values-eu/strings.xml
+++ b/PermissionController/res/values-eu/strings.xml
@@ -60,7 +60,7 @@
<string name="grant_dialog_button_allow_all_files" msgid="4955436994954829894">"Eman fitxategi guztiak kudeatzeko baimena"</string>
<string name="grant_dialog_button_allow_media_only" msgid="4832877658422573832">"Eman multimedia-fitxategiak erabiltzeko baimena"</string>
<string name="app_permissions_breadcrumb" msgid="5136969550489411650">"Aplikazioak"</string>
- <string name="app_permissions" msgid="3369917736607944781">"Aplikazio-baimenak"</string>
+ <string name="app_permissions" msgid="3369917736607944781">"Aplikazio-baimenaU+2060k"</string>
<string name="unused_apps" msgid="2058057455175955094">"Erabiltzen ez diren aplikazioak"</string>
<string name="edit_photos_description" msgid="5540108003480078892">"Editatu aplikazio honetarako hautatutako argazkiak"</string>
<string name="no_unused_apps" msgid="12809387670415295">"Ez dago erabiltzen ez duzun aplikaziorik"</string>
@@ -78,7 +78,7 @@
<string name="never_ask_again" msgid="4728762438198560329">"Ez galdetu berriro"</string>
<string name="no_permissions" msgid="3881676756371148563">"Ez dago baimenik"</string>
<string name="additional_permissions" msgid="5801285469338873430">"Baimen gehigarriak"</string>
- <string name="app_permissions_info_button_label" msgid="7633312050729974623">"Ireki aplikazioaren informazioa"</string>
+ <string name="app_permissions_info_button_label" msgid="7633312050729974623">"Ireki aplikazioari buruzko informazioa"</string>
<string name="additional_permissions_more" msgid="5681220714755304407">"{count,plural, =1{Beste #}other{Beste #}}"</string>
<string name="old_sdk_deny_warning" msgid="2382236998845153919">"Android-en bertsio zaharrago baterako diseinatuta dago aplikazio hau. Baimena ukatzen baduzu, agian aurrerantzean ez du behar bezala funtzionatuko."</string>
<string name="storage_supergroup_warning_allow" msgid="103093462784523190">"Android-en bertsio zaharrago baterako dago diseinatuta aplikazio hau. Baimena ematen baduzu, biltegi osoa erabiltzeko baimena emango da (argazkiak, musika, audioa eta bestelako fitxategiak atzitzekoa barne)."</string>
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g> erabiltzeko baimena aplikazio honetarako <xliff:g id="DEVICE_NAME">%2$s</xliff:g> gailuan"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Ikusi <xliff:g id="APP">%1$s</xliff:g> aplikazioaren baimen guztiak"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Ikusi baimen hau duten aplikazio guztiak"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informazioa"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Ezarpenak"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Erakutsi laguntzaileak mikrofonoa erabiltzeko duen baimena"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Erabili gabeko aplikazioen ezarpenak"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Kendu baimenak aplikazioa erabiltzen ez bada"</string>
@@ -263,7 +265,7 @@
<string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ordu}other{# ordu}}"</string>
<string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minutu}other{# minutu}}"</string>
<string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# segundo}other{# segundo}}"</string>
- <string name="permission_reminders" msgid="6528257957664832636">"Baimenen abisuak"</string>
+ <string name="permission_reminders" msgid="6528257957664832636">"Baimenen gogorarazpenak"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"Erabiltzen ez den 1 aplikazio"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Erabiltzen ez diren <xliff:g id="NUMBER_OF_APPS">%s</xliff:g> aplikazio"</string>
<string name="auto_revoke_permission_reminder_notification_content" msgid="4492228990462107487">"Baimenak kendu egin dira zure pribatutasuna babesteko. Sakatu berrikusteko."</string>
@@ -279,7 +281,7 @@
<string name="post_drive_permission_decision_reminder_summary_1_app_2_permissions" msgid="671791184670801301">"Gidatu bitartean, <xliff:g id="PERMISSION_1">%2$s</xliff:g> eta <xliff:g id="PERMISSION_2">%3$s</xliff:g> erabiltzeko baimena eman diozu <xliff:g id="APP">%1$s</xliff:g> aplikazioari"</string>
<string name="post_drive_permission_decision_reminder_summary_1_app_multi_permission" msgid="4080701771111456927">"Gidatu bitartean, <xliff:g id="COUNT">%1$d</xliff:g> baimen eman dizkiozu <xliff:g id="APP">%2$s</xliff:g> aplikazioari"</string>
<string name="post_drive_permission_decision_reminder_summary_multi_apps" msgid="5253882771252863902">"{count,plural, =1{Gidatu bitartean, baimenak eman dizkiezu <xliff:g id="APP_0">%1$s</xliff:g> eta beste # aplikaziori}other{Gidatu bitartean, baimenak eman dizkiezu <xliff:g id="APP_1">%1$s</xliff:g> eta beste # aplikaziori}}"</string>
- <string name="go_to_settings" msgid="1053735612211228335">"Joan ezarpenetara"</string>
+ <string name="go_to_settings" msgid="1053735612211228335">"Joan Ezarpenak atalera"</string>
<string name="auto_revoke_setting_subtitle" msgid="8631720570723050460">"Aplikazio batzuk ez dira erabili zenbait hilabetez"</string>
<string name="permissions_removed_category_title" msgid="1064754271178447643">"Baimenak kendu zaizkien aplikazioak"</string>
<string name="permission_removed_page_title" msgid="2627436155091001209">"Baimenak kendu zaizkien aplikazioak"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Irekiko diren estekak"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Lanerako aplikazio lehenetsiak"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Eremu pribatuko aplikazio lehenetsiak"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Gailurako optimizatuta"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Beste batzuk"</string>
<string name="default_app_none" msgid="9084592086808194457">"Bat ere ez"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(sistemaren aplikazio lehenetsia)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Ez dago aplikaziorik"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Eman ezarpen mugatuak erabiltzeko baimena"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Murriztapenak ditu ezarpenak"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Zure segurtasuna bermatzeko, ezarpena ez dago erabilgarri une honetan."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Ezin da osatu ekintza deia abian den bitartean"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Ezarpen hau blokeatuta dago zure gailua eta datuak babesteko"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Baliteke iruzurgileak aplikazio kaltegarriak instalatzen saiatzea, aplikazio ezezagunak iturburu berri batetik instalatzeko eskatuta."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Baliteke iruzurgileak zure gailua kontrolatzen saiatzea, aplikazio batean erabilerraztasun-eginbideak erabiltzeko baimena eskatuta."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Baliteke iruzurgileak zure gailua kaltetzen saiatzea ezarpen honekin."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Aplikazioari <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> erabiltzeko baimena ukatu zaio"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Kontuzko informazioa erabiltzeko baimen bat eskatu du aplikazioak, eta agian horrek arriskuan jarriko ditu zure informazio pertsonala eta finantzei buruzko informazioa.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Baliteke aplikazioak behar bezala ez funtzionatzea baimen murriztu hori gabe. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Lortu baimena emateko argibideak&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikazioari <xliff:g id="ROLE_NAME">%1$s</xliff:g> lehenetsia izateko baimena ukatu zaio"</string>
diff --git a/PermissionController/res/values-fa-v33/strings.xml b/PermissionController/res/values-fa-v33/strings.xml
index 6c7abf7c5..4b93d824d 100644
--- a/PermissionController/res/values-fa-v33/strings.xml
+++ b/PermissionController/res/values-fa-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"هشدارهای بیشتر"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"هشدارهای ردشده"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{گسترده کردن و دیدن یک هشدار دیگر}one{گسترده کردن و دیدن # هشدار دیگر}other{گسترده کردن و دیدن # هشدار دیگر}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"جمع کردن"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"هشدار. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"کنش کامل شد"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"بررسی کردن تنظیماتی که می‌تواند امنیت بیشتری به دستگاهتان اضافه کند"</string>
diff --git a/PermissionController/res/values-fa-watch/strings.xml b/PermissionController/res/values-fa-watch/strings.xml
index b602b38f3..16acde24b 100644
--- a/PermissionController/res/values-fa-watch/strings.xml
+++ b/PermissionController/res/values-fa-watch/strings.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="grant_dialog_button_deny_dont_ask_again" msgid="5709879604352260492">"مجاز نیست، دیگر سؤال نشود"</string>
+ <string name="grant_dialog_button_deny_dont_ask_again" msgid="5709879604352260492">"مجاز نیست، دوباره پرسیده نشود"</string>
<string name="current_permission_template" msgid="6634462553790549887">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> / <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
<string name="preference_show_system_apps" msgid="1055740303992024300">"نمایش‌ برنامه‌های سیستم"</string>
<string name="permission_summary_enforced_by_policy" msgid="2352478756952948019">"امکان تغییر نیست"</string>
diff --git a/PermissionController/res/values-fa/strings.xml b/PermissionController/res/values-fa/strings.xml
index ae6063dcc..53f94f4c9 100644
--- a/PermissionController/res/values-fa/strings.xml
+++ b/PermissionController/res/values-fa/strings.xml
@@ -75,7 +75,7 @@
<string name="app_disable_dlg_positive" msgid="7418444149981904940">"غیرفعال کردن برنامه"</string>
<string name="app_disable_dlg_text" msgid="3126943217146120240">"‏اگر این برنامه را غیرفعال کنید، ممکن است Android و سایر برنامه‌های دیگر عملکرد موردانتظار را نداشته باشند. به‌خاطر داشته باشید که نمی‌توانید این برنامه را حذف کنید، چون از برنامه‌های ازپیش نصب‌شده روی دستگاه است. این برنامه، با غیرفعال کردن، خاموش می‌شود و در دستگاه پنهان می‌شود."</string>
<string name="app_permission_manager" msgid="3903811137630909550">"مدیر اجازه‌ها"</string>
- <string name="never_ask_again" msgid="4728762438198560329">"دوباره سؤال نشود"</string>
+ <string name="never_ask_again" msgid="4728762438198560329">"دوباره پرسیده نشود"</string>
<string name="no_permissions" msgid="3881676756371148563">"مجوزی موجود نیست"</string>
<string name="additional_permissions" msgid="5801285469338873430">"اجازه‌های جانبی"</string>
<string name="app_permissions_info_button_label" msgid="7633312050729974623">"باز کردن اطلاعات برنامه"</string>
@@ -200,8 +200,10 @@
<string name="app_permission_title" msgid="2090897901051370711">"اجازه <xliff:g id="PERM">%1$s</xliff:g>"</string>
<string name="app_permission_header" msgid="2951363137032603806">"دسترسی <xliff:g id="PERM">%1$s</xliff:g> برای این برنامه"</string>
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"دسترسی <xliff:g id="PERM">%1$s</xliff:g> از این برنامه در <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
- <string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"مشاهده همه اجازه‌های <xliff:g id="APP">%1$s</xliff:g>"</string>
+ <string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"دیدن همه اجازه‌های <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"دیدن همه برنامه‌هایی که این مجوز را دارند"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"اطلاعات"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"تنظیمات"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"نمایش میزان استفاده «دستیار» از میکروفون"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"تنظیمات برنامه‌های استفاده‌نشده"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"اگر از برنامه استفاده نمی‌شود، اجازه‌ها برداشته شوند"</string>
@@ -320,7 +322,7 @@
<string name="permission_subtitle_only_in_foreground" msgid="9068389431267377564">"تنها هنگام استفاده از برنامه"</string>
<string name="permission_subtitle_media_only" msgid="8917869683764720717">"رسانه"</string>
<string name="permission_subtitle_all_files" msgid="4982613338298067862">"همه فایل‌ها"</string>
- <string name="permission_subtitle_background" msgid="8916750995309083180">"همیشه مجاز بودن"</string>
+ <string name="permission_subtitle_background" msgid="8916750995309083180">"همیشه مجاز است"</string>
<string name="app_perms_24h_access" msgid="99069906850627181">"آخرین زمان دسترسی: <xliff:g id="TIME_DATE">%1$s</xliff:g>"</string>
<string name="app_perms_24h_access_yest" msgid="5411926024794555022">"آخرین زمان دسترسی دیروز ساعت <xliff:g id="TIME_DATE">%1$s</xliff:g>"</string>
<string name="app_perms_7d_access" msgid="4945055548894683751">"آخرین دسترسی: <xliff:g id="TIME_DATE_0">%1$s</xliff:g> ساعت <xliff:g id="TIME_DATE_1">%2$s</xliff:g>"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"باز کردن پیوندها"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"پیش‌فرض برای کار"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"برنامه‌های پیش‌فرض برای فضای خصوصی"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"بهینه‌سازی‌شده برای دستگاه"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"موارد دیگر"</string>
<string name="default_app_none" msgid="9084592086808194457">"هیچ‌کدام"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(پیش‌فرض سیستم)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"برنامه‌ای موجود نیست"</string>
@@ -601,7 +605,7 @@
<string name="active_app_usage_2_qs" msgid="6107866785243565283">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحال استفاده از آن است (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
<string name="recent_app_usage_2_qs" msgid="3591205954235694403">"اخیراً <xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده کرده است (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string>
<string name="media_confirm_dialog_positive_button" msgid="9020793594051526399">"تأیید"</string>
- <string name="media_confirm_dialog_negative_button" msgid="226987376924861785">"برگشت"</string>
+ <string name="media_confirm_dialog_negative_button" msgid="226987376924861785">"برگشتن"</string>
<string name="media_confirm_dialog_title_a_to_p_aural_allow" msgid="8560601114044699903">"دسترسی به فایل‌های دیگر نیز مجاز می‌شود"</string>
<string name="media_confirm_dialog_title_a_to_p_aural_deny" msgid="7841428716317307685">"دسترسی به فایل‌های دیگر نیز مجاز نمی‌شود"</string>
<string name="media_confirm_dialog_title_a_to_p_visual_allow" msgid="6469086448310893751">"دسترسی به فایل‌های دیگر نیز مجاز می‌شود"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"مجاز کردن تنظیمات محدودشده"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"تنظیم محدودشده"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"برای حفظ امنیت شما، درحال‌حاضر این تنظیم دردسترس نیست."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"این کنش درطول تماس تکمیل نمی‌شود"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n برای محافظت از دستگاه و داده‌های شما، این تنظیم مسدود شده است"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"کلاهبرداران ممکن است با درخواست نصب برنامه‌های ناشناس از منبعی جدید، تلاش کنند برنامه‌های مضر نصب کنند."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"کلاهبرداران ممکن است با درخواست مجاز کردن دسترسی به تنظیم دسترس‌پذیری برنامه‌ای، تلاش کنند کنترل دستگاهتان را به‌دست بگیرند."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"کلاهبرداران ممکن است بخواهند بااستفاده از این تنظیم به دستگاهتان آسیب برسانند."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"درخواست برنامه برای دسترسی به <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> رد شد"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"‏این برنامه درخواست دسترسی به اجازه‌ای حساس را داشته است که می‌تواند اطلاعات شخصی و مالی‌تان را درمعرض خطر قرار دهد.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>ممکن است برنامه بدون این اجازه محدودشده به‌درستی کار نکند. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;با نحوه اعطای دسترسی آشنا شوید&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"درخواست دسترسی برنامه برای تبدیل شدن به <xliff:g id="ROLE_NAME">%1$s</xliff:g> پیش‌فرض رد شد"</string>
diff --git a/PermissionController/res/values-fi-v33/strings.xml b/PermissionController/res/values-fi-v33/strings.xml
index 9b57c6172..edb8737b3 100644
--- a/PermissionController/res/values-fi-v33/strings.xml
+++ b/PermissionController/res/values-fi-v33/strings.xml
@@ -16,8 +16,8 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="role_dialer_request_description" msgid="6188305064871543419">"Sovellus saa luvan lähettää sinulle ilmoituksia ja pääsyn kameraan, yhteystietoihin, mikrofoniin, puhelimeen ja tekstiviesteihin"</string>
- <string name="role_sms_request_description" msgid="1506966389698625395">"Sovellus saa luvan lähettää sinulle ilmoituksia ja pääsyn kameraan, yhteystietoihin, tiedostoihin, mikrofoniin, puhelimeen ja tekstiviesteihin"</string>
+ <string name="role_dialer_request_description" msgid="6188305064871543419">"Sovellus saa luvan lähettää sinulle ilmoituksia ja se saa pääsyn kameraan, yhteystietoihin, mikrofoniin, puhelimeen ja tekstiviesteihin"</string>
+ <string name="role_sms_request_description" msgid="1506966389698625395">"Sovellus saa luvan lähettää sinulle ilmoituksia ja se saa pääsyn kameraan, yhteystietoihin, tiedostoihin, mikrofoniin, puhelimeen ja tekstiviesteihin"</string>
<string name="permission_description_summary_storage" msgid="1917071243213043858">"Sovellukset, joilla on tämä lupa, saavat pääsyn kaikkiin laitteen tiedostoihin"</string>
<string name="work_policy_title" msgid="832967780713677409">"Työkäytäntötietosi"</string>
<string name="work_policy_summary" msgid="3886113358084963931">"IT-järjestelmänvalvojan ylläpitämät asetukset"</string>
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Lisää ilmoituksia"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Hylätyt hälytykset"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Laajenna ja katso yksi muu ilmoitus}other{Laajenna ja katso # muuta ilmoitusta}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Tiivistä"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Hälytys. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Toiminto suoritettu"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Tarkista asetukset, jotka voivat parantaa laitteesi turvallisuutta"</string>
diff --git a/PermissionController/res/values-fi/strings.xml b/PermissionController/res/values-fi/strings.xml
index e47b603d3..1a9029bfa 100644
--- a/PermissionController/res/values-fi/strings.xml
+++ b/PermissionController/res/values-fi/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Tällä sovelluksella on pääsy (<xliff:g id="PERM">%1$s</xliff:g>) laitteella <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Näytä kaikki luvat, jotka <xliff:g id="APP">%1$s</xliff:g> on saanut"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Näytä kaikki sovellukset, joilla on tämä lupa"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Tiedot"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Asetukset"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Näytä Assistantin mikrofonin käyttö"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Käyttämättömien sovellusten asetukset"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Poista luvat, jos sovellusta ei käytetä"</string>
@@ -410,7 +412,7 @@
<string name="role_wallet_label" msgid="3719419175656204207">"Oletuslompakkosovellus"</string>
<string name="role_wallet_short_label" msgid="6521288403762457452">"Lompakkosovellus"</string>
<string name="role_wallet_description" msgid="3726535836165949838">"Lompakkosovelluksista on apua erilaisissa tilanteissa, koska niihin voi tallentaa esimerkiksi credit- ja kanta-asiakaskortit ja autonavaimet"</string>
- <string name="role_wallet_request_title" msgid="4770217108262737093">"Valitaanko <xliff:g id="APP_NAME">%1$s</xliff:g> oletuslompakkosovelluksesi?"</string>
+ <string name="role_wallet_request_title" msgid="4770217108262737093">"Asetetaanko <xliff:g id="APP_NAME">%1$s</xliff:g> oletuslompakkosovelluksesi?"</string>
<string name="role_wallet_request_description" msgid="6305487425777483053">"Lupia ei tarvita"</string>
<string name="request_role_current_default" msgid="738722892438247184">"Nykyinen oletus"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Älä kysy uudelleen"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Linkkien avaaminen"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Työkäytön oletus"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Oletus yksityiselle tilalle"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimoitu laitteelle"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Muut"</string>
<string name="default_app_none" msgid="9084592086808194457">"Ei mitään"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Järjestelmän oletusarvo)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Ei sovelluksia"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Salli rajoitetut asetukset"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Rajoitettu asetus"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Asetus ei ole tällä hetkellä käytettävissä turvallisuussyistä."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Toimintoa ei voi suorittaa puhelun aikana"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Tämä asetus on estetty laitteen ja datan suojaamiseksi"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Huijarit voivat yrittää asentaa haitallisia sovelluksia pyytämällä sinua asentamaan tuntemattomia sovelluksia uudesta lähteestä."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Huijarit voivat yrittää hallita laitetta pyytämällä sinua sallimaan sovellukselle pääsyn saavutettavuuteen."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Huijarit voivat yrittää vahingoittaa laitetta tällä asetuksella."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Sovellukselta on evätty pääsy: <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Sovellus on pyytänyt pääsyä arkaluontoiseen lupaan, joka voi vaarantaa henkilökohtaisia tietojasi ja taloustietojasi.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Sovellus ei välttämättä toimi oikein ilman tätä rajoitettua lupaa. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Lue, miten voit sallia pääsyn&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Sovellus ei saa olla oletuksena <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-fr-rCA-v33/strings.xml b/PermissionController/res/values-fr-rCA-v33/strings.xml
index a2f81bcea..ddbddf8e6 100644
--- a/PermissionController/res/values-fr-rCA-v33/strings.xml
+++ b/PermissionController/res/values-fr-rCA-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Plus d\'alertes"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Alertes ignorées"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Développez et affichez une autre alerte}one{Développez et affichez # autre alerte}many{Développez et affichez # d\'autres alertes}other{Développez et affichez # autres alertes}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Réduire"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerte. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Action terminée"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Vérifiez les paramètres qui peuvent protéger davantage votre appareil"</string>
diff --git a/PermissionController/res/values-fr-rCA/strings.xml b/PermissionController/res/values-fr-rCA/strings.xml
index 31b6bdbec..c8936db35 100644
--- a/PermissionController/res/values-fr-rCA/strings.xml
+++ b/PermissionController/res/values-fr-rCA/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Accès <xliff:g id="PERM">%1$s</xliff:g> pour cette appli sur <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Afficher toutes les autorisations pour <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Afficher toutes les applis qui possèdent cette autorisation"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Renseignements"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Paramètres"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Afficher l\'usage du microphone de l\'assistant"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Paramètres des applis inutilisées"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Retirer les autorisations si l\'appli est inutilisée"</string>
@@ -357,11 +359,11 @@
<string name="role_assistant_short_label" msgid="3369003713187703399">"Appli d\'assistant numérique"</string>
<string name="role_assistant_description" msgid="6622458130459922952">"Les applis d\'assistance peuvent vous aider en fonction de l\'information affichée à l\'écran. Certaines applis sont compatibles à la fois avec le lanceur d\'applis et les services d\'entrée vocale, vous permettant de bénéficier d\'une assistance intégrée."</string>
<string name="role_browser_label" msgid="2877796144554070207">"Appli de navigation par défaut"</string>
- <string name="role_browser_short_label" msgid="6745009127123292296">"Appli de navigateur"</string>
+ <string name="role_browser_short_label" msgid="6745009127123292296">"Appli de navigation"</string>
<string name="role_browser_description" msgid="3465253637499842671">"Applis qui vous donnent accès à Internet et qui affichent des liens que vous pouvez toucher"</string>
<string name="role_browser_request_title" msgid="2895200507835937192">"Définir <xliff:g id="APP_NAME">%1$s</xliff:g> comme appli par défaut pour la navigation?"</string>
<string name="role_browser_request_description" msgid="5888803407905985941">"Aucune autorisation nécessaire"</string>
- <string name="role_dialer_label" msgid="1100224146343237968">"Appli de téléphone par défaut"</string>
+ <string name="role_dialer_label" msgid="1100224146343237968">"Appli Téléphone par défaut"</string>
<string name="role_dialer_short_label" msgid="7186888549465352489">"Appli Téléphone"</string>
<string name="role_dialer_description" msgid="8768708633696539612">"Applis qui vous permettent de faire et de recevoir des appels téléphoniques sur votre appareil"</string>
<string name="role_dialer_request_title" msgid="5959618560705912058">"Définir <xliff:g id="APP_NAME">%1$s</xliff:g> comme appli de téléphonie par défaut?"</string>
@@ -380,8 +382,8 @@
<string name="role_emergency_request_description" msgid="131645948770262850">"Aucune autorisation nécessaire"</string>
<string name="role_emergency_search_keywords" msgid="1920007722599213358">"En cas d\'urgence"</string>
<string name="role_home_label" msgid="3871847846649769412">"Appli d\'accueil par défaut"</string>
- <string name="role_home_short_label" msgid="8544733747952272337">"Appli sur l\'écran d\'accueil"</string>
- <string name="role_home_description" msgid="7997371519626556675">"Applis qui remplacent les écrans d\'accueil sur votre appareil Android et qui vous donnent accès au contenu et aux fonctionnalités de votre appareil"</string>
+ <string name="role_home_short_label" msgid="8544733747952272337">"Appli d\'accueil"</string>
+ <string name="role_home_description" msgid="7997371519626556675">"Applis, aussi appelées lanceurs d\'applis, qui remplacent les écrans d\'accueil sur votre appareil Android et qui vous donnent accès au contenu et aux fonctionnalités de votre appareil"</string>
<string name="role_home_request_title" msgid="738136983453341081">"Définir <xliff:g id="APP_NAME">%1$s</xliff:g> comme appli d\'écran d\'accueil par défaut?"</string>
<string name="role_home_request_description" msgid="2658833966716057673">"Aucune autorisation nécessaire"</string>
<string name="role_home_search_keywords" msgid="3830755001192666285">"lanceur d\'applis"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Ouverture des liens"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Par défaut pour util. profess."</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Applis par défaut pour l\'Espace privé"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimisée pour l\'appareil"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Autres"</string>
<string name="default_app_none" msgid="9084592086808194457">"Aucune"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Paramètre(s) système par défaut)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Aucune appli"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Autoriser les paramètres restreints"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Paramètre restreint"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Pour protéger votre sécurité, ce paramètre n\'est pas accessible actuellement."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Impossible d\'effectuer l\'action pendant un appel"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Ce paramètre est bloqué pour protéger votre appareil et vos données"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Les escrocs peuvent tenter d\'installer des applis nuisibles en vous demandant d\'installer des applis inconnues à partir d\'une nouvelle source."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Les escrocs peuvent tenter de prendre le contrôle de votre appareil en vous demandant d\'autoriser l\'accès au service d\'accessibilité pour une appli."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Les escrocs peuvent tenter d\'endommager votre appareil avec ce paramètre."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"L\'appli n\'a pas obtenu l\'accès à <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"L\'appli a demandé l\'accès à une autorisation sensible qui pose un risque pour vos renseignements personnels et financiers.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Il est possible que l\'appli ne fonctionne pas correctement sans cette autorisation limitée. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Découvrir comment autoriser l\'accès&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"L\'appli n\'a pas obtenu l\'accès pour avoir le rôle<xliff:g id="ROLE_NAME">%1$s</xliff:g> par défaut"</string>
diff --git a/PermissionController/res/values-fr-v33/strings.xml b/PermissionController/res/values-fr-v33/strings.xml
index 24bd671a8..d10b59e57 100644
--- a/PermissionController/res/values-fr-v33/strings.xml
+++ b/PermissionController/res/values-fr-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Autres alertes"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Alertes ignorées"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Développer et voir 1 autre alerte}one{Développer et voir # autre alerte}many{Développer et voir # autres alertes}other{Développer et voir # autres alertes}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Réduire"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerte. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Action terminée"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Vérifiez les paramètres qui peuvent renforcer la protection de votre appareil"</string>
diff --git a/PermissionController/res/values-fr/strings.xml b/PermissionController/res/values-fr/strings.xml
index 14c29125e..10057977b 100644
--- a/PermissionController/res/values-fr/strings.xml
+++ b/PermissionController/res/values-fr/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Accès à <xliff:g id="PERM">%1$s</xliff:g> pour cette application sur <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Voir toutes les autorisations pour <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Voir toutes les applis ayant cette autorisation"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informations"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Paramètres"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Afficher l\'utilisation du micro par l\'Assistant"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Paramètres des applis inutilisées"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Supprimer les autorisations si l\'application n\'est pas utilisée"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Ouverture des liens"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Par défaut pour utilisation pro"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Paramètres par défaut d\'Espace privé"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimisées pour l\'appareil"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Autres"</string>
<string name="default_app_none" msgid="9084592086808194457">"Aucune"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Application système par défaut)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Aucune application"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Autoriser les paramètres restreints"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Paramètre restreint"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Pour votre sécurité, ce paramètre est actuellement indisponible."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Action impossible pendant un appel"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Ce paramètre est bloqué pour protéger votre appareil et vos données"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Des escrocs peuvent essayer d\'installer des applis malveillantes en vous demandant d\'installer des applis inconnues à partir d\'une nouvelle source."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Des escrocs peuvent essayer de prendre le contrôle de votre appareil en vous demandant d\'autoriser l\'accès aux fonctionnalités d\'accessibilité pour une appli."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Des escrocs peuvent tenter d\'endommager votre appareil avec ce paramètre."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"L\'appli s\'est vu refuser l\'accès à <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Cette appli a demandé l\'accès à une autorisation sensible susceptible d\'exposer vos informations financières et personnelles à un risque.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Si elle ne dispose pas d\'un accès à cette autorisation restreinte, l\'appli peut ne pas fonctionner correctement. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Découvrez comment autoriser l\'accès&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"L\'appli s\'est vu refuser l\'accès au statut de <xliff:g id="ROLE_NAME">%1$s</xliff:g> par défaut"</string>
diff --git a/PermissionController/res/values-gl-v33/strings.xml b/PermissionController/res/values-gl-v33/strings.xml
index 3c8898c4f..ea3123611 100644
--- a/PermissionController/res/values-gl-v33/strings.xml
+++ b/PermissionController/res/values-gl-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Máis alertas"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Alertas pechadas"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Despregar tarxeta e ver 1 alerta máis}other{Despregar tarxeta e ver # alertas máis}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Contraer"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerta. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Acción completada"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Comproba as opcións de configuración que poden aumentar a protección do dispositivo"</string>
diff --git a/PermissionController/res/values-gl/strings.xml b/PermissionController/res/values-gl/strings.xml
index 8ceeab1a6..aef51eaa4 100644
--- a/PermissionController/res/values-gl/strings.xml
+++ b/PermissionController/res/values-gl/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Esta aplicación ten o seguinte permiso de acceso en <xliff:g id="DEVICE_NAME">%2$s</xliff:g>: <xliff:g id="PERM">%1$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Ver todos os permisos de <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Ver todas as aplicacións que teñen este permiso"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Información"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Configuración"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Mostrar uso do micrófono do Asistente"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Configuración das aplicacións sen uso"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Quitar permisos se non se usa a aplicación"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Apertura de ligazóns"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Predeterminadas para o traballo"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Aplicacións predeterminadas do espazo privado"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizadas para o dispositivo"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Outras"</string>
<string name="default_app_none" msgid="9084592086808194457">"Ningunha"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Opción predeterminada do sistema)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Non hai ningunha aplicación"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Permitir opcións restrinxidas"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Opción de configuración restrinxida"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Pola túa seguranza, esta opción de configuración non está dispoñible nestes momentos."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Non se pode completar a acción durante as chamadas"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Esta opción de configuración está bloqueada para protexer o teu dispositivo e os teus datos"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Os estafadores poden pedirche que instales aplicacións descoñecidas dunha orixe nova para instalar aplicacións daniñas."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Os estafadores poden solicitarche permiso para acceder ás opcións de accesibilidade dunha aplicación co fin de tentar tomar o control do teu dispositivo."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Os estafadores poden tentar danar o teu dispositivo mediante esta opción de configuración."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Denegóuselle á aplicación o acceso ao permiso: <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"A aplicación solicitou acceso a un permiso confidencial que pode poñer en perigo a túa información persoal e financeira.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>É posible que a aplicación non funcione correctamente sen este permiso restrinxido. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Máis información sobre como permitir o acceso&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Denegóuselle á aplicación o acceso para actuar como <xliff:g id="ROLE_NAME">%1$s</xliff:g> de forma predeterminada"</string>
diff --git a/PermissionController/res/values-gu-v33/strings.xml b/PermissionController/res/values-gu-v33/strings.xml
index d77558fc3..767538cdd 100644
--- a/PermissionController/res/values-gu-v33/strings.xml
+++ b/PermissionController/res/values-gu-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"વધુ અલર્ટ"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"છોડી દીધેલા અલર્ટ"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{મોટું કરો અને વધુ એક અલર્ટ જુઓ}one{મોટું કરો અને વધુ # અલર્ટ જુઓ}other{મોટું કરો અને વધુ # અલર્ટ જુઓ}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"નાનું કરો"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"અલર્ટ. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"ક્રિયા પૂર્ણ થઈ"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"તમારા ડિવાઇસમાં સુરક્ષા ઉપાયો ઉમેરી શકે એવા સેટિંગ ચેક કરો"</string>
diff --git a/PermissionController/res/values-gu/strings.xml b/PermissionController/res/values-gu/strings.xml
index b65a5c582..8e43211c2 100644
--- a/PermissionController/res/values-gu/strings.xml
+++ b/PermissionController/res/values-gu/strings.xml
@@ -64,7 +64,7 @@
<string name="unused_apps" msgid="2058057455175955094">"ન વપરાયેલી ઍપ"</string>
<string name="edit_photos_description" msgid="5540108003480078892">"આ ઍપ માટે પસંદ કરેલા ફોટામાં ફેરફાર કરો"</string>
<string name="no_unused_apps" msgid="12809387670415295">"કોઈ બિનવપરાયેલી ઍપ નથી"</string>
- <string name="zero_unused_apps" msgid="9024448554157499748">"બિનવપરાયેલી 0 ઍપ"</string>
+ <string name="zero_unused_apps" msgid="9024448554157499748">"0 બિનવપરાયેલી ઍપ"</string>
<string name="review_permission_decisions" msgid="309559429150613632">"પરવાનગી સંબંધિત નિર્ણયો"</string>
<string name="review_permission_decisions_view_all" msgid="90391040431566130">"તાજેતરની પરવાનગી સંબંધિત બધા નિર્ણયો જુઓ"</string>
<string name="review_permission_decisions_empty" msgid="8120775336417279806">"તાજેતરની પરવાનગી સંબંધિત કોઈ નિર્ણય નથી"</string>
@@ -84,7 +84,7 @@
<string name="storage_supergroup_warning_allow" msgid="103093462784523190">"આ ઍપ Androidના જૂના વર્ઝન માટે ડિઝાઇન કરવામાં આવી હતી. જો તમે આ પરવાનગીને મંજૂરી આપશો, તો (ફોટા, વીડિયો, મ્યુઝિક, ઑડિયો અને અન્ય ફાઇલો સહિત) સંપૂર્ણ સ્ટોરેજના ઍક્સેસની મંજૂરી આપવામાં આવશે."</string>
<string name="storage_supergroup_warning_deny" msgid="6420765672683284347">"આ ઍપ Androidના જૂના વર્ઝન માટે ડિઝાઇન કરવામાં આવી હતી. જો તમે આ પરવાનગી નકારો છો, તો (ફોટા, વીડિયો, મ્યુઝિક, ઑડિયો અને અન્ય ફાઇલો સહિત) સંપૂર્ણ સ્ટોરેજના ઍક્સેસની મંજૂરી નકારવામાં આવશે."</string>
<string name="default_permission_description" msgid="4624464917726285203">"અજાણી ક્રિયા કરો"</string>
- <string name="app_permissions_group_summary" msgid="8788419008958284002">"<xliff:g id="COUNT_1">%2$d</xliff:g> માંથી <xliff:g id="COUNT_0">%1$d</xliff:g> ઍપની મંજૂરી છે"</string>
+ <string name="app_permissions_group_summary" msgid="8788419008958284002">"<xliff:g id="COUNT_1">%2$d</xliff:g>માંથી <xliff:g id="COUNT_0">%1$d</xliff:g> ઍપને મંજૂરી છે"</string>
<string name="app_permissions_group_summary2" msgid="4329922444840521150">"<xliff:g id="COUNT_0">%1$d</xliff:g>/<xliff:g id="COUNT_1">%2$d</xliff:g> ઍપને મંજૂરી છે"</string>
<string name="menu_show_system" msgid="4254021607027872504">"સિસ્ટમ બતાવો"</string>
<string name="menu_hide_system" msgid="3855390843744028465">"સિસ્ટમ છુપાવો"</string>
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> પર આ ઍપ માટે <xliff:g id="PERM">%1$s</xliff:g>નો ઍક્સેસ"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"<xliff:g id="APP">%1$s</xliff:g>ની બધી પરવાનગીઓ જુઓ"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"આ પરવાનગી સાથે બધી ઍપ જુઓ"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"માહિતી"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"સેટિંગ"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"આસિસ્ટંટ દ્વારા વપરાયેલો માઇક્રોફોનની પરવાનગીનો ડેટા બતાવો"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ન વપરાયેલી ઍપના સેટિંગ"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"ઍપ ઉપયોગમાં ન હોવા પર પરવાનગીઓ કાઢી નાખો"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"લિંક ખોલવી"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"કાર્ય માટે ડિફૉલ્ટ"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"ખાનગી સ્પેસ માટે ડિફૉલ્ટ"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ડિવાઇસ માટે ઑપ્ટિમાઇઝ કરેલી છે"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"અન્ય"</string>
<string name="default_app_none" msgid="9084592086808194457">"કોઈ નહીં"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(સિસ્ટમ ડિફૉલ્ટ)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"કોઈ ઍપ નથી"</string>
@@ -579,7 +583,7 @@
<string name="safety_status_preference_title_and_summary_content_description" msgid="3511373256505058464">"સુરક્ષા અને પ્રાઇવસીનું સ્ટેટસ. <xliff:g id="OVERALL_SAFETY_STATUS">%1$s</xliff:g>. <xliff:g id="SUMMARY_OF_DEVICE_STATUS">%2$s</xliff:g>"</string>
<string name="security_settings" msgid="3808106921175271317">"સુરક્ષા સેટિંગ"</string>
<string name="sensor_permissions_qs" msgid="1022267900031317472">"પરવાનગીઓ"</string>
- <string name="safety_privacy_qs_tile_title" msgid="727301867710374052">"સુરક્ષા અને પ્રાઇવસી"</string>
+ <string name="safety_privacy_qs_tile_title" msgid="727301867710374052">"સિક્યુરિટી અને પ્રાઇવસી"</string>
<string name="safety_privacy_qs_tile_subtitle" msgid="3621544532041936749">"સ્ટેટસ ચેક કરો"</string>
<string name="privacy_controls_qs" msgid="5780144882040591169">"તમારી પ્રાઇવસીને લગતા નિયંત્રણો"</string>
<string name="security_settings_button_label_qs" msgid="8280343822465962330">"વધુ સેટિંગ"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"પ્રતિબંધિત સેટિંગને મંજૂરી આપો"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"પ્રતિબંધિત સેટિંગ"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"તમારી સુરક્ષા માટે, આ સેટિંગ હાલમાં ઉપલબ્ધ નથી."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"કૉલ દરમિયાન ક્રિયા પૂર્ણ કરી શકાતી નથી"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n તમારા ડિવાઇસ અને ડેટાને સુરક્ષિત રાખવા માટે આ સેટિંગ બ્લૉક કરવામાં આવ્યું છે"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"સ્કૅમર તમને નવા સોર્સ પરથી અજાણી ઍપ ઇન્સ્ટૉલ કરવાનું કહીને હાનિકારક ઍપ ઇન્સ્ટૉલ કરવાનો પ્રયાસ કરી શકે છે."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"સ્કૅમર તમને કોઈ ઍપના ઍક્સેસની મંજૂરી આપવાનું કહીને તમારા ડિવાઇસને નિયંત્રિત કરવાનો પ્રયાસ કરી શકે છે."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"સ્કૅમર આ સેટિંગ વડે તમારા ડિવાઇસને નુકસાન પહોંચાડવાનો પ્રયાસ કરી શકે છે."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"ઍપને <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>નો ઍક્સેસ નકારવામાં આવ્યો"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"આ ઍપ દ્વારા કોઈ સંવેદનશીલ માહિતીની પરવાનગીના ઍક્સેસની વિનંતી કરવામાં આવી છે, જેને કારણે તમારી વ્યક્તિગત અને નાણાકીય માહિતી જોખમમાં આવી શકે છે.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>એ પણ શક્ય છે કે આ પ્રતિબંધિત પરવાનગી વિના ઍપ કદાચ યોગ્ય રીતે કામ ન પણ કરી શકે. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;ઍક્સેસ મંજૂર કરવાની રીત જાણો&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"ઍપને ડિફૉલ્ટ <xliff:g id="ROLE_NAME">%1$s</xliff:g> બનવાનો ઍક્સેસ નકારવામાં આવ્યો"</string>
diff --git a/PermissionController/res/values-hi-v33/strings.xml b/PermissionController/res/values-hi-v33/strings.xml
index 9cf4f044d..ef727a060 100644
--- a/PermissionController/res/values-hi-v33/strings.xml
+++ b/PermissionController/res/values-hi-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"ज़्यादा चेतावनियां"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"खारिज किए गए अलर्ट"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{कार्ड को बड़ा करके एक और सूचना देखें}one{कार्ड को बड़ा करके # और सूचना देखें}other{कार्ड को बड़ा करके # और सूचनाएं देखें}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"छोटा करें"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"चेतावनी. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"कार्रवाई पूरी हुई"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"आपके डिवाइस को सुरक्षित रखने वाली सेटिंग देखें"</string>
@@ -40,7 +41,7 @@
<string name="safety_center_qs_privacy_control" msgid="1160682635058529673">"स्विच करें. <xliff:g id="PRIVACY_CONTROL_TITLE">%1$s</xliff:g>. <xliff:g id="PRIVACY_CONTROL_STATUS">%2$s</xliff:g>"</string>
<string name="safety_center_qs_toggle_action" msgid="5920465736488119255">"टॉगल करें"</string>
<string name="safety_center_qs_open_action" msgid="2760200829912423728">"खोलें"</string>
- <string name="safety_center_review_settings_button" msgid="938981137942443930">"सेटिंग की समीक्षा करें"</string>
+ <string name="safety_center_review_settings_button" msgid="938981137942443930">"सेटिंग देखें"</string>
<string name="safety_center_gear_label" msgid="5175877094379694098">"सेटिंग"</string>
<string name="safety_center_info_label" msgid="8993181584061825412">"जानकारी"</string>
</resources>
diff --git a/PermissionController/res/values-hi/strings.xml b/PermissionController/res/values-hi/strings.xml
index 2557708bc..59034f2a1 100644
--- a/PermissionController/res/values-hi/strings.xml
+++ b/PermissionController/res/values-hi/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> पर इस ऐप्लिकेशन के लिए <xliff:g id="PERM">%1$s</xliff:g> का ऐक्सेस"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"<xliff:g id="APP">%1$s</xliff:g> को मिली सभी अनुमतियां देखें"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"इस अनुमति वाले सभी ऐप्लिकेशन देखें"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"जानकारी"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"सेटिंग"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"असिस्टेंट माइक्रोफ़ोन के इस्तेमाल से जुड़ा डेटा दिखाएं"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"इस्तेमाल न हो रहे ऐप के लिए सेटिंग"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"ऐप्लिकेशन का इस्तेमाल न होने पर अनुमतियां हटाएं"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"खुलने वाले लिंक"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"काम के लिए डिफ़ॉल्ट"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"प्राइवेट स्पेस के लिए डिफ़ॉल्ट ऐप्लिकेशन"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"डिवाइस के लिए ऑप्टिमाइज़ किया गया"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"अन्य"</string>
<string name="default_app_none" msgid="9084592086808194457">"कोई नहीं"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(सिस्टम डिफ़ॉल्ट)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"कोई ऐप्लिकेशन नहीं"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"पाबंदी वाली सेटिंग को अनुमति दें"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"इस सेटिंग पर पाबंदी लगाई गई है"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"आपकी सुरक्षा के लिए, यह सेटिंग फ़िलहाल उपलब्ध नहीं है."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"कॉल के दौरान कार्रवाई पूरी नहीं की जा सकती"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n आपके डिवाइस और डेटा को सुरक्षित रखने के लिए, यह सेटिंग ब्लॉक की गई है"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"धोखाधड़ी करने वाले लोग, आपको नए सोर्स से अनजान ऐप्लिकेशन इंस्टॉल करने के लिए कहकर, नुकसान पहुंचाने वाले ऐप्लिकेशन इंस्टॉल करने की कोशिश कर सकते हैं."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"धोखाधड़ी करने वाले लोग, आपके डिवाइस में मौजूद किसी ऐप्लिकेशन को ऐक्सेस करने की अनुमति लेकर, आपके डिवाइस को कंट्रोल करने की कोशिश कर सकते हैं."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"इस सेटिंग से, धोखाधड़ी करने वाले लोग आपके डिवाइस को नुकसान पहुंचाने की कोशिश कर सकते हैं."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"ऐप्लिकेशन को, <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> को ऐक्सेस करने की अनुमति नहीं मिली"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"इस ऐप्लिकेशन ने संवेदनशील जानकारी ऐक्सेस करने का अनुरोध किया है. इसे अनुमति देने पर, आपकी निजी और वित्तीय जानकारी की सुरक्षा को खतरा हो सकता है.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>हालांकि, हो सकता है कि पाबंदी वाली अनुमति न मिलने पर, ऐप्लिकेशन सही तरह से काम न करें. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;ऐक्सेस देने का तरीका जानें&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"ऐप्लिकेशन को डिफ़ॉल्ट <xliff:g id="ROLE_NAME">%1$s</xliff:g> के तौर पर सेट करने की अनुमति नहीं दी गई"</string>
diff --git a/PermissionController/res/values-hr-v33/strings.xml b/PermissionController/res/values-hr-v33/strings.xml
index 77c8a200f..09df088d8 100644
--- a/PermissionController/res/values-hr-v33/strings.xml
+++ b/PermissionController/res/values-hr-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Više upozorenja"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Odbačena upozorenja"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Proširite i pogledajte još jedno upozorenje}one{Proširite i pogledajte još # upozorenje}few{Proširite i pogledajte još # upozorenja}other{Proširite i pogledajte još # upozorenja}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Sažimanje"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Upozorenje. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Radnja je dovršena"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Provjerite postavke kojima možete dodatno zaštititi svoj uređaj"</string>
diff --git a/PermissionController/res/values-hr/strings.xml b/PermissionController/res/values-hr/strings.xml
index 55cfcfa78..b49109246 100644
--- a/PermissionController/res/values-hr/strings.xml
+++ b/PermissionController/res/values-hr/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Pristup značajke <xliff:g id="PERM">%1$s</xliff:g> za ovu aplikaciju na uređaju <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Pogledajte sva dopuštenja aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Pogledajte sve aplikacije s tim dopuštenjem"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informacije"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Postavke"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Prikaz korištenja mikrofona Asistenta"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Postavke nekorištenih aplikacija"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Ukloni dopuštenja ako se aplikacija ne upotrebljava"</string>
@@ -251,7 +253,7 @@
<string name="app_permission_never_accessed_summary" msgid="401346181461975090">"Nikad pristupljeno"</string>
<string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"Odbijeno/nikad pristupljeno"</string>
<string name="allowed_header" msgid="7769277978004790414">"Imaju dopuštenje"</string>
- <string name="allowed_always_header" msgid="6455903312589013545">"Imaju dopuštenje cijelo vrijeme"</string>
+ <string name="allowed_always_header" msgid="6455903312589013545">"Pristup je dopušten cijelo vrijeme"</string>
<string name="allowed_foreground_header" msgid="6845655788447833353">"Imaju dopuštenje samo tijekom upotrebe"</string>
<string name="allowed_storage_scoped" msgid="5383645873719086975">"Dopušten pristup samo medijima"</string>
<string name="allowed_storage_full" msgid="5356699280625693530">"Dopušteno upravljanje svim datotekama"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Otvaranje veza"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Zadano za posao"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Zadano za privatni prostor"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizirano za uređaj"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Ostalo"</string>
<string name="default_app_none" msgid="9084592086808194457">"Nijedna"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Zadana postavka sustava)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nema aplikacija"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> želi prenijeti informacije o otklanjanju pogrešaka."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Dijeli podatke o otklanjanju pogrešaka?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Sistem je otkrio problem."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> zahtijeva prijenos izvješća o programskim pogreškama s ovog uređaja od <xliff:g id="DATE">%2$s</xliff:g> u <xliff:g id="TIME">%3$s</xliff:g>. Izvješća o programskim pogreškama sadržavaju osobne podatke o uređaju ili one koje su zabilježile aplikacije, kao što su korisnička imena, podaci o lokaciji, identifikatori uređaja i podaci o mreži. Izvješća o programskim pogreškama dijelite samo s osobama i aplikacijama koje smatrate pouzdanim. Dopustiti aplikaciji <xliff:g id="APP_NAME_1">%4$s</xliff:g> prijenos izvješća o programskim pogreškama?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> zahtijeva prijenos izvješća o programskim pogreškama s ovog uređaja od <xliff:g id="DATE">%2$s</xliff:g> u <xliff:g id="TIME">%3$s</xliff:g>. Izvješća o programskim pogreškama sadržavaju osobne podatke o uređaju ili one koje su zabilježile aplikacije, kao što su korisnička imena, podaci o lokaciji, identifikatori uređaja i podaci o mreži. Izvješća o programskim pogreškama dijelite samo s osobama i aplikacijama koje smatrate pouzdanim. Želite li dopustiti aplikaciji <xliff:g id="APP_NAME_1">%4$s</xliff:g> prijenos izvješća o programskim pogreškama?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Došlo je do pogreške pri obradi izvješća o programskoj pogrešci za aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g>. Stoga je odbijeno dijeljenje detaljnih podataka o otklanjanju pogrešaka. Ispričavamo se zbog prekida."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Dopusti"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Odbij"</string>
@@ -674,12 +678,17 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Dopusti ograničene postavke"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Ograničena postavka"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Radi vaše sigurnosti ova postavka trenutačno nije dostupna."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Radnja se ne može dovršiti tijekom poziva"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Ta je postavka blokirana radi zaštite vašeg uređaja i podataka"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Prevaranti mogu pokušati instalirati štetne aplikacije tako da zatraže da instalirate nepoznate aplikacije iz novog izvora."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Prevaranti mogu pokušati preuzeti kontrolu nad vašim uređajem tako da zatraže da dopustite pristup pristupačnosti za aplikaciju."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Prevaranti mogu pokušati naštetiti vašem uređaju uz tu postavku."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Aplikaciji je odbijen pristup dopuštenju <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
- <string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Aplikacija je zatražila pristup dopuštenju za osjetljive podatke koje može ugroziti vaše osobne i financijske podatke.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Moguće je da aplikacija neće pravilno funkcionirati bez tog ograničenog dopuštenja. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Saznajte kako omogućiti pristup&lt;/a&gt;"</string>
- <string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikaciji je uskraćeno da prema zadanim postavkama bude <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
- <string name="enhanced_confirmation_dialog_desc_role" msgid="6369601947905234551">"Aplikacija je zatražila pristup dopuštenjima za osjetljive podatke koja mogu ugroziti vaše osobne i financijske podatke.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Moguće je da aplikacija neće pravilno funkcionirati bez tih ograničenih dopuštenja. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Saznajte kako omogućiti pristup&lt;/a&gt;"</string>
+ <string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Aplikacija je zatražila pristup dopuštenju za osjetljive podatke koje može ugroziti vaše osobne i financijske podatke.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Moguće je da aplikacija neće pravilno funkcionirati bez tog uskraćenog dopuštenja. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Saznajte kako omogućiti pristup&lt;/a&gt;"</string>
+ <string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikaciji je uskraćeno da bude zadana <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
+ <string name="enhanced_confirmation_dialog_desc_role" msgid="6369601947905234551">"Aplikacija je zatražila pristup dopuštenjima za osjetljive podatke čime može ugroziti vaše osobne i financijske podatke.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Moguće je da aplikacija neće pravilno funkcionirati bez tih ograničenih dopuštenja. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Saznajte kako omogućiti pristup&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_settings_default" msgid="1858092969721041576">"Aplikaciji je odbijen pristup"</string>
- <string name="enhanced_confirmation_dialog_desc_settings_default" msgid="6911632348359332981">"Pristup tom dopuštenju može ugroziti vaše osobne i financijske podatke.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Moguće je da aplikacija neće pravilno funkcionirati bez tog ograničenog dopuštenja. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Saznajte kako omogućiti pristup&lt;/a&gt;"</string>
+ <string name="enhanced_confirmation_dialog_desc_settings_default" msgid="6911632348359332981">"Pristup tom dopuštenju može ugroziti vaše osobne i financijske podatke.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Moguće je da aplikacija neće pravilno funkcionirati bez tog dopuštenja. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Saznajte kako omogućiti pristup&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_learn_more" msgid="5226619861379095709">"Saznajte više"</string>
<string name="enhanced_confirmation_dialog_ok" msgid="8560373821598619924">"U redu"</string>
<string name="permission_grant_dialog_streaming_blocked_title" msgid="8905241017017043649">"Upit za dopuštenje je spriječen"</string>
diff --git a/PermissionController/res/values-hu-v33/strings.xml b/PermissionController/res/values-hu-v33/strings.xml
index ee454fc12..ede269016 100644
--- a/PermissionController/res/values-hu-v33/strings.xml
+++ b/PermissionController/res/values-hu-v33/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="role_dialer_request_description" msgid="6188305064871543419">"Az alkalmazás engedélyt kap értesítések küldésére, és hozzáférhet majd a következőkhöz: Kamera, Névjegyek, Mikrofon, Telefon és SMS."</string>
- <string name="role_sms_request_description" msgid="1506966389698625395">"Az alkalmazás engedélyt kap értesítések küldésére, és hozzáférhet majd a következőkhöz: Kamera, Címtár, Files, Mikrofon, Telefon és SMS."</string>
+ <string name="role_sms_request_description" msgid="1506966389698625395">"Az alkalmazás engedélyt kap értesítések küldésére, és hozzáférhet majd a következőkhöz: Kamera, Névjegyek, Fájlok, Mikrofon, Telefon és SMS."</string>
<string name="permission_description_summary_storage" msgid="1917071243213043858">"Az ezzel az engedéllyel rendelkező alkalmazások hozzáférhetnek az ezen az eszközön lévő összes fájlhoz"</string>
<string name="work_policy_title" msgid="832967780713677409">"Munkahelyi házirendekkel kapcsolatos adatok"</string>
<string name="work_policy_summary" msgid="3886113358084963931">"A rendszergazda által kezelt beállítások"</string>
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Több értesítés"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Elvetett értesítések"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Ha kibontja, még egy értesítést láthat}other{Ha kibontja, még # értesítést láthat}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Összecsukás"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Figyelmeztetés. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Művelet befejezve"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Tekintse át a beállításokat, amelyek növelhetik eszköze védelmét"</string>
diff --git a/PermissionController/res/values-hu/strings.xml b/PermissionController/res/values-hu/strings.xml
index 32bfb9660..038f0161f 100644
--- a/PermissionController/res/values-hu/strings.xml
+++ b/PermissionController/res/values-hu/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g> – hozzáférés az alkalmazás számára a(z) <xliff:g id="DEVICE_NAME">%2$s</xliff:g> eszközön"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"A(z) <xliff:g id="APP">%1$s</xliff:g> összes engedélyének megtekintése"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Az ezzel az engedéllyel rendelkező összes alkalmazás megtekintése"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Információ"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Beállítások"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Mikrofon Segéd általi használatának megjelenítése"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Nem használt alkalmazások beállításai"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Engedélyek eltávolítása, ha nem használja az alkalmazást"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Linkek megnyitása"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Munkahelyi alapértelmezett"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Alapértelmezett a magánterületnél"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Az eszközre optimalizálva"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Egyéb"</string>
<string name="default_app_none" msgid="9084592086808194457">"Nincs"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Alapértelmezett)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nincs alkalmazás"</string>
@@ -491,7 +495,7 @@
<string name="permgrouprequest_coarselocation_imagetext" msgid="8650605041483025297">"Hozzávetőleges"</string>
<string name="permgrouprequest_calendar" msgid="1493150855673603806">"Engedélyezi a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; számára, hogy hozzáférjen a naptárhoz?"</string>
<string name="permgrouprequest_device_aware_calendar" msgid="7161929851377463612">"Engedélyezi a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; számára, hogy hozzáférjen a naptárához ezen az eszközön: &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>’s&lt;/b&gt;?"</string>
- <string name="permgrouprequest_sms" msgid="5672063688745420991">"Engedélyezi a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; számára, hogy SMS-eket küldhessen és tekinthessen meg?"</string>
+ <string name="permgrouprequest_sms" msgid="5672063688745420991">"Engedélyezi a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; számára SMS-ek küldését és megtekintését?"</string>
<string name="permgrouprequest_device_aware_sms" msgid="6639977653040502291">"Engedélyezi a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; számára az SMS-ek küldését és megtekintését ezen az eszközön: &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_storage" msgid="8717773092518621602">"Engedélyezi a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; számára, hogy hozzáférjen az eszközön tárolt fotókhoz, médiatartalmakhoz és fájlokhoz?"</string>
<string name="permgrouprequest_device_aware_storage" msgid="6933251810928606636">"Engedélyezi a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; számára, hogy fotókhoz, médiatartalmakhoz és fájlokhoz férjen hozzá ezen az eszközön: &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Korlátozott beállítások engedélyezése"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Korlátozott beállítás"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Biztonsága érdekében ez a beállítás jelenleg nem használható."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"A művelet nem végezhető el hívás közben"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Ez a beállítás le van tiltva az eszköz és az adatok védelme érdekében"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Csalók megpróbálhatnak kártékony alkalmazásokat telepíteni úgy, hogy arra kérik, telepítsen ismeretlen alkalmazásokat egy új forrásból."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Csalók megpróbálhatják átvenni az eszköz feletti irányítást úgy, hogy arra kérik Önt, hogy engedélyezze a kisegítő lehetőségekhez való hozzáférést egy alkalmazás számára."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Csalók megpróbálhatják károsítani az eszközét ezzel a beállítással."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Az app nem kapott hozzáférést a következőhöz: <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Az alkalmazás hozzáférési engedélyt kért a bizalmas adatokhoz, ami veszélybe sodorhatja az Ön személyes és pénzügyi adatait.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Lehetséges, hogy az alkalmazás nem működik megfelelően enélkül a korlátozott engedély nélkül. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;További információ a hozzáférés megadásának módjáról.&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Az app nem kapott hozzáférést a következőhöz: alapértelmezett <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-hy-v33/strings.xml b/PermissionController/res/values-hy-v33/strings.xml
index 6efb079ef..790addb16 100644
--- a/PermissionController/res/values-hy-v33/strings.xml
+++ b/PermissionController/res/values-hy-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Այլ ծանուցումներ"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Անտեսված ծանուցումներ"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Ծավալել և տեսնել ևս մեկ զգուշացում}one{Ծավալել և տեսնել ևս # զգուշացում}other{Ծավալել և տեսնել ևս # զգուշացում}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Ծալել"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Ծանուցում։ <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Գործողությունն ավարտված է"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Ծանոթացեք կարգավորումներին, որոնց օգնությամբ կարող եք բարձրացնել ձեր սարքի անվտանգության մակարդակը"</string>
diff --git a/PermissionController/res/values-hy/strings.xml b/PermissionController/res/values-hy/strings.xml
index 88c8d8d28..171da788c 100644
--- a/PermissionController/res/values-hy/strings.xml
+++ b/PermissionController/res/values-hy/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g>ն օգտագործելու թույլտվություն այս հավելվածի համար <xliff:g id="DEVICE_NAME">%2$s</xliff:g> սարքում"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Տեսնել «<xliff:g id="APP">%1$s</xliff:g>» հավելվածի բոլոր թույլտվությունները"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Դիտել այս թույլտվությունն ունեցող հավելվածների ցանկը"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Տեղեկություններ"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Կարգավորումներ"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Ցույց տալ օգնականի կողմից խոսափողի օգտագործման վիճակագրությունը"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Չօգտագործվող հավելվածների կարգավորումներ"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Հեռացնել թույլտվությունները, եթե հավելվածը չի օգտագործվում"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Հղումների բացում"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Կանխադրված՝ աշխատանքի համար"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Կանխադրված հավելվածներ մասնավոր տարածքի համար"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Սարքի համար օպտիմալացված"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Այլ"</string>
<string name="default_app_none" msgid="9084592086808194457">"Չկա"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Համակարգի կանխադրված հավելված)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Հավելվածներ չկան"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g>-ն ուզում է վերբեռնել վրիպազերծման տվյալները:"</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Ուղարկե՞լ վրիպազերծման տվյալները"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Համակարգը խնդիր է հայտնաբերել"</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g>-ը թույլտվություն է խնդրում՝ այս սարքից վրիպակների հաշվետվությունը (վերցված <xliff:g id="DATE">%2$s</xliff:g>-ին ժամը <xliff:g id="TIME">%3$s</xliff:g>-ին) վերբեռնելու համար: Վրիպակների հաշվետվությունները ներառում են տվյալներ ձեր սարքի մասին կամ անձնական տվյալներ, որոնք գրանցվել են հավելվածների կողմից, օրինակ՝ օգտատերերի անուններ, տեղադրության մասին տվյալներ, սարքերի ID-ներ և ցանցի մասին տեղեկություններ: Վրիպակների հաշվետվություններով կիսվեք միայն այն մարդկանց ու հավելվածների հետ, որոնց կարող եք վստահել այս տեղեկությունները: Թույլատրե՞լ <xliff:g id="APP_NAME_1">%4$s</xliff:g>-ին վերբեռնել վրիպակների հաշվետվությունը:"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g>-ը թույլտվություն է խնդրում՝ <xliff:g id="DATE">%2$s</xliff:g>, ժամը <xliff:g id="TIME">%3$s</xliff:g> ստեղծված վրիպակների հաշվետվությունն այս սարքից վերբեռնելու համար: Վրիպակների հաշվետվությունները ներառում են տվյալներ ձեր սարքի մասին կամ անձնական տվյալներ, որոնք գրանցվել են հավելվածների կողմից, օրինակ՝ օգտատերերի անուններ, տեղադրության մասին տվյալներ, սարքերի ID-ներ և ցանցի մասին տեղեկություններ: Վրիպակների հաշվետվություններով կիսվեք միայն այն մարդկանց ու հավելվածների հետ, որոնց կարող եք վստահել այս տեղեկությունները: Թույլատրե՞լ <xliff:g id="APP_NAME_1">%4$s</xliff:g>-ին վերբեռնել վրիպակների հաշվետվությունը:"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Չհաջողվեց մշակել <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածում սխալի մասին տեղեկությունները։ Վրիպազերծման տվյալների ուղարկումը չեղարկվել է։ Հայցում ենք ձեր ներողամտությունը պատճառած անհարմարության համար:"</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Թույլատրել"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Մերժել"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Թույլատրել սահմանափակ ռեժիմի կարգավորումները"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Սահմանափակումներով կարգավորում"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Անվտանգության նկատառումներից ելնելով՝ այս կարգավորումը ներկայումս անհասանելի է։"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Զանգի ընթացքում հնարավոր չէ կատարել գործողությունը"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Այս կարգավորումն արգելափակված է՝ ձեր սարքի և տվյալների անվտանգության նկատառումներից ելնելով"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Խաբեբաները կարող են փորձել վտանգավոր հավելվածներ տեղադրել ձեր սարքում՝ ձեզ խնդրելով, որ նոր աղբյուրից անհայտ հավելվածներ տեղադրեք։"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Խաբեբաները կարող են խնդրել ձեզ, որ տրամադրեք հավելվածների հատուկ գործառույթների հասանելիություն՝ փորձելով առգրավել ձեր սարքի կառավարումը։"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Այս կարգավորման միջոցով խաբեբաները կարող են վնասել ձեր սարքը։"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Հավելվածին մերժվել է <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>ի օգտագործման թույլտվությունը"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Հավելվածը հայցել է կոնֆիդենցիալ տեղեկությունների օգտագործման թույլտվություն, որը կարող է վտանգի ենթարկել ձեր անձնական և ֆինանսական տեղեկությունները։<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Հնարավոր է, որ առանց այս սահմանափակված թույլտվության՝ հավելվածը չաշխատի պատշաճ կերպով։ &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Ինչպես տրամադրել տվյալների օգտագործման թույլտվություն&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Հավելվածին մերժվել է «<xliff:g id="ROLE_NAME">%1$s</xliff:g>» կատեգորիայում կանխադրված լինելու թույլտվությունը"</string>
diff --git a/PermissionController/res/values-in-v33/strings.xml b/PermissionController/res/values-in-v33/strings.xml
index 90be234ea..32f0c44fe 100644
--- a/PermissionController/res/values-in-v33/strings.xml
+++ b/PermissionController/res/values-in-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Peringatan lain"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Peringatan yang ditutup"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Luaskan untuk melihat satu peringatan lain}other{Luaskan untuk melihat # peringatan lain}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Ciutkan"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Peringatan. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Tindakan selesai"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Periksa setelan yang dapat menambahkan perlindungan ke perangkat Anda"</string>
diff --git a/PermissionController/res/values-in/strings.xml b/PermissionController/res/values-in/strings.xml
index e409aabd9..2dc9d5f21 100644
--- a/PermissionController/res/values-in/strings.xml
+++ b/PermissionController/res/values-in/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Akses <xliff:g id="PERM">%1$s</xliff:g> untuk aplikasi ini di <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Lihat semua izin <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Lihat semua aplikasi yang memiliki izin ini"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informasi"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Setelan"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Tampilkan penggunaan mikrofon Asisten"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Setelan aplikasi yang tidak digunakan"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Hapus izin jika aplikasi tidak digunakan"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Membuka link"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Default untuk kerja"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Default untuk ruang privasi"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Dioptimalkan untuk perangkat"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Lainnya"</string>
<string name="default_app_none" msgid="9084592086808194457">"Tidak ada"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Default sistem)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Tidak ada aplikasi"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Izinkan setelan terbatas"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Setelan terbatas"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Demi keamanan Anda, setelan ini tidak tersedia untuk saat ini."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Tidak bisa selesaikan tindakan selama panggilan"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Setelan ini diblokir untuk melindungi perangkat dan data Anda"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Scammer mungkin mencoba menginstal aplikasi berbahaya dengan meminta Anda menginstal aplikasi tidak dikenal dari sumber baru."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Scammer mungkin mencoba mengambil kendali perangkat Anda dengan meminta Anda mengizinkan akses aksesibilitas untuk suatu aplikasi."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Scammer mungkin mencoba membahayakan perangkat Anda dengan setelan ini."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Aplikasi ditolak aksesnya ke <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Aplikasi meminta akses ke izin sensitif yang dapat membahayakan info pribadi dan keuangan Anda.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Aplikasi mungkin tidak dapat berfungsi dengan baik tanpa izin terbatas ini. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&amp;gtPelajari cara mengizinkan akses&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikasi ditolak aksesnya untuk menjadi <xliff:g id="ROLE_NAME">%1$s</xliff:g> default"</string>
diff --git a/PermissionController/res/values-is-v33/strings.xml b/PermissionController/res/values-is-v33/strings.xml
index 3f7695027..ae065f689 100644
--- a/PermissionController/res/values-is-v33/strings.xml
+++ b/PermissionController/res/values-is-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Fleiri tilkynningar"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Hunsaðar viðvaranir"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Stækka og sjá eina viðvörun í viðbót}one{Stækka og sjá # viðvörun í viðbót}other{Stækka og sjá # viðvaranir í viðbót}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Minnka"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Viðvörun. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Aðgerð lokið"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Skoðunarstillingar sem geta bætt öryggi tækisins þíns"</string>
diff --git a/PermissionController/res/values-is/strings.xml b/PermissionController/res/values-is/strings.xml
index ee49e777d..9d9f68535 100644
--- a/PermissionController/res/values-is/strings.xml
+++ b/PermissionController/res/values-is/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Aðgangur að <xliff:g id="PERM">%1$s</xliff:g> fyrir þetta forrit í <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Sjá allar heimildir fyrir „<xliff:g id="APP">%1$s</xliff:g>“"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Sjá öll forrit með þessa heimild"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Upplýsingar"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Stillingar"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Sýna hljóðnemanotkun hjálpara"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Ónotaðar forritastillingar"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Fjarlægja heimildir ef forrit er ekki notað"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Opnun tengla"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Sjálfgefið fyrir vinnu"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Sjálfgefið fyrir leynirými"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Fínstillt fyrir tæki"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Annað"</string>
<string name="default_app_none" msgid="9084592086808194457">"Ekkert"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Sjálfgildi kerfis)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Engin forrit"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Leyfa takmarkaðar stillingar"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Takmörkuð stilling"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Til að tryggja öryggi þitt er þessi stilling ekki tiltæk eins og er."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Ekki hægt að ljúka við aðgerð á meðan á símtali stendur"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Lokað er á þessa stillingu til að gæta að öryggi tækisins þíns og gagna"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Svindlarar gætu reynt að setja upp skaðleg forrit með því að biðja þig um að setja upp óþekkt forrit af nýjum uppruna."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Svindlarar gætu reynt að ná stjórn yfir tækinu þínu með því að biðja um aðgang að aðgengi forrits."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Svindlarar gætu reynt að valda tækinu þínu skaða með þessari stillingu."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Forritið fékk ekki aðgang að <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Forritið bað um aðgang að heimild fyrir viðkvæmu efni sem getur stofnað persónu- og fjármálaupplýsingum þínum í hættu.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Forritið virkar hugsanlega ekki sem skyldi án þessarar takmörkuðu heimildar. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Kynntu þér hvernig þú leyfir aðgang&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Forritið fékk ekki aðgang að sjálfgefnu <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-it-v33/strings.xml b/PermissionController/res/values-it-v33/strings.xml
index 9bea693c9..59eef5b89 100644
--- a/PermissionController/res/values-it-v33/strings.xml
+++ b/PermissionController/res/values-it-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Altri avvisi"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Avvisi chiusi"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Espandi e visualizza un altro avviso}many{Espandi e visualizza altri # di avvisi}other{Espandi e visualizza altri # avvisi}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Comprimi"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Avviso. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Azione completata"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Controlla le impostazioni che possono proteggere meglio il tuo dispositivo"</string>
diff --git a/PermissionController/res/values-it/strings.xml b/PermissionController/res/values-it/strings.xml
index c855b4ce6..d19d09251 100644
--- a/PermissionController/res/values-it/strings.xml
+++ b/PermissionController/res/values-it/strings.xml
@@ -92,7 +92,7 @@
<string name="menu_show_24_hours_data" msgid="8228054833323380780">"Mostra ultime 24 ore"</string>
<string name="manage_permission" msgid="2895385393037061964">"Gestisci autorizzazione"</string>
<string name="no_apps" msgid="2412612731628386816">"Nessuna app"</string>
- <string name="location_settings" msgid="3624412509133422562">"Geolocalizzazione"</string>
+ <string name="location_settings" msgid="3624412509133422562">"Impostazioni di localizzazione"</string>
<string name="location_warning" msgid="2381649060929040962">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> è un fornitore di servizi di geolocalizzazione per questo dispositivo. È possibile modificare l\'accesso alla posizione dalle impostazioni per la geolocalizzazione."</string>
<string name="system_warning" msgid="1173400963234358816">"Se rifiuti questa autorizzazione, le funzionalità di base del dispositivo potrebbero non funzionare più come previsto."</string>
<string name="deny_read_media_visual_warning" msgid="3982586279917232827">"Questa app è stata progettata per una versione precedente di Android. Se non consenti a questa app di accedere a foto e video, verrà negato anche l\'accesso a musica e altro audio."</string>
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g> accesso per questa app su <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Mostra tutte le autorizzazioni di <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Mostra tutte le app con questa autorizzazione"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informazioni"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Impostazioni"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Mostra utilizzo microfono dell\'assistente"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Impostazioni app inutilizzate"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Rimuovi autorizzazioni se non in uso"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Apertura link"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Predefinite per il lavoro"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Predefinite per lo spazio privato"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Ottimizzate per il dispositivo"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Altre"</string>
<string name="default_app_none" msgid="9084592086808194457">"Nessuna"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Predefinita)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nessuna app"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Consenti impostazioni con limitazioni"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Impostazione con limitazioni"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Per la tua sicurezza, questa impostazione non è al momento disponibile."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Impossibile completare l\'azione durante la chiamata"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Questa impostazione è bloccata per proteggere il tuo dispositivo e i tuoi dati"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"I truffatori potrebbero tentare di installare app dannose chiedendoti di installare app sconosciute da una nuova origine."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"I truffatori potrebbero tentare di prendere il controllo del tuo dispositivo chiedendoti di consentire l\'accesso alle funzioni di accessibilità di un\'app."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"I truffatori potrebbero tentare di danneggiare il tuo dispositivo con questa impostazione."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"All\'app è stato negato l\'accesso a <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"L\'app ha richiesto l\'accesso a un\'autorizzazione sensibile che può mettere a rischio le tue informazioni finanziarie e personali.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>È possibile che l\'app non funzioni correttamente senza questa autorizzazione limitata. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Scopri di più su come consentire l\'accesso&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"All\'app è stato negato l\'accesso al ruolo <xliff:g id="ROLE_NAME">%1$s</xliff:g> predefinita"</string>
diff --git a/PermissionController/res/values-iw-v33/strings.xml b/PermissionController/res/values-iw-v33/strings.xml
index b94c5f360..b8ded1655 100644
--- a/PermissionController/res/values-iw-v33/strings.xml
+++ b/PermissionController/res/values-iw-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"התראות נוספות"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"התראות שנסגרו"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{הרחבה וצפייה בהתראה נוספת אחת}one{הרחבה וצפייה ב-# התראות נוספות}two{הרחבה וצפייה ב-# התראות נוספות}other{הרחבה וצפייה ב-# התראות נוספות}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"כיווץ"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"התראה. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"הפעולה הושלמה"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"בדיקת ההגדרות שיכולות לשפר את ההגנה על המכשיר"</string>
diff --git a/PermissionController/res/values-iw/strings.xml b/PermissionController/res/values-iw/strings.xml
index 0fc33a62b..df90d3297 100644
--- a/PermissionController/res/values-iw/strings.xml
+++ b/PermissionController/res/values-iw/strings.xml
@@ -202,14 +202,16 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"גישה ל<xliff:g id="PERM">%1$s</xliff:g> לאפליקציה הזו ב<xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"הצגת כל ההרשאות של \'<xliff:g id="APP">%1$s</xliff:g>\'"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"אני רוצה לראות את כל האפליקציות עם ההרשאה הזו"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"מידע"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"הגדרות"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"‏השימוש במיקרופון של Assistant"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"הגדרות של אפליקציות שמזמן לא השתמשת בהן"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"הסרת ההרשאות כשלא בשימוש"</string>
<string name="unused_apps_label" msgid="2595428768404901064">"הסרת הרשאות ופינוי מקום אחסון"</string>
- <string name="unused_apps_label_v2" msgid="7058776770056517980">"השהיית הפעילות באפליקציה אם אין בה שימוש"</string>
- <string name="unused_apps_label_v3" msgid="693340578642156657">"ניהול האפליקציה כשהיא לא בשימוש"</string>
+ <string name="unused_apps_label_v2" msgid="7058776770056517980">"השהיית האפליקציה אם מזמן לא נפתחה"</string>
+ <string name="unused_apps_label_v3" msgid="693340578642156657">"הגבלת האפליקציה כשהיא לא בשימוש"</string>
<string name="unused_apps_summary" msgid="8839466950318403115">"הסרת ההרשאות, מחיקה של הקבצים הזמניים, הפסקה של קבלת ההתראות"</string>
- <string name="unused_apps_summary_v2" msgid="5011313200815115802">"הסרת ההרשאות, מחיקה של הקבצים הזמניים, הפסקה של קבלת ההתראות והעברת האפליקציה לארכיון"</string>
+ <string name="unused_apps_summary_v2" msgid="5011313200815115802">"ההרשאות יוסרו, הקבצים הזמניים יימחקו, ההתראות יופסקו והאפליקציה תעבור לארכיון"</string>
<string name="auto_revoke_summary" msgid="5867548789805911683">"כדי להגן על הנתונים שלך, אם האפליקציה הזו לא תהיה בשימוש במשך מספר חודשים, ההרשאות שניתנו לה יוסרו."</string>
<string name="auto_revoke_summary_with_permissions" msgid="389712086597285013">"כדי להגן על הנתונים שלך, אם האפליקציה לא תהיה בשימוש במשך מספר חודשים, ההרשאות הבאות יוסרו: <xliff:g id="PERMS">%1$s</xliff:g>"</string>
<string name="auto_revoked_apps_page_summary" msgid="6594753657893756536">"כדי להגן על הנתונים שלך, הוסרו הרשאות מאפליקציות שלא השתמשת בהן במשך מספר חודשים."</string>
@@ -266,11 +268,11 @@
<string name="permission_reminders" msgid="6528257957664832636">"תזכורות להרשאות"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"אפליקציה אחת שמזמן לא השתמשת בה"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> אפליקציות שמזמן לא השתמשת בהן"</string>
- <string name="auto_revoke_permission_reminder_notification_content" msgid="4492228990462107487">"הוסרו הרשאות כדי להגן על הפרטיות שלך. יש להקיש כדי לבדוק"</string>
+ <string name="auto_revoke_permission_reminder_notification_content" msgid="4492228990462107487">"הוסרו הרשאות כדי להגן על הפרטיות שלך. יש ללחוץ כדי לבדוק"</string>
<string name="auto_revoke_permission_notification_title" msgid="2629844160853454657">"הוסרו הרשאות לאפליקציות שמזמן לא השתמשת בהן"</string>
- <string name="auto_revoke_permission_notification_content" msgid="5125990886047799375">"יש אפליקציות שלא נעשה בהן שימוש כבר כמה חודשים. אפשר להקיש כדי לבדוק."</string>
+ <string name="auto_revoke_permission_notification_content" msgid="5125990886047799375">"יש אפליקציות שלא נעשה בהן שימוש כבר כמה חודשים. אפשר ללחוץ כדי לבדוק."</string>
<string name="unused_apps_notification_title" msgid="4314832015894238019">"{count,plural, =1{אפליקציה אחת שמזמן לא השתמשת בה}one{# אפליקציות שמזמן לא השתמשת בהן}two{# אפליקציות שמזמן לא השתמשת בהן}other{# אפליקציות שמזמן לא השתמשת בהן}}"</string>
- <string name="unused_apps_notification_content" msgid="9195026773244581246">"ההרשאות בוטלו, הקבצים הזמניים הוסרו וההתראות הופסקו. יש להקיש כדי לבדוק."</string>
+ <string name="unused_apps_notification_content" msgid="9195026773244581246">"ההרשאות בוטלו, הקבצים הזמניים הוסרו וההתראות הופסקו. יש ללחוץ כדי לבדוק."</string>
<string name="unused_apps_safety_center_card_title" msgid="5638409355530099149">"בדיקת אפליקציות שההרשאות שלהן הוסרו"</string>
<string name="unused_apps_safety_center_card_content" msgid="1088557243627427820">"ההרשאות והקבצים הזמניים של אפליקציות שלא השתמשת בהן זמן מה הוסרו, וההתראות מהן הופסקו."</string>
<string name="unused_apps_safety_center_action_title" msgid="8865914432518993194">"בדיקת האפליקציות"</string>
@@ -287,7 +289,7 @@
<string name="months_ago" msgid="1766026492610646354">"לפני <xliff:g id="COUNT">%1$d</xliff:g> חודשים"</string>
<string name="auto_revoke_preference_summary" msgid="5517958331781391481">"הוסרו הרשאות כדי להגן על הפרטיות שלך"</string>
<string name="background_location_access_reminder_notification_title" msgid="1140797924301941262">"האפליקציה <xliff:g id="APP_NAME">%s</xliff:g> קיבלה גישה ברקע למיקום שלך"</string>
- <string name="background_location_access_reminder_notification_content" msgid="7787084707336546245">"האפליקציה הזו יכולה תמיד לגשת למיקום שלך. יש להקיש כדי לשנות את ההגדרה הזו."</string>
+ <string name="background_location_access_reminder_notification_content" msgid="7787084707336546245">"האפליקציה הזו יכולה תמיד לגשת למיקום שלך. יש ללחוץ כדי לשנות את ההגדרה הזו."</string>
<string name="notification_listener_reminder_notification_title" msgid="3747210460187479091">"בדיקת אפליקציה עם גישה להתראות"</string>
<string name="notification_listener_reminder_notification_content" msgid="831476101108863427">"האפליקציה <xliff:g id="APP_NAME">%s</xliff:g> יכולה לסגור את ההתראות שלך, לפעול לפיהן ולגשת לתוכן בהתראות"</string>
<string name="notification_listener_warning_card_content" msgid="7840973324284115893">"האפליקציה הזו יכולה לסגור את ההתראות שלך, לפעול לפיהן ולגשת לתוכן בהתראות. לחלק מהאפליקציות נדרשת גישה כזו כדי לתפקד כמו שצריך."</string>
@@ -302,12 +304,12 @@
<string name="accessibility_remove_access_success_label" msgid="4380995302917014670">"הגישה הוסרה"</string>
<string name="safety_center_notification_app_label" msgid="2457720616141926534">"‏מערכת Android"</string>
<string name="auto_revoke_after_notification_title" msgid="5417761027669887431">"הוסרו הרשאות הניתנות לאפליקציה כדי להגן על הפרטיות"</string>
- <string name="auto_revoke_after_notification_content_one" msgid="6804038707453662753">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%s</xliff:g>. יש להקיש כדי לבדוק."</string>
- <string name="auto_revoke_after_notification_content_two" msgid="9108709764831425172">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%s</xliff:g> ובאפליקציה אחת נוספת. יש להקיש כדי לבדוק."</string>
- <string name="auto_revoke_after_notification_content_many" msgid="4774106206289751220">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> וב-<xliff:g id="NUMBER_OF_APPS">%2$s</xliff:g> אפליקציות נוספות. יש להקיש כדי לבדוק."</string>
+ <string name="auto_revoke_after_notification_content_one" msgid="6804038707453662753">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%s</xliff:g>. יש ללחוץ כדי לבדוק."</string>
+ <string name="auto_revoke_after_notification_content_two" msgid="9108709764831425172">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%s</xliff:g> ובאפליקציה אחת נוספת. יש ללחוץ כדי לבדוק."</string>
+ <string name="auto_revoke_after_notification_content_many" msgid="4774106206289751220">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> וב-<xliff:g id="NUMBER_OF_APPS">%2$s</xliff:g> אפליקציות נוספות. יש ללחוץ כדי לבדוק."</string>
<string name="auto_revoke_before_notification_title_one" msgid="6758024954464359876">"אפליקציה אחת לא נמצאת בשימוש"</string>
<string name="auto_revoke_before_notification_title_many" msgid="4415543943846385685">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> אפליקציות לא נמצאות בשימוש"</string>
- <string name="auto_revoke_before_notification_content_one" msgid="1156635373417068822">"הרשאות יוסרו כדי להגן על הפרטיות שלך. יש להקיש כדי לבדוק."</string>
+ <string name="auto_revoke_before_notification_content_one" msgid="1156635373417068822">"הרשאות יוסרו כדי להגן על הפרטיות שלך. יש ללחוץ כדי לבדוק."</string>
<string name="unused_apps_title" msgid="8589298917717872239">"אפליקציות שמזמן לא השתמשת בהן"</string>
<string name="unused_apps_subtitle_after" msgid="2034267519506357898">"הרשאות הוסרו מהאפליקציות"</string>
<string name="unused_apps_subtitle_before" msgid="5233302577076132427">"הרשאות יוסרו מהאפליקציות"</string>
@@ -358,7 +360,7 @@
<string name="role_assistant_description" msgid="6622458130459922952">"אפליקציות עזרה יכולות לסייע על סמך המידע שמוצג לך במסך. אפליקציות מסוימות תומכות גם בשירותי מרכז אפליקציות וגם בקלט קולי כדי לספק סיוע משולב."</string>
<string name="role_browser_label" msgid="2877796144554070207">"אפליקציית ברירת מחדל לדפדפן"</string>
<string name="role_browser_short_label" msgid="6745009127123292296">"אפליקציית דפדפן"</string>
- <string name="role_browser_description" msgid="3465253637499842671">"אפליקציות שמספקות לך גישה לאינטרנט ומציגות קישורים להקשה"</string>
+ <string name="role_browser_description" msgid="3465253637499842671">"אפליקציות שמספקות לך גישה לאינטרנט ומציגות קישורים ללחיצה"</string>
<string name="role_browser_request_title" msgid="2895200507835937192">"להגדיר את <xliff:g id="APP_NAME">%1$s</xliff:g> כאפליקציית הדפדפן המשמשת כברירת מחדל?"</string>
<string name="role_browser_request_description" msgid="5888803407905985941">"אין צורך בהרשאות"</string>
<string name="role_dialer_label" msgid="1100224146343237968">"אפליקציית ברירת המחדל לטלפון"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"פתיחת קישורים"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"ברירת מחדל לעבודה"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"ברירת מחדל עבור המרחב הפרטי"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"מותאמות למכשיר"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"אחרות"</string>
<string name="default_app_none" msgid="9084592086808194457">"ללא"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(ברירת מחדל של המערכת)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"אין אפליקציות"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> מבקשת להעלות נתונים של ניפוי באגים."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"לשתף נתונים של ניפוי באגים?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"המערכת איתרה בעיה."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"אפליקציית <xliff:g id="APP_NAME_0">%1$s</xliff:g> מבקשת להעלות דוח על באגים מהמכשיר הזה, שנוצר בתאריך <xliff:g id="DATE">%2$s</xliff:g> בשעה <xliff:g id="TIME">%3$s</xliff:g>. דוחות על באגים כוללים מידע אישי על המכשיר או מידע אישי שנשמר באמצעות אפליקציות. למשל, שמות משתמשים, נתוני מיקום, מזהי מכשיר ופרטי רשת. יש לשתף דוחות על באגים רק עם אפליקציות ואנשים מהימנים. לאפשר לאפליקציית <xliff:g id="APP_NAME_1">%4$s</xliff:g> להעלות דוח על באגים?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"‫<xliff:g id="APP_NAME_0">%1$s</xliff:g> מבקשת להעלות דוח על באגים מהמכשיר הזה, שנוצר בתאריך <xliff:g id="DATE">%2$s</xliff:g> בשעה <xliff:g id="TIME">%3$s</xliff:g>. דוחות על באגים כוללים מידע אישי על המכשיר או מידע אישי שנשמר באמצעות אפליקציות. למשל, שמות משתמשים, נתוני מיקום, מזהי מכשיר ופרטי רשת. יש לשתף דוחות על באגים רק עם אפליקציות ואנשים שאמינים בעיניך. לאפשר לאפליקציית <xliff:g id="APP_NAME_1">%4$s</xliff:g> להעלות דוח על באגים?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"אירעה שגיאה בעיבוד הדוח על הבאג עבור <xliff:g id="APP_NAME">%1$s</xliff:g>. לכן, נדחה שיתוף הנתונים המפורטים לגבי ניפוי השגיאות. מצטערים על ההפרעה."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"כן, זה בסדר"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"אני לא מרשה"</string>
@@ -471,7 +475,7 @@
<string name="permgrouprequest_device_aware_storage_isolated" msgid="6463062962458809752">"‏לתת לאפליקציה &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; הרשאת גישה לתמונות ולמדיה במכשיר &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_contacts" msgid="8391550064551053695">"‏לתת לאפליקציה &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; הרשאת גישה לאנשי הקשר שלך?"</string>
<string name="permgrouprequest_device_aware_contacts" msgid="731025863972535928">"‏לתת לאפליקציה &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; הרשאת גישה לאנשי הקשר במכשיר &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgrouprequest_location" msgid="6990232580121067883">"‏לתת לאפליקציה \'&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;\' הרשאת גישה למיקום המכשיר?"</string>
+ <string name="permgrouprequest_location" msgid="6990232580121067883">"‏לתת לאפליקציה \"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;\" הרשאת גישה למיקום המכשיר?"</string>
<string name="permgrouprequest_device_aware_location" msgid="6075412127429878638">"‏לתת לאפליקציה &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; הרשאת גישה למיקום של &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequestdetail_location" msgid="2635935335778429894">"לאפליקציה תהיה גישה אל נתוני המיקום רק בזמן השימוש בה"</string>
<string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"‏לתת לאפליקציה &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; הרשאת גישה למיקום המכשיר?"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"הרשאה להגדרות מוגבלות"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"הגדרה מוגבלת"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"מטעמי אבטחה, ההגדרה הזו לא זמינה כרגע."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"אי אפשר להשלים את הפעולה במהלך שיחה"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"‫<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n ההגדרה הזו חסומה כדי להגן על המכשיר ועל הנתונים שלך"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"רמאים שמנסים לגרום לך להתקין אפליקציות מזיקות יבקשו ממך להתקין אפליקציות לא ידועות ממקור חדש."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"רמאים שמנסים להשתלט על המכשיר שלך יבקשו ממך לתת לאפליקציה גישה לתכונות נגישות."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"רמאים יכולים להזיק למכשיר באמצעות ההגדרה הזו."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"בקשת הגישה של האפליקציה ל<xliff:g id="PERMISSION_NAME">%1$s</xliff:g> נדחתה"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"‏האפליקציה ביקשה הרשאת גישה למידע רגיש שעלולה לסכן את המידע האישי והפיננסי שלך.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>יכול להיות שהאפליקציה לא תעבוד כמו שצריך ללא ההרשאה המוגבלת הזו. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;מידע נוסף על מתן גישה להרשאות&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"בקשת הגישה של האפליקציה לשמש כברירת המחדל של <xliff:g id="ROLE_NAME">%1$s</xliff:g> נדחתה"</string>
diff --git a/PermissionController/res/values-ja-v33/strings.xml b/PermissionController/res/values-ja-v33/strings.xml
index 0669bd15d..b988e30c8 100644
--- a/PermissionController/res/values-ja-v33/strings.xml
+++ b/PermissionController/res/values-ja-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"その他のアラート"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"削除されたアラート"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{1 個以上のアラートを開いて表示する}other{# 個以上のアラートを開いて表示する}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"閉じる"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"アラート: <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"操作が完了しました"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"デバイスの保護を強化できる設定をご確認ください"</string>
diff --git a/PermissionController/res/values-ja/strings.xml b/PermissionController/res/values-ja/strings.xml
index e7322b07b..0be93d464 100644
--- a/PermissionController/res/values-ja/strings.xml
+++ b/PermissionController/res/values-ja/strings.xml
@@ -185,7 +185,7 @@
<string name="app_permission_usage_summary" msgid="390383661936709672">"アクセス: <xliff:g id="NUM">%1$s</xliff:g> 回。合計時間: <xliff:g id="DURATION">%2$s</xliff:g>。最終使用: <xliff:g id="TIME">%3$s</xliff:g>前。"</string>
<string name="app_permission_usage_summary_no_duration" msgid="3698475875179457400">"アクセス: <xliff:g id="NUM">%1$s</xliff:g> 回。最終使用: <xliff:g id="TIME">%2$s</xliff:g>前。"</string>
<string name="app_permission_button_allow" msgid="5808039516494774647">"許可する"</string>
- <string name="app_permission_button_allow_all_files" msgid="1792232272599018825">"すべてのファイルの管理を許可"</string>
+ <string name="app_permission_button_allow_all_files" msgid="1792232272599018825">"すべての管理を許可"</string>
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"アクセスのみ許可"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"常に許可"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"アプリの使用中のみ許可"</string>
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> でこのアプリに <xliff:g id="PERM">%1$s</xliff:g> へのアクセス権が付与されています"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"「<xliff:g id="APP">%1$s</xliff:g>」アプリの権限をすべて表示"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"この権限があるアプリをすべて表示"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"情報"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"設定"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"アシスタントのマイクの使用を表示"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"使用していないアプリの設定"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"アプリが使用されていない場合に権限を削除"</string>
@@ -211,7 +213,7 @@
<string name="unused_apps_summary" msgid="8839466950318403115">"権限と一時ファイルを削除し、通知を停止します"</string>
<string name="unused_apps_summary_v2" msgid="5011313200815115802">"権限と一時ファイルを削除し、通知を停止し、アプリをアーカイブします"</string>
<string name="auto_revoke_summary" msgid="5867548789805911683">"データ保護のため、このアプリが数か月使用されていない場合はアプリの権限が取り消されます。"</string>
- <string name="auto_revoke_summary_with_permissions" msgid="389712086597285013">"データ保護のため、アプリが数か月使用されていない場合は以下の権限が取り消されます。<xliff:g id="PERMS">%1$s</xliff:g>"</string>
+ <string name="auto_revoke_summary_with_permissions" msgid="389712086597285013">"データ保護のため、アプリが数か月使用されていない場合は次の権限が取り消されます。<xliff:g id="PERMS">%1$s</xliff:g>"</string>
<string name="auto_revoked_apps_page_summary" msgid="6594753657893756536">"データ保護のため、数か月使用していないアプリの権限を削除しました。"</string>
<string name="auto_revoke_open_app_message" msgid="8075556291711205039">"もう一度権限を許可するには、アプリを開いてください。"</string>
<string name="auto_revoke_disabled" msgid="8697684442991567188">"このアプリでは現在、自動削除は無効になっています。"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"リンクを開く"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"デフォルトの仕事用アプリ"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"プライベート スペースのデフォルト"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"デバイス向けに最適化"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"その他"</string>
<string name="default_app_none" msgid="9084592086808194457">"なし"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(システムのデフォルト)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"アプリなし"</string>
@@ -487,8 +491,8 @@
<string name="permgrouprequest_device_aware_fineupgrade" msgid="4453775952305587571">"&lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;の位置情報に対する &lt;b&gt;<xliff:g id="APP_NAME">&lt;b&gt;%1$s&lt;/b&gt;</xliff:g>&lt;/b&gt; のアクセス権を「おおよそ」から「正確」に変更しますか?"</string>
<string name="permgrouprequest_coarselocation" msgid="7244605063736425232">"このデバイスのおおよその位置情報へのアクセスを &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; に許可しますか?"</string>
<string name="permgrouprequest_device_aware_coarselocation" msgid="8367540370912066757">"&lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;のおおよその位置情報へのアクセスを &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; に許可しますか?"</string>
- <string name="permgrouprequest_finelocation_imagetext" msgid="1313062433398914334">"正確"</string>
- <string name="permgrouprequest_coarselocation_imagetext" msgid="8650605041483025297">"おおよそ"</string>
+ <string name="permgrouprequest_finelocation_imagetext" msgid="1313062433398914334">"正確な位置"</string>
+ <string name="permgrouprequest_coarselocation_imagetext" msgid="8650605041483025297">"おおよその位置"</string>
<string name="permgrouprequest_calendar" msgid="1493150855673603806">"カレンダーへのアクセスを「&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;」に許可しますか?"</string>
<string name="permgrouprequest_device_aware_calendar" msgid="7161929851377463612">"&lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;内のカレンダーへのアクセスを &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; に許可しますか?"</string>
<string name="permgrouprequest_sms" msgid="5672063688745420991">"SMS メッセージの送信と表示を「&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;」に許可しますか?"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"制限付き設定を許可"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"制限付き設定"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"セキュリティ保護のため、この設定は現在利用できません。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"通話中は操作を完了できません"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n デバイスとデータを保護するため、この設定はブロックされています"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"詐欺師が、新しいソースから不明なアプリをインストールするようユーザーに依頼して、有害なアプリをインストールしようとする可能性があります。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"詐欺師が、ユーザー補助機能へのアクセスをアプリに許可するようユーザーに依頼して、デバイスを操作しようとする可能性があります。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"この設定により、詐欺師がデバイスに危害を与える可能性があります。"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"アプリは<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>へのアクセスを拒否されました"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"機密情報に関わる権限へのアクセスをアプリがリクエストしました。この権限へのアクセスは、あなたの個人情報や財務情報を危険にさらす恐れがあります。<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>この制限付きの権限がないとアプリは正しく動作しない可能性があります。&lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;アクセスを許可する方法の詳細&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"アプリはデフォルトの<xliff:g id="ROLE_NAME">%1$s</xliff:g>としてのアクセスを拒否されました"</string>
diff --git a/PermissionController/res/values-ka-v33/strings.xml b/PermissionController/res/values-ka-v33/strings.xml
index f92c9ebe8..e44d2bce7 100644
--- a/PermissionController/res/values-ka-v33/strings.xml
+++ b/PermissionController/res/values-ka-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"სხვა გაფრთხილებები"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"დახურული გაფრთხილებები"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{გააფართოვეთ და ნახეთ კიდევ ერთი გაფრთხილება}other{გააფართოვეთ და იხილეთ კიდევ # გაფრთხილება}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"ჩაკეცვა"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"გაფრთხილება. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"მოქმედება დასრულებულია"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"შეამოწმეთ პარამეტრები თქვენი მოწყობილობის უსაფრთხოების გასაძლიერებლად"</string>
diff --git a/PermissionController/res/values-ka/strings.xml b/PermissionController/res/values-ka/strings.xml
index 3f836afdf..c99cf319d 100644
--- a/PermissionController/res/values-ka/strings.xml
+++ b/PermissionController/res/values-ka/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"(<xliff:g id="PERM">%1$s</xliff:g>) წვდომა ამ აპისთვის <xliff:g id="DEVICE_NAME">%2$s</xliff:g>-ზე"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"ყველა ნებართვის ნახვა: <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ამ ნებართვის მქონე ყველა აპის ნახვა"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"ინფორმაცია"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"პარამეტრები"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"ასისტენტის მიკროფონის გამოყენების ჩვენება"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"გამოუყენებელი აპის პარამეტრები"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"ნებართვების ამოშლა აპის გამოუყენებლობის შემთხვევაში."</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"ბმულების გახსნა"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"ნაგულისხმევი სამსახურისთვის"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"კერძო სივრცისთვის ნაგულისხმევი აპები"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ოპტიმიზებულია მოწყობილობისთვის"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"სხვა"</string>
<string name="default_app_none" msgid="9084592086808194457">"არცერთი"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(სისტემის ნაგულისხმევი)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"აპები არ არის"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"შეზღუდული პარამეტრების დაშვება"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"შეზღუდული პარამეტრი"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"თქვენივე უსაფრთხოებისთვის ეს პარამეტრი ამჟამად მიუწვდომელია."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"ზარის დროს მოქმედების დასრულება შეუძლებელია"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n ეს პარამეტრი დაბლოკილია თქვენი მოწყობილობისა და მონაცემების დასაცავად"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"თაღლითებმა შეიძლება ცადონ მავნე აპების ინსტალაცია ახალი წყაროდან უცნობი აპების დაყენების მოთხოვნით."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"თაღლითებმა შეიძლება ცადონ თქვენი მოწყობილობის კონტროლი აპისთვის წვდომის დაშვების მოთხოვნით."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"ამ პარამეტრის მეშვეობით თაღლითებმა შეიძლება ცადონ თქვენი მოწყობილობის დაზიანება."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"აპისთვის უარყოფილია ნებართვაზე წვდომა: <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"აპმა სენსიტიურ ინფორმაციაზე წვდომა მოითხოვა, რაც თქვენს პირად და ფინანსურ ინფორმაციას საფრთხის ქვეშ აყენებს.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>შესაძლოა აპმა ამ შეზღუდული ნებართვის გარეშე სათანადოდ ვერ იმუშაოს. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;შეიტყვეთ მეტი ნებართვის დაშვების შესახებ&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"აპისთვის ნაგულისხმევ როლზე (<xliff:g id="ROLE_NAME">%1$s</xliff:g>) წვდომა უარყოფილია"</string>
diff --git a/PermissionController/res/values-kk-v33/strings.xml b/PermissionController/res/values-kk-v33/strings.xml
index 9538c1503..18b4ba1d6 100644
--- a/PermissionController/res/values-kk-v33/strings.xml
+++ b/PermissionController/res/values-kk-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Басқа хабарландырулар"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Жабылған хабарландырулар"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Тағы бір хабарландыруды жаю және көру}other{Тағы # хабарландыруды жаю және көру}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Жию"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Ескерту. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Әрекет аяқталды."</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Құрығыңыздың қауіпсіздігін арттыратын параметрлерді тексеріңіз."</string>
diff --git a/PermissionController/res/values-kk/strings.xml b/PermissionController/res/values-kk/strings.xml
index ee9e14a6a..817361573 100644
--- a/PermissionController/res/values-kk/strings.xml
+++ b/PermissionController/res/values-kk/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Құрылғыдағы (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>) осы қолданбаның рұқсаты: <xliff:g id="PERM">%1$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Барлық <xliff:g id="APP">%1$s</xliff:g> рұқсаттарын көру"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Осы рұқсатқа ие барлық қолданбаларды көру"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Ақпарат"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Параметрлер"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Assistant микрофонының пайдаланылуын көрсету"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Пайдаланылмайтын қолданба параметрлері"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Қолданба пайдаланылмаса, рұқсаттарды өшіру"</string>
@@ -251,7 +253,7 @@
<string name="app_permission_never_accessed_summary" msgid="401346181461975090">"Ешқашан пайдаланбады"</string>
<string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"Тыйым салынған/Ешқашан пайдаланбаған"</string>
<string name="allowed_header" msgid="7769277978004790414">"Рұқсат берілгендер"</string>
- <string name="allowed_always_header" msgid="6455903312589013545">"Біржола рұқсат берілгендер"</string>
+ <string name="allowed_always_header" msgid="6455903312589013545">"Біржола рұқсат берілген"</string>
<string name="allowed_foreground_header" msgid="6845655788447833353">"Пайдаланғанда ғана рұқсат берілгендер"</string>
<string name="allowed_storage_scoped" msgid="5383645873719086975">"Тек мультимедианы пайдалана алатын қолданбалар"</string>
<string name="allowed_storage_full" msgid="5356699280625693530">"Барлық файлдарды басқара алатын қолданбалар"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Сілтемелер ашу"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Жұмыс үшін әдепкі қолданба"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Құпия кеңістікке арналған әдепкі қолданбалар"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Құрылғы үшін оңтайландырылған"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Басқа"</string>
<string name="default_app_none" msgid="9084592086808194457">"Жоқ"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(жүйенің әдепкі қолданбасы)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Қолданба жоқ"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Шектелген параметрлерге рұқсат беру"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Шектелген параметр"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Қауіпсіздік мақсатында бұл параметрді қазір пайдалану мүмкін емес."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Қоңырау кезінде әрекетті аяқтау мүмкін емес"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Бұл параметр құрылғы мен деректерді қорғау үшін блокталған."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Алаяқтар жаңа дереккөзден белгісіз қолданбалар орнатуыңызды сұрап, зиянды қолданбалар орнатқысы келуі мүмкін."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Алаяқтар қолданбаны пайдалануға рұқсат беруіңізді сұрап, құрылғыңызды басқарғысы келуі мүмкін."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Алаяқтар осы параметр арқылы құрылғыңызға зиян келтіруі мүмкін."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Қолданбаға <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> рұқсаты берілмеді"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Қолданба жеке және қаржылық ақпаратыңызға қауіп төндіруі мүмкін құпия ақпарат рұқсатын сұрады.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Қолданба бұл шектеулі рұқсатсыз дұрыс жұмыс істемеуі мүмкін. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Рұқсат беру туралы ақпарат&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Қолданбаға әдепкі <xliff:g id="ROLE_NAME">%1$s</xliff:g> болу рұқсаты берілмеді"</string>
diff --git a/PermissionController/res/values-km-v33/strings.xml b/PermissionController/res/values-km-v33/strings.xml
index 9926d9664..8536da5c1 100644
--- a/PermissionController/res/values-km-v33/strings.xml
+++ b/PermissionController/res/values-km-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"ការជូនដំណឹង​ច្រើនទៀត"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"ការជូន​ដំណឹងដែលបានច្រានចោល"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{ពង្រីកដើម្បីមើលការជូនដំណឹងមួយទៀត}other{ពង្រីកដើម្បីមើលការជូនដំណឹង # ទៀត}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"បង្រួម"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"ការជូន​ដំណឹង <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"សកម្មភាពបានបញ្ចប់"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"ពិនិត្យការកំណត់ដែលអាចបន្ថែមការការពារទៅដល់ឧបករណ៍របស់អ្នក"</string>
diff --git a/PermissionController/res/values-km/strings.xml b/PermissionController/res/values-km/strings.xml
index 60e0590ce..d7710e353 100644
--- a/PermissionController/res/values-km/strings.xml
+++ b/PermissionController/res/values-km/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"សិទ្ធិចូលប្រើ <xliff:g id="PERM">%1$s</xliff:g> សម្រាប់កម្មវិធីនេះនៅលើ <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"មើល​ការអនុញ្ញាតទាំងអស់ឱ្យទៅ <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"មើលកម្មវិធី​ទាំងអស់​ដែលមាន​ការអនុញ្ញាត​នេះ"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"ព័ត៌មាន"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"ការកំណត់"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"បង្ហាញការប្រើប្រាស់​មីក្រូហ្វូនរបស់ជំនួយការ"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ការកំណត់កម្មវិធីដែលមិនប្រើ"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"ដកការ​អនុញ្ញាតចេញ ប្រសិនបើ​មិនប្រើប្រាស់​កម្មវិធី"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"ការបើកតំណ"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"លំនាំដើម​សម្រាប់ការងារ"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"លំនាំដើមសម្រាប់លំហឯកជន"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"បានបង្កើនប្រសិទ្ធភាពសម្រាប់ឧបករណ៍"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"ផ្សេងទៀត"</string>
<string name="default_app_none" msgid="9084592086808194457">"គ្មាន"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(លំនាំដើមប្រព័ន្ធ)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"គ្មានកម្មវិធី​ទេ​"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"អនុញ្ញាតការកំណត់​ដែលបានដាក់កំហិត"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"ការកំណត់​ដែលបានដាក់កំហិត"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"ដើម្បីសុវត្ថិភាព​របស់អ្នក បច្ចុប្បន្នមិនអាចប្រើការកំណត់នេះបានទេ។"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"មិនអាចបញ្ចប់សកម្មភាពក្នុងអំឡុងពេលហៅទូរសព្ទបានទេ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n ការកំណត់នេះត្រូវបានទប់ស្កាត់ ដើម្បីការពារឧបករណ៍ និងទិន្នន័យរបស់អ្នក"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"ជនឆបោកអាចនឹងព្យាយាមដំឡើងកម្មវិធីដែលអាចបង្កគ្រោះថ្នាក់ ដោយស្នើសុំឱ្យអ្នកដំឡើងកម្មវិធីមិនស្គាល់ពីប្រភពថ្មី។"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"ជនឆបោកអាចនឹងព្យាយាមគ្រប់គ្រងឧបករណ៍របស់អ្នក ដោយស្នើសុំឱ្យអ្នកអនុញ្ញាតសិទ្ធិចូលប្រើប្រាស់មុខងារភាពងាយស្រួលសម្រាប់កម្មវិធី។"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"ជនឆបោកអាចនឹងព្យាយាមបង្កគ្រោះថ្នាក់ដល់ឧបករណ៍ដោយប្រើការកំណត់នេះ។"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"កម្មវិធីត្រូវបានបដិសេធមិនឱ្យចូលប្រើ<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"កម្មវិធីបានស្នើសុំសិទ្ធិចូលប្រើការអនុញ្ញាត​ដែលមានលក្ខណៈរសើប ដែលអាចធ្វើឱ្យព័ត៌មានហិរញ្ញវត្ថុ និងព័ត៌មានផ្ទាល់ខ្លួនរបស់អ្នកប្រឈមនឹងហានិភ័យ។<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>កម្មវិធីអាចនឹងមិនដំណើរការបានត្រឹមត្រូវទេ ប្រសិនបើគ្មានការអនុញ្ញាតដែលមានការរឹតបន្តឹងនេះ។ &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;ស្វែងយល់អំពីរបៀបផ្ដល់សិទ្ធិចូលប្រើ&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"កម្មវិធីត្រូវបានបដិសេធមិនឱ្យចូលប្រើជា<xliff:g id="ROLE_NAME">%1$s</xliff:g>លំនាំដើម"</string>
diff --git a/PermissionController/res/values-kn-v33/strings.xml b/PermissionController/res/values-kn-v33/strings.xml
index f4d39d7ac..f12f9cb86 100644
--- a/PermissionController/res/values-kn-v33/strings.xml
+++ b/PermissionController/res/values-kn-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"ಇನ್ನಷ್ಟು ಎಚ್ಚರಿಕೆಗಳು"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"ವಜಾಗೊಳಿಸಿದ ಎಚ್ಚರಿಕೆಗಳು"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{ವಿಸ್ತರಿಸಿ ಮತ್ತು ಇನ್ನೊಂದು ಎಚ್ಚರಿಕೆಯನ್ನು ನೋಡಿ}one{ವಿಸ್ತರಿಸಿ ಮತ್ತು # ಹೆಚ್ಚಿನ ಎಚ್ಚರಿಕೆಗಳನ್ನು ನೋಡಿ}other{ವಿಸ್ತರಿಸಿ ಮತ್ತು # ಹೆಚ್ಚಿನ ಎಚ್ಚರಿಕೆಗಳನ್ನು ನೋಡಿ}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"ಕುಗ್ಗಿಸಿ"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"ಅಲರ್ಟ್. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"ಆ್ಯಕ್ಷನ್ ಪೂರ್ಣಗೊಂಡಿದೆ"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ರಕ್ಷಣೆಯನ್ನು ಸೇರಿಸಬಹುದಾದ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಿ"</string>
diff --git a/PermissionController/res/values-kn/strings.xml b/PermissionController/res/values-kn/strings.xml
index 43ce0a8b9..b613cf5d4 100644
--- a/PermissionController/res/values-kn/strings.xml
+++ b/PermissionController/res/values-kn/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> ನಲ್ಲಿ ಈ ಆ್ಯಪ್‌ಗಾಗಿ <xliff:g id="PERM">%1$s</xliff:g> ಆ್ಯಕ್ಸೆಸ್"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"ಎಲ್ಲಾ <xliff:g id="APP">%1$s</xliff:g> ಅನುಮತಿಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ಈ ಅನುಮತಿಯನ್ನು ಹೊಂದಿರುವ ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"ಮಾಹಿತಿ"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"ಅಸಿಸ್ಟೆಂಟ್‌ನ ಮೈಕ್ರೋಫೋನ್ ಬಳಕೆಯನ್ನು ತೋರಿಸಿ"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ಬಳಕೆಯಾಗದಿರುವ ಆ್ಯಪ್ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"ಆ್ಯಪ್‌ ಬಳಸದಿದ್ದರೆ ಅನುಮತಿಗಳನ್ನು ತೆಗೆದುಹಾಕಿ"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"ಲಿಂಕ್‍‍ಗಳನ್ನು ತೆರೆಯುವುದು"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"ಕೆಲಸದ ಕುರಿತಾದ ಡೀಫಾಲ್ಟ್ ಆ್ಯಪ್"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"ಪ್ರೈವೆಟ್ ಸ್ಪೇಸ್‌ನ ಡೀಫಾಲ್ಟ್"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ಸಾಧನಕ್ಕಾಗಿ ಆಪ್ಟಿಮೈಸ್ ಮಾಡಲಾಗಿದೆ"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"ಇತರೆ"</string>
<string name="default_app_none" msgid="9084592086808194457">"ಯಾವುದೂ ಬೇಡ"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(ಸಿಸ್ಟಂ ಡಿಫಾಲ್ಟ್)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"ಯಾವುದೇ ಆ್ಯಪ್‌ಗಳು ಇಲ್ಲ"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"ನಿರ್ಬಂಧಿಸಲಾದ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಅನುಮತಿಸಿ"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"ನಿರ್ಬಂಧಿಸಲಾದ ಸೆಟ್ಟಿಂಗ್"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"ನಿಮ್ಮ ಸುರಕ್ಷತೆಗಾಗಿ, ಈ ಸೆಟ್ಟಿಂಗ್ ಪ್ರಸ್ತುತ ಲಭ್ಯವಿಲ್ಲ."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"ಕರೆಯ ಸಮಯದಲ್ಲಿ ಕ್ರಿಯೆಯನ್ನು ಪೂರ್ಣಗೊಳಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n ನಿಮ್ಮ ಸಾಧನ ಮತ್ತು ಡೇಟಾವನ್ನು ರಕ್ಷಿಸಲು ಈ ಸೆಟ್ಟಿಂಗ್ ಅನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"ಹೊಸ ಮೂಲದಿಂದ ಅಪರಿಚಿತ ಆ್ಯಪ್‌ಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಲು ಕೇಳುವ ಮೂಲಕ ಸ್ಕ್ಯಾಮರ್‌ಗಳು ಹಾನಿಕಾರಕ ಆ್ಯಪ್‌ಗಳನ್ನು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಲು ಪ್ರಯತ್ನಿಸಬಹುದು."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"ಆ್ಯಪ್‌ಗೆ ಆ್ಯಕ್ಸೆಸ್ಸಿಬಿಲಿಟಿ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಅನುಮತಿಸುವಂತೆ ಕೇಳುವ ಮೂಲಕ ಸ್ಕ್ಯಾಮರ್‌ಗಳು ನಿಮ್ಮ ಸಾಧನವನ್ನು ನಿಯಂತ್ರಿಸಲು ಪ್ರಯತ್ನಿಸಬಹುದು."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"ಈ ಸೆಟ್ಟಿಂಗ್ ಮೂಲಕ ಸ್ಕ್ಯಾಮರ್‌ಗಳು ನಿಮ್ಮ ಸಾಧನಕ್ಕೆ ಹಾನಿ ಮಾಡಲು ಪ್ರಯತ್ನಿಸಬಹುದು."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g> ಅನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಆ್ಯಪ್‌ಗೆ ನಿರಾಕರಿಸಲಾಗಿದೆ"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"ನಿಮ್ಮ ವೈಯಕ್ತಿಕ ಮತ್ತು ಹಣಕಾಸಿನ ಮಾಹಿತಿಯನ್ನು ಅಪಾಯಕ್ಕೆ ಸಿಲುಕಿಸಬಹುದಾದ ಸೂಕ್ಷ್ಮ ಅನುಮತಿಗೆ ಆ್ಯಪ್ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ವಿನಂತಿಸಿದೆ.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>.ಈ ನಿರ್ಬಂಧಿತ ಅನುಮತಿಯಿಲ್ಲದೆ ಆ್ಯಪ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸದಿರುವ ಸಾಧ್ಯತೆಯಿದೆ. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಹೇಗೆ ಅನುಮತಿಸುವುದು ಎಂಬುದನ್ನು ತಿಳಿಯಿರಿ&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"ಡೀಫಾಲ್ಟ್ <xliff:g id="ROLE_NAME">%1$s</xliff:g> ಆಗಿರಲು ಆ್ಯಪ್‌ಗೆ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ನಿರಾಕರಿಸಲಾಗಿದೆ"</string>
diff --git a/PermissionController/res/values-ko-v33/strings.xml b/PermissionController/res/values-ko-v33/strings.xml
index 87bd343e9..f5c440762 100644
--- a/PermissionController/res/values-ko-v33/strings.xml
+++ b/PermissionController/res/values-ko-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"알림 더보기"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"닫은 알림"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{펼쳐서 알림 1개 더보기}other{펼쳐서 알림 #개 더보기}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"접기"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"주의. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"작업 완료"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"기기 보안을 강화할 수 있는 설정 확인"</string>
diff --git a/PermissionController/res/values-ko/strings.xml b/PermissionController/res/values-ko/strings.xml
index d611cdf9d..c58b2e98a 100644
--- a/PermissionController/res/values-ko/strings.xml
+++ b/PermissionController/res/values-ko/strings.xml
@@ -84,7 +84,7 @@
<string name="storage_supergroup_warning_allow" msgid="103093462784523190">"이 앱은 Android 이전 버전에 맞게 설계되었습니다. 저장소 액세스 권한을 부여하면 앱에서 모든 저장소(사진, 동영상, 음악, 오디오 및 기타 파일 포함)에 액세스할 수 있습니다."</string>
<string name="storage_supergroup_warning_deny" msgid="6420765672683284347">"이 앱은 Android 이전 버전에 맞게 설계되었습니다. 저장소 액세스 권한을 부여하지 않으면 앱에서 모든 저장소(사진, 동영상, 음악, 오디오 및 기타 파일)에 액세스할 수 없습니다."</string>
<string name="default_permission_description" msgid="4624464917726285203">"알 수 없는 작업 실행"</string>
- <string name="app_permissions_group_summary" msgid="8788419008958284002">"<xliff:g id="COUNT_0">%1$d</xliff:g>/<xliff:g id="COUNT_1">%2$d</xliff:g>개 앱 허용됨"</string>
+ <string name="app_permissions_group_summary" msgid="8788419008958284002">"앱 <xliff:g id="COUNT_1">%2$d</xliff:g>개 중 <xliff:g id="COUNT_0">%1$d</xliff:g>개 허용됨"</string>
<string name="app_permissions_group_summary2" msgid="4329922444840521150">"앱 <xliff:g id="COUNT_0">%1$d</xliff:g>/<xliff:g id="COUNT_1">%2$d</xliff:g>개에 권한 부여됨"</string>
<string name="menu_show_system" msgid="4254021607027872504">"시스템 표시"</string>
<string name="menu_hide_system" msgid="3855390843744028465">"시스템 숨기기"</string>
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"이 앱의 <xliff:g id="DEVICE_NAME">%2$s</xliff:g> <xliff:g id="PERM">%1$s</xliff:g> 액세스 권한"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"<xliff:g id="APP">%1$s</xliff:g> 권한 모두 보기"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"이 권한이 있는 앱 모두 보기"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"정보"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"설정"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"어시스턴트 마이크 사용 표시"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"사용하지 않는 앱 설정"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"앱이 사용되지 않는 경우 권한 삭제"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"링크 열기"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"직장용 기본 앱"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"비공개 스페이스의 기본값"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"기기에 최적화된 앱"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"기타"</string>
<string name="default_app_none" msgid="9084592086808194457">"없음"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(시스템 기본값)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"앱 없음"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"제한된 설정 허용"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"제한된 설정"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"보안을 위해 이 설정은 현재 사용할 수 없습니다."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"통화 중에는 작업을 완료할 수 없습니다"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n 기기 및 데이터 보호를 위해 이 설정이 차단되었습니다."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"사기범이 새로운 소스에서 알 수 없는 앱을 설치하도록 요청하여 유해한 앱을 설치하려고 할 수 있습니다."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"사기범이 앱에 대한 접근성 액세스를 허용해 달라고 요청하여 기기를 제어하려고 할 수 있습니다."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"사기범이 이 설정을 통해 기기를 손상시키려고 할 수 있습니다."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>에 대한 앱의 액세스가 거부됨"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"앱에서 개인 정보 및 금융 정보가 위험에 노출될 수 있는 민감한 권한에 대한 액세스를 요청했습니다.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>이 제한된 권한 없이는 앱이 제대로 작동하지 않을 수 있습니다. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;액세스 허용 방법 알아보기&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"앱을 기본 <xliff:g id="ROLE_NAME">%1$s</xliff:g>으로 사용하기 위한 액세스가 거부됨"</string>
diff --git a/PermissionController/res/values-ky-v33/strings.xml b/PermissionController/res/values-ky-v33/strings.xml
index 3f2e12a3d..410ecd42e 100644
--- a/PermissionController/res/values-ky-v33/strings.xml
+++ b/PermissionController/res/values-ky-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Дагы эскертүүлөр"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Жабылган эскертүүлөр"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Дагы бир эскертүүнү көрүү үчүн жайып көрсөтүү}other{Дагы # эскертүүнү көрүү үчүн жайып көрсөтүү}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Жыйыштыруу"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Эскертүү. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Аракет аткарылды"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Түзмөгүңүздүн коопсуздугун бекемдей турган параметрлерди текшериңиз"</string>
diff --git a/PermissionController/res/values-ky/strings.xml b/PermissionController/res/values-ky/strings.xml
index f72d72a1e..4cc2b1c9f 100644
--- a/PermissionController/res/values-ky/strings.xml
+++ b/PermissionController/res/values-ky/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> түзмөгүндө бул колдонмо үчүн <xliff:g id="PERM">%1$s</xliff:g> уруксаты"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Бардык <xliff:g id="APP">%1$s</xliff:g> уруксаттарын көрүү"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Ушундай уруксат берилген бардык колдонмолорду көрүү"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Маалымат"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Параметрлер"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Кошумча микрофондун иштешин көрсөтүү"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Колдонулбаган колдонмолордун параметрлери"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Эгер колдонмо пайдаланылбаса, уруксаттар өчүрүлсүн"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Шилтемелерди ачуу"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Жумуш үчүн демейки жөндөөлөр"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Жеке мейкиндик үчүн демейки колдонмолор"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Түзмөккө оптималдаштырылды"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Башкалар"</string>
<string name="default_app_none" msgid="9084592086808194457">"Жок"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Демейки тутум)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Бир да колдонмо жок"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> мүчүлүштүктөрдү оңдоо маалыматын жүктөп бергиси келет."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Мүчүлүштүктөр тууралуу кабарлансынбы?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Система бир мүчүлүштүктү аныктады."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"\"<xliff:g id="APP_NAME_0">%1$s</xliff:g>\" колдонмосу ушул түзмөктөн <xliff:g id="DATE">%2$s</xliff:g>, саат <xliff:g id="TIME">%3$s</xliff:g> түзүлгөн мүчүлүштүктөр тууралуу отчетту жүктөп берүүнү сурап жатат. Отчетто колдонуучулардын аттары, жүргөн жерлери, түзмөктүн идентификаторлору жана тармактын дайын-даректери сыяктуу түзмөгүңүздөгү же колдонмолоруңуздагы жеке маалымат камтылышы мүмкүн. Андыктан мүчүлүштүктөр тууралуу отчетту ишенимдүү адамдар жана колдонмолор менен гана бөлүшүңүз. \"<xliff:g id="APP_NAME_1">%4$s</xliff:g>\" колдонмосуна мүчүлүштүк тууралуу отчетту жүктөөгө уруксат бересизби?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"\"<xliff:g id="APP_NAME_0">%1$s</xliff:g>\" колдонмосу ушул түзмөктөн <xliff:g id="DATE">%2$s</xliff:g>, саат <xliff:g id="TIME">%3$s</xliff:g> түзүлгөн мүчүлүштүктөр тууралуу отчетту жүктөөгө уруксат сурап жатат. Отчетто колдонуучулардын аттары, жүргөн жерлери, түзмөктүн идентификаторлору жана тармактын дайын-даректери сыяктуу түзмөгүңүздөгү же колдонмолоруңуздагы жеке маалымат камтылышы мүмкүн. Андыктан мүчүлүштүктөр тууралуу отчетту ишенимдүү адамдар жана колдонмолор менен гана бөлүшүңүз. \"<xliff:g id="APP_NAME_1">%4$s</xliff:g>\" колдонмосу мүчүлүштүк тууралуу отчетту жүктөй берсинби?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"<xliff:g id="APP_NAME">%1$s</xliff:g> колдонмосундагы мүчүлүштүк тууралуу кабарды иштетүүдө ката кетти. Мүчүлүштүктөрдү аныктоо тууралуу кеңири маалыматтарды бөлүшүү сурамы четке кагылды. Ыңгайсыздык үчүн кечирим сурайбыз."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Уруксат берүү"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Тыюу салынат"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Чектелген параметрлерге уруксат берүү"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Чектелген функция"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Коопсуздук максатында бул параметр азырынча иштебейт."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Чалуу учурунда аракетти бүтүрүү мүмкүн эмес"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Түзмөгүңүздү жана маалыматыңызды коргоо үчүн бул параметр бөгөттөлдү"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Шылуундар жаңы булактан белгисиз колдонмолорду орнотууну суранып, зыяндуу колдонмолорду орнотууга аракет кылышы мүмкүн."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Шылуундар колдонмого атайын мүмкүнчүлүктөрдү берүүнү суранып, түзмөгүңүздү көзөмөлдөөгө аракет кылышы мүмкүн."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Шылуундар бул функция аркылуу түзмөгүңүзгө зыян келтирүүгө аракет кылышы мүмкүн."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Колдонмого <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> параметрин колдонууга тыюу салынды"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Колдонмо жеке жана каржы маалыматыңызга коркунуч жаратышы мүмкүн болгон купуя маалыматты көрүүгө уруксат сурады.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Мындай уруксатсыз колдонмо ойдогудай иштебей коюшу мүмкүн. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Мүмкүнчүлүк берүү жөнүндө кеңири маалымат&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Колдонмого демейки <xliff:g id="ROLE_NAME">%1$s</xliff:g> ролуна тыюу салынды"</string>
diff --git a/PermissionController/res/values-lo-v33/strings.xml b/PermissionController/res/values-lo-v33/strings.xml
index 743a5bec9..16b913cec 100644
--- a/PermissionController/res/values-lo-v33/strings.xml
+++ b/PermissionController/res/values-lo-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"ແຈ້ງເຕືອນເພີ່ມເຕີມ"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"ປິດການແຈ້ງເຕືອນແລ້ວ"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{ຂະຫຍາຍ ແລະ ເບິ່ງແຈ້ງເຕືອນເພີ່ມອີກ 1 ລາຍການ}other{ຂະຫຍາຍ ແລະ ເບິ່ງແຈ້ງເຕືອນເພີ່ມອີກ # ລາຍການ}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"ຫຍໍ້ລົງ"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"ແຈ້ງເຕືອນ. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"ຄຳສັ່ງສຳເລັດ"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"ກວດເບິ່ງການຕັ້ງຄ່າທີ່ສາມາດເພີ່ມການປ້ອງກັນໃຫ້ກັບອຸປະກອນຂອງທ່ານ"</string>
diff --git a/PermissionController/res/values-lo/strings.xml b/PermissionController/res/values-lo/strings.xml
index f71900d86..7872fadd3 100644
--- a/PermissionController/res/values-lo/strings.xml
+++ b/PermissionController/res/values-lo/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g> ເຂົ້າເຖິງແອັບນີ້ຢູ່ <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"ເບິ່ງສິດອະນຸຍາດ <xliff:g id="APP">%1$s</xliff:g> ທັງໝົດ"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ເບິ່ງແອັບທັງໝົດທີ່ມີສິດອະນຸຍາດນີ້"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"ຂໍ້ມູນ"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"ການຕັ້ງຄ່າ"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"ສະແດງການໃຊ້ໄມໂຄຣໂຟນຂອງຜູ້ຊ່ວຍ"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ການຕັ້ງຄ່າແອັບທີ່ບໍ່ໄດ້ໃຊ້"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"ລຶບສິດອະນຸຍາດຫາກບໍ່ໄດ້ໃຊ້ແອັບ"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"ການເປີດລິ້ງ"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"ຄ່າເລີ່ມຕົ້ນສຳລັບບ່ອນເຮັດວຽກ"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"ຄ່າເລີ່ມຕົ້ນສຳລັບພື້ນທີ່ສ່ວນບຸກຄົນ"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ໄດ້ຮັບການເພີ່ມປະສິດທິພາບສຳລັບອຸປະກອນ"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"ອື່ນໆ"</string>
<string name="default_app_none" msgid="9084592086808194457">"ບໍ່ມີ"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(ຄ່າເລີ່ມຕົ້ນຂອງລະບົບ)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"ບໍ່ມີແອັບ"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"ອະນຸຍາດການຕັ້ງຄ່າທີ່ຈຳກັດ"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"ການຕັ້ງຄ່າທີ່ຈຳກັດ"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"ເພື່ອຄວາມປອດໄພຂອງທ່ານ, ຕອນນີ້ຈຶ່ງບໍ່ສາມາດໃຊ້ການຕັ້ງຄ່ານີ້ໄດ້."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"ບໍ່ສາມາດດຳເນີນການໃຫ້ສຳເລັດໄດ້ໃນລະຫວ່າງການໂທ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n ການຕັ້ງຄ່ານີ້ຖືກບລັອກເພື່ອປົກປ້ອງອຸປະກອນ ແລະ ຂໍ້ມູນຂອງທ່ານ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"ສະແກມເມີອາດຈະພະຍາຍາມຕິດຕັ້ງແອັບທີ່ເປັນອັນຕະລາຍໂດຍການຂໍໃຫ້ທ່ານຕິດຕັ້ງແອັບທີ່ບໍ່ຮູ້ຈັກຈາກແຫຼ່ງໃໝ່."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"ສະແກມເມີອາດຈະພະຍາຍາມຄວບຄຸມອຸປະກອນຂອງທ່ານໂດຍການຂໍໃຫ້ທ່ານອະນຸຍາດສິດເຂົ້າເຖິງສໍາລັບແອັບ."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"ສະແກມເມີອາດຈະພະຍາຍາມສ້າງອັນຕະລາຍໃຫ້ແກ່ອຸປະກອນຂອງທ່ານດ້ວຍການຕັ້ງຄ່ານີ້."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"ແອັບຖືກປະຕິເສດສິດເຂົ້າເຖິງຫາ <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"ແອັບໄດ້ຮ້ອງຂໍການສິດເຖິງການອະນຸຍາດທີ່ລະອຽດອ່ອນ ເຊິ່ງສາມາດເຮັດໃຫ້ຂໍ້ມູນສ່ວນຕົວ ແລະ ຂໍ້ມູນການເງິນຂອງທ່ານມີຄວາມສ່ຽງ.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>ມັນເປັນໄປໄດ້ວ່າແອັບດັ່ງກ່າວຈະບໍ່ເຮັດວຽກຢ່າງຖືກຕ້ອງໂດຍບໍ່ມີການອະນຸຍາດທີ່ຖືກຈຳກັດໄວ້ນີ້. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;ສຶກສາວິທີອະນຸຍາດສິດເຂົ້າເຖິງ&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"ແອັບຖືກປະຕິເສດສິດເຂົ້າເຖິງໃຫ້ເປັນ <xliff:g id="ROLE_NAME">%1$s</xliff:g> ຄ່າເລີ່ມຕົ້ນ"</string>
diff --git a/PermissionController/res/values-lt-v33/strings.xml b/PermissionController/res/values-lt-v33/strings.xml
index 41c3bdc9f..86876c5e9 100644
--- a/PermissionController/res/values-lt-v33/strings.xml
+++ b/PermissionController/res/values-lt-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Daugiau įspėjimų"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Atsisakyta įspėjimų"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Išskleiskite ir peržiūrėkite dar vieną įspėjimą}one{Išskleiskite ir peržiūrėkite dar # įspėjimą}few{Išskleiskite ir peržiūrėkite dar # įspėjimus}many{Išskleiskite ir peržiūrėkite dar # įspėjimo}other{Išskleiskite ir peržiūrėkite dar # įspėjimų}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Sutraukti"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Įspėjimas. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Veiksmas atliktas"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Patikrinkite nustatymus, kuriuos taikant galima užtikrinti papildomą įrenginio apsaugą"</string>
diff --git a/PermissionController/res/values-lt/strings.xml b/PermissionController/res/values-lt/strings.xml
index 59cd88b5b..fa7f4d1a3 100644
--- a/PermissionController/res/values-lt/strings.xml
+++ b/PermissionController/res/values-lt/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g> prieiga šiai programai „<xliff:g id="DEVICE_NAME">%2$s</xliff:g>“"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Žr. visus „<xliff:g id="APP">%1$s</xliff:g>“ leidimus"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Žr. visas programas, kurioms suteiktas šis leidimas"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informacija"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Nustatymai"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Rodyti Padėjėjo mikrofono naudojimą"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Nenaudojamos programos nustatymai"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Pašalinti leidimus, jei programa nenaudojama"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Nuorodų atidarymas"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Numatytosios darbo programos"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Privačios erdvės numatytosios programos"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizuota pagal įrenginį"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Kita"</string>
<string name="default_app_none" msgid="9084592086808194457">"Nėra"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Sistemos numatytoji programa)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nėra programų"</string>
@@ -584,7 +588,7 @@
<string name="privacy_controls_qs" msgid="5780144882040591169">"Jūsų privatumo valdikliai"</string>
<string name="security_settings_button_label_qs" msgid="8280343822465962330">"Daugiau nustatymų"</string>
<string name="camera_toggle_label_qs" msgid="3880261453066157285">"Prieiga prie fotoaparato"</string>
- <string name="microphone_toggle_label_qs" msgid="8132912469813396552">"Prieiga prie mikrofono"</string>
+ <string name="microphone_toggle_label_qs" msgid="8132912469813396552">"Mikrofonas"</string>
<string name="permissions_removed_qs" msgid="8957319130625294572">"Leidimas pašalintas"</string>
<string name="camera_usage_qs" msgid="4394233566086665994">"Žr. pastarąjį vaizdo kameros naudojimą"</string>
<string name="microphone_usage_qs" msgid="8527666682168170417">"Žr. pastarąjį mikrofono naudojimą"</string>
@@ -625,7 +629,7 @@
<string name="safety_center_background_location_access_revoked" msgid="6972274943343442213">"Prieiga pakeista"</string>
<string name="safety_center_view_recent_location_access" msgid="3524391299490678243">"Žr. pastarąjį vietovės nustatymo funkcijos naudojimą"</string>
<string name="privacy_controls_title" msgid="7605929972256835199">"Privatumo valdikliai"</string>
- <string name="camera_toggle_title" msgid="1251201397431837666">"Prieiga prie fotoaparato"</string>
+ <string name="camera_toggle_title" msgid="1251201397431837666">"Fotoaparatas"</string>
<string name="mic_toggle_title" msgid="2649991093496110162">"Prieiga prie mikrofono"</string>
<string name="perm_toggle_description" msgid="7801326363741451379">"Skirta programoms ir paslaugoms"</string>
<string name="mic_toggle_description" msgid="9163104307990677157">"Skirta programoms ir paslaugoms. Jei šis nustatymas išjungtas, mikrofono duomenys vis tiek gali būti bendrinami, skambinant pagalbos numeriu."</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Leisti apribotus nustatymus"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Apribotas nustatymas"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Šis nustatymas šiuo metu nepasiekiamas dėl jūsų saugumo."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Nepavyko atlikti veiksmo per skambutį"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Šis nustatymas užblokuotas siekiant apsaugoti įrenginį ir duomenis"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Aferistai gali bandyti įdiegti žalingų programų, prašydami įdiegti nežinomų programų iš naujo šaltinio."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Aferistai gali bandyti perimti jūsų įrenginio valdymą, prašydami suteikti galimybę pasiekti programą."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Naudodami šį nustatymą aferistai gali bandyti pakenkti jūsų įrenginiui."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Programai prieiga nesuteikta:<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Programa pateikė leidimo pasiekti neskelbtiną informaciją, dėl kurio gali kilti pavojus jūsų asmens ir finansinei informacijai, užklausą.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Gali būti, kad be šio apriboto leidimo programa neveiks tinkamai. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Sužinokite, kaip suteikti prieigą&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Programai nesuteikta prieiga kaip numatytojo šio vaidmens: <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-lv-v33/strings.xml b/PermissionController/res/values-lv-v33/strings.xml
index 825ac7aec..315b2da06 100644
--- a/PermissionController/res/values-lv-v33/strings.xml
+++ b/PermissionController/res/values-lv-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Citi brīdinājumi"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Noraidītie brīdinājumi"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Izvērsiet un skatiet vēl vienu brīdinājumu.}zero{Izvērsiet un skatiet vēl # brīdinājumus.}one{Izvērsiet un skatiet vēl # brīdinājumu.}other{Izvērsiet un skatiet vēl # brīdinājumus.}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Sakļaut"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Brīdinājums. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Darbība ir pabeigta"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Pārbaudiet iestatījumus, kas var uzlabot ierīces aizsardzību"</string>
diff --git a/PermissionController/res/values-lv/strings.xml b/PermissionController/res/values-lv/strings.xml
index 297279e73..8829ee0da 100644
--- a/PermissionController/res/values-lv/strings.xml
+++ b/PermissionController/res/values-lv/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Šai lietotnei ir piešķirta piekļuves atļauja “<xliff:g id="PERM">%1$s</xliff:g>” šajā ierīcē: <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Skatīt visas lietotnei <xliff:g id="APP">%1$s</xliff:g> piešķirtās atļaujas"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Skatīt visas lietotnes, kam ir šī atļauja"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informācija"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Iestatījumi"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Rādīt Asistenta mikrofona lietojumu"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Neizmantoto lietotņu iestatījumi"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Noņemt atļaujas, ja lietotne netiek izmantota"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Saišu atvēršana"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Noklusējuma iestatījums darbam"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Noklusējums privātajai telpai"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizētas ierīcei"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Citas"</string>
<string name="default_app_none" msgid="9084592086808194457">"Nav"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Sistēmas noklusējums)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nav lietotņu"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> vēlas augšupielādēt atkļūdošanas informāciju."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Vai kopīgot atkļūdošanas datus?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Sistēmā tika konstatēta problēma."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> pieprasa augšupielādēt kļūdas pārskatu no šīs ierīces, kas veikts: <xliff:g id="DATE">%2$s</xliff:g> plkst. <xliff:g id="TIME">%3$s</xliff:g>. Kļūdu pārskatos ir ietverta personas informācija par jūsu ierīci vai lietotnēs reģistrēta informācija, piemēram, lietotājvārdi, atrašanās vietas dati, ierīču identifikatori un tīkla informācija. Kopīgojiet kļūdu pārskatus tikai ar lietotājiem un lietotnēm, kuriem uzticat šo informāciju. Vai atļaut lietotnei <xliff:g id="APP_NAME_1">%4$s</xliff:g> augšupielādēt kļūdas pārskatu?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> pieprasa augšupielādēt kļūdas pārskatu no šīs ierīces, kas veikts: <xliff:g id="DATE">%2$s</xliff:g> plkst. <xliff:g id="TIME">%3$s</xliff:g>. Kļūdu pārskatos ir ietverta personas informācija par jūsu ierīci vai lietotnēs reģistrēta informācija, piemēram, lietotājvārdi, atrašanās vietas dati, ierīču identifikatori un tīkla informācija. Kopīgojiet kļūdu pārskatus tikai ar lietotājiem un lietotnēm, kuriem uzticat šo informāciju. Vai atļaut <xliff:g id="APP_NAME_1">%4$s</xliff:g> augšupielādēt kļūdas pārskatu?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Apstrādājot lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> kļūdas pārskatu, radās problēma. Tāpēc detalizēto atkļūdošanas datu kopīgošana tika liegta. Atvainojiet par traucējumu!"</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Atļaut"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Neatļaut"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Atļaut ierobežotos iestatījumus"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Ierobežots iestatījums"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Drošības apsvērumu dēļ šis iestatījums pašlaik nav pieejams."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Nevar pabeigt darbību zvana laikā"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Šis iestatījums ir bloķēts, lai aizsargātu jūsu ierīci un datus"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Krāpnieki var mēģināt instalēt kaitīgas lietotnes, lūdzot jums instalēt nezināmas lietotnes no jauna avota."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Krāpnieki var mēģināt pārņemt kontroli pār jūsu ierīci, lūdzot jums atļaut piekļuvi pieejamības funkcijām lietotnē."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Izmantojot šo iestatījumu, krāpnieki var mēģināt kaitēt jūsu ierīcei."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Lietotnes piekļuve atļaujai “<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>” tika liegta"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Lietotne ir pieprasījusi piekļuvi sensitīvai atļaujai, kas var apdraudēt jūsu personas un finanšu informāciju.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Iespējams, lietotne nedarbosies pareizi bez šīs ierobežotās atļaujas. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Uzziniet, kā piešķirt piekļuvi.&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Lietotnes piekļuve noklusējuma lomai “<xliff:g id="ROLE_NAME">%1$s</xliff:g>” tika liegta"</string>
diff --git a/PermissionController/res/values-mk-v33/strings.xml b/PermissionController/res/values-mk-v33/strings.xml
index 95896fb8e..2852ec7e1 100644
--- a/PermissionController/res/values-mk-v33/strings.xml
+++ b/PermissionController/res/values-mk-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Повеќе предупредувања"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Отфрлени известувања"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Проширете и видете уште едно предупредување}one{Проширете и видете уште # предупредување}other{Проширете и видете уште # предупредувањa}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Собери"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Предупредување. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Дејството е завршено"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Прегледајте ги поставките со кои може дополнително да го заштитите уредот"</string>
diff --git a/PermissionController/res/values-mk/strings.xml b/PermissionController/res/values-mk/strings.xml
index 2fdd35649..d8ba89c77 100644
--- a/PermissionController/res/values-mk/strings.xml
+++ b/PermissionController/res/values-mk/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Пристап до <xliff:g id="PERM">%1$s</xliff:g> за апликацијава на <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Прикажи ги сите дозволи за <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Прикажи ги сите апликации со оваа дозвола"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Информации"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Поставки"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Прикажи го користењето на микрофонот на „Помошникот“"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Поставки за некористени апликации"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Отстрани ги дозволите ако апликацијата не се користи"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"За отворање линкови"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Стандардно за работа"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Стандардно за „Приватен простор“"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Оптимизирано за уредот"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Други"</string>
<string name="default_app_none" msgid="9084592086808194457">"Нема"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Стандардно за системот)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Нема апликации"</string>
@@ -624,7 +628,7 @@
<string name="safety_center_background_location_access_reminder_summary" msgid="7431657777510537658">"Апликацијава може секогаш да пристапува до вашата локација, дури и кога е затворена.\n\nНа одредени апликации за безбедност и итни случаи им е потребен пристап до вашата локација во заднината за правилно да функционираат."</string>
<string name="safety_center_background_location_access_revoked" msgid="6972274943343442213">"Пристапот е променет"</string>
<string name="safety_center_view_recent_location_access" msgid="3524391299490678243">"Погледнете го неодамнешното користење на локацијата"</string>
- <string name="privacy_controls_title" msgid="7605929972256835199">"Контроли на приватноста"</string>
+ <string name="privacy_controls_title" msgid="7605929972256835199">"Контроли за приватност"</string>
<string name="camera_toggle_title" msgid="1251201397431837666">"Пристап до камерата"</string>
<string name="mic_toggle_title" msgid="2649991093496110162">"Пристап до микрофонот"</string>
<string name="perm_toggle_description" msgid="7801326363741451379">"За апликации и услуги"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Дозволете ограничени поставки"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Ограничена поставка"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"За ваша безбедност, поставкава е недостапна во моментов."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Дејството не може да се заврши во тек на повик"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Поставкава е блокирана за да ги заштити вашиот уред и податоци"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Измамниците може да се обидат да инсталираат штетни апликации барајќи од вас да инсталирате непознати апликации од нов извор."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Измамниците може да се обидат да ја преземат контролата врз вашиот уред барајќи од вас да дозволите пристап до пристапноста за некоја апликација."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Измамниците може да се обидат да му наштетат на вашиот уред со поставкава."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Барањето за пристап на апликацијата до <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> е одбиено"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Апликацијата побара пристап до дозвола за чувствителни податоци што може да ја загрози безбедноста на вашите лични и финансиски податоци.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Можно е апликацијата да не функционира правилно без ограниченава дозвола. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Дознајте како да дозволите пристап&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Барањето на апликацијата да биде стандарднa <xliff:g id="ROLE_NAME">%1$s</xliff:g> е одбиено"</string>
diff --git a/PermissionController/res/values-ml-v33/strings.xml b/PermissionController/res/values-ml-v33/strings.xml
index a4cc0190e..2f09237ef 100644
--- a/PermissionController/res/values-ml-v33/strings.xml
+++ b/PermissionController/res/values-ml-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"കൂടുതൽ മുന്നറിയിപ്പുകൾ"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"ഡിസ്‌മിസ് ചെയ്‌ത മുന്നറിയിപ്പുകൾ"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{വികസിപ്പിച്ച് ഒരു മുന്നറിയിപ്പ് കൂടി കാണുക}other{വികസിപ്പിച്ച് # മുന്നറിയിപ്പ് കൂടി കാണുക}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"ചുരുക്കുക"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"മുന്നറിയിപ്പ്. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"പ്രവർത്തനം പൂർത്തിയായി"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"നിങ്ങളുടെ ഉപകരണത്തിന് കൂടുതൽ പരിരക്ഷ നൽകാനാകുന്ന ക്രമീകരണങ്ങൾ പരിശോധിക്കുക"</string>
diff --git a/PermissionController/res/values-ml/strings.xml b/PermissionController/res/values-ml/strings.xml
index 09ea9e376..707bede6d 100644
--- a/PermissionController/res/values-ml/strings.xml
+++ b/PermissionController/res/values-ml/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> എന്നതിലെ ഈ ആപ്പിനുള്ള <xliff:g id="PERM">%1$s</xliff:g> ആക്സസ്"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"എല്ലാ <xliff:g id="APP">%1$s</xliff:g> അനുമതികളും കാണുക"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ഈ അനുമതിയുള്ള എല്ലാ ആപ്പുകളും കാണുക"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"വിവരങ്ങൾ"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"ക്രമീകരണം"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"സഹായ മൈക്രോഫോൺ ഉപയോഗം കാണിക്കുക"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ഉപയോഗിക്കാത്ത ആപ്പ് ക്രമീകരണം"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"ഉപയോഗിക്കാത്ത ആപ്പാണെങ്കിൽ അനുമതികൾ നീക്കം ചെയ്യുക"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"ലിങ്കുകൾ തുറക്കൽ"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"ജോലി ആവശ്യങ്ങൾക്ക് ഡിഫോൾട്ട്"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"സ്വകാര്യ സ്പേസിനായുള്ള ഡിഫോൾട്ട്"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ഉപകരണത്തിനായി ഒപ്റ്റിമൈസ് ചെയ്‌തു"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"മറ്റുള്ളവ"</string>
<string name="default_app_none" msgid="9084592086808194457">"ഒന്നുമില്ല"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(സിസ്‌റ്റം ഡിഫോൾട്ട്)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"ആപ്പുകൾ ഒന്നുമില്ല"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"നിയന്ത്രിത ക്രമീകരണം അനുവദിക്കുക"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"നിയന്ത്രിത ക്രമീകരണം"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"നിങ്ങളുടെ സുരക്ഷയ്ക്ക്, ഈ ക്രമീകരണം നിലവിൽ ലഭ്യമല്ല."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"കോളിനിടെ പ്രവർത്തനം പൂർത്തിയാക്കാനാവില്ല"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n നിങ്ങളുടെ ഉപകരണവും ഡാറ്റയും പരിരക്ഷിക്കാൻ ഈ ക്രമീകരണം ബ്ലോക്ക് ചെയ്തിരിക്കുന്നു"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"പുതിയൊരു ഉറവിടത്തിൽ നിന്നുള്ള അജ്ഞാതമായ ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യാൻ നിങ്ങളോട് അവശ്യപ്പെട്ടുകൊണ്ട് സ്‌കാമർമാർ ദോഷകരമായ ആപ്പുകൾ ഇൻസ്റ്റാൾ ചെയ്യാൻ ശ്രമിച്ചേക്കാം."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"ഒരു ആപ്പിനായുള്ള ഉപയോഗസഹായി ആക്സസ് അനുവദിക്കാൻ നിങ്ങളോട് ആവശ്യപ്പെട്ടുകൊണ്ട് സ്‌കാമർമാർ നിങ്ങളുടെ ഉപകരണത്തിന്റെ നിയന്ത്രണം ഏറ്റെടുക്കാൻ ശ്രമിച്ചേക്കാം."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"ഈ ക്രമീകരണം ഉപയോഗിച്ച് നിങ്ങളുടെ ഉപകരണത്തിന് ദോഷം വരുത്താൻ സ്‌കാമർമാർ ശ്രമിച്ചേക്കാം."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"ആപ്പിന് <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> എന്നതിലേക്കുള്ള ആക്‌സസ് നിരസിച്ചു"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"ആപ്പ് സെൻസിറ്റീവ് വിവരങ്ങൾക്കുള്ള അനുമതിയിലേക്ക് ആക്‌സസ് അഭ്യർത്ഥിച്ചു, ഇത് നിങ്ങളുടെ വ്യക്തിപരവും സാമ്പത്തികവുമായ വിവരങ്ങളെ അപകടത്തിലാക്കിയേക്കാം.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>ഈ നിയന്ത്രിത അനുമതിയില്ലാതെ ആപ്പ് ശരിയായി പ്രവർത്തിച്ചേക്കില്ല. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;ആക്സസ് എങ്ങനെ അനുവദിക്കുന്നുവെന്നറിയുക&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"ആപ്പിന് ഡിഫോൾട്ട് <xliff:g id="ROLE_NAME">%1$s</xliff:g> ആകാനുള്ള ആക്‌സസ് നിരസിച്ചു"</string>
diff --git a/PermissionController/res/values-mn-v33/strings.xml b/PermissionController/res/values-mn-v33/strings.xml
index 327723433..dd8a7b570 100644
--- a/PermissionController/res/values-mn-v33/strings.xml
+++ b/PermissionController/res/values-mn-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Бусад сэрэмжлүүлэг"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Сэрэмжлүүлгийг хаасан"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Дэлгээд дахин нэг анхааруулга харах}other{Дэлгээд дахин # анхааруулга харах}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Хураах"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Сэрэмжлүүлэг. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Үйлдэл дууссан"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Таны төхөөрөмжид хамгаалалт нэмэх боломжтой тохиргоог шалгана уу"</string>
diff --git a/PermissionController/res/values-mn/strings.xml b/PermissionController/res/values-mn/strings.xml
index c705e388b..d85eddc45 100644
--- a/PermissionController/res/values-mn/strings.xml
+++ b/PermissionController/res/values-mn/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> дээрх энэ аппын <xliff:g id="PERM">%1$s</xliff:g>-д хандах эрх"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"<xliff:g id="APP">%1$s</xliff:g>-н бүх зөвшөөрлийг харах"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Энэ зөвшөөрөлтэй бүх аппыг харах"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Мэдээлэл"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Тохиргоо"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Туслах микрофон ашиглалтыг харуулах"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Ашиглаагүй аппын тохиргоо"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Аппыг ашигладаггүй бол зөвшөөрлийг нь хасах"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Холбоосыг нээх сонголт"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Ажлын өгөгдмөл апп"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Хаалттай орон зайн өгөгдмөл"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Төхөөрөмжид зориулж оновчилсон"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Бусад"</string>
<string name="default_app_none" msgid="9084592086808194457">"Тохируулсан апп алга"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Системийн өгөгдмөл)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Апп алга"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Хязгаарлагдсан тохиргоог зөвшөөрөх"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Хязгаарлагдсан тохиргоо"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Таны аюулгүй байдлын үүднээс энэ тохиргоо одоогоор боломжгүй байна."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Дуудлагын үеэр үйлдэл гүйцэтгэх боломжгүй"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Энэ тохиргоог таны төхөөрөмж, өгөгдлийг хамгаалахын тулд блоклосон"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Залилагч танаас шинэ эх сурвалжаас тодорхойгүй апп суулгахыг хүсэж хор хөнөөлтэй апп суулгахаар оролдож болзошгүй."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Залилагч танаас аппын хандалтын эрхийг зөвшөөрөхийг хүсэж таны төхөөрөмжийн хяналтыг гартаа авахаар оролдож болзошгүй."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Залилагч энэ тохиргоогоор таны төхөөрөмжид хор хөнөөл учруулахаар оролдож болзошгүй."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Аппад <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>-д хандах эрх олгохоос татгалзсан"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Апп нь таны хувийн болон санхүүгийн мэдээллийг эрсдэлд оруулж болох эмзэг зөвшөөрөлд хандах эрх хүссэн.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Энэ хязгаарлагдмал зөвшөөрөлгүйгээр уг апп зохих ёсоор ажиллахгүй байх боломжтой. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Хандах эрхийг хэрхэн зөвшөөрөх талаар мэдэж авах&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Аппад өгөгдмөл <xliff:g id="ROLE_NAME">%1$s</xliff:g> болох эрх олгохоос татгалзсан"</string>
diff --git a/PermissionController/res/values-mr-v33/strings.xml b/PermissionController/res/values-mr-v33/strings.xml
index cea91a4b2..ceeb2332a 100644
--- a/PermissionController/res/values-mr-v33/strings.xml
+++ b/PermissionController/res/values-mr-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"आणखी सूचना"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"डिसमिस केलेल्या सूचना"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{विस्तार करा आणि आणखी एक सूचना पहा}other{विस्तार करा आणि आणखी # सूचना पहा}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"कोलॅप्स करा"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"इशारा. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"कृती पूर्ण झाली आहे"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"तुमच्या डिव्हाइसला संरक्षण जोडू शकतील अशी सेटिंग्ज तपासा"</string>
diff --git a/PermissionController/res/values-mr/strings.xml b/PermissionController/res/values-mr/strings.xml
index c8c259d93..538dd424f 100644
--- a/PermissionController/res/values-mr/strings.xml
+++ b/PermissionController/res/values-mr/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"या अ‍ॅपसाठी <xliff:g id="DEVICE_NAME">%2$s</xliff:g> वरील <xliff:g id="PERM">%1$s</xliff:g> चा अ‍ॅक्सेस"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"सर्व <xliff:g id="APP">%1$s</xliff:g> परवानग्या पहा"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ही परवानगी असलेली सर्व अ‍ॅप्स पहा"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"माहिती"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"सेटिंग्ज"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Assistant ने मायक्रोफोनचा केलेला वापर दाखवा"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"न वापरलेली ॲप सेटिंग्ज"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"अ‍ॅप वापरले नसल्यास, परवानग्या काढून टाका"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"उघडणार्‍या लिंक"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"कार्यासाठी डीफॉल्ट"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"खाजगी स्पेससाठी डीफॉल्ट"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ऑप्टिमाइझ केलेले डिव्हाइस"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"इतर"</string>
<string name="default_app_none" msgid="9084592086808194457">"काहीही नाही"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(सिस्टम डीफॉल्ट)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"अ‍ॅप्स नाहीत"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"प्रतिबंधित सेटिंग्जना अनुमती द्या"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"प्रतिबंधित सेटिंग"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"तुमच्या सुरक्षेसाठी, हे सेटिंग सध्या उपलब्ध नाही."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"कॉलदरम्यान कृती पूर्ण करू शकत नाही"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n तुमच्या डिव्हाइसचे आणि डेटाचे संरक्षण करण्यासाठी हे सेटिंग ब्लॉक केले आहे"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"घोटाळेबाज व्यक्ती तुम्हाला नवीन स्रोतामधून अज्ञात अ‍ॅप्स इंस्टॉल करण्यास सांगून हानिकारक अ‍ॅप्‍स इंस्टॉल करण्याचा प्रयत्न करू शकतात."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"घोटाळेबाज व्यक्ती तुम्हाला अ‍ॅपसाठी अ‍ॅक्सेसिबिलिटी अ‍ॅक्सेसची अनुमती देण्यास सांगून तुमच्या डिव्हाइसचे नियंत्रण मिळवण्याचा प्रयत्न करू शकतात."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"घोटाळेबाज व्यक्ती हे सेटिंग्ज वापरून तुमच्या डिव्हाइसला हानी पोहोचवण्याचा प्रयत्न करू शकतात."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"अ‍ॅपचा <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> साठीचा अ‍ॅक्सेस नाकारला गेला आहे"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"ॲपने संवेदनशील परवानगीसाठी अ‍ॅक्सेसची विनंती केली आहे, ज्यामुळे तुमची वैयक्तिक आणि आर्थिक माहिती धोक्यात येऊ शकते.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>या प्रतिबंधित परवानगीशिवाय ॲप कदाचित योग्यरीत्या काम करणार नाही. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;अ‍ॅक्सेसला अनुमती कशी द्यावी हे जाणून घ्या&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"अ‍ॅपचा डीफॉल्ट <xliff:g id="ROLE_NAME">%1$s</xliff:g> असण्यासाठीचा अ‍ॅक्सेस नाकारला गेला आहे"</string>
diff --git a/PermissionController/res/values-ms-v33/strings.xml b/PermissionController/res/values-ms-v33/strings.xml
index d5540a429..12507e50c 100644
--- a/PermissionController/res/values-ms-v33/strings.xml
+++ b/PermissionController/res/values-ms-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Lagi makluman"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Makluman yang diketepikan"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Kembangkan dan lihat satu makluman lagi}other{Kembangkan dan lihat # makluman lagi}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Kuncupkan"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Amaran. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Tindakan selesai"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Semak tetapan yang boleh menambah perlindungan pada peranti anda"</string>
diff --git a/PermissionController/res/values-ms/strings.xml b/PermissionController/res/values-ms/strings.xml
index 6b65594b4..93e07ebc2 100644
--- a/PermissionController/res/values-ms/strings.xml
+++ b/PermissionController/res/values-ms/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"akses <xliff:g id="PERM">%1$s</xliff:g> untuk apl ini pada <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Lihat semua kebenaran <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Lihat semua apl dengan kebenaran ini"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Maklumat"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Tetapan"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Tunjukkan penggunaan mikrofon pembantu"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Tetapan apl yang tidak digunakan"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Alih keluar kebenaran jika apl tidak digunakan"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Membuka pautan"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Lalai untuk kerja"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Lalai untuk ruang privasi"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Dioptimumkan untuk peranti"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Lain-lain"</string>
<string name="default_app_none" msgid="9084592086808194457">"Tiada"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Ciri lalai sistem)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Tiada apl"</string>
@@ -674,10 +678,15 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Benarkan tetapan terhad"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Tetapan terhad"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Untuk keselamatan anda, tetapan ini tidak tersedia pada masa ini."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Tidak dapat melengkapkan tindakan semasa panggilan"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Tetapan ini disekat untuk melindungi peranti dan data anda"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Penipu mungkin cuba untuk memasang apl berbahaya dengan meminta anda memasang apl yang tidak diketahui daripada sumber baharu."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Penipu mungkin cuba mengawal peranti anda dengan meminta anda membenarkan akses kebolehaksesan untuk apl."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Penipu mungkin cuba untuk memudaratkan peranti anda dengan tetapan ini."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Akses apl kepada <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> telah ditolak"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Apl meminta akses kepada kebenaran sensitif yang boleh mengakibatkan risiko terhadap maklumat peribadi dan kewangan anda.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Apl tersebut mungkin tidak dapat berfungsi dengan betul tanpa kebenaran terhad ini. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Ketahui cara membenarkan akses&lt;/a&gt;"</string>
- <string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Akses apl untuk menjadi <xliff:g id="ROLE_NAME">%1$s</xliff:g> secara lalai telah ditolak"</string>
- <string name="enhanced_confirmation_dialog_desc_role" msgid="6369601947905234551">"Apl meminta akses kepada kebenaran sensitif yang boleh mengakibatkan risiko terhadap maklumat peribadi dan kewangan anda.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Apl tersebut mungkin tidak dapat berfungsi dengan betul tanpa kebenaran terhad ini. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Ketahui cara membenarkan akses&lt;/a&gt;"</string>
+ <string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Akses apl untuk menjadi <xliff:g id="ROLE_NAME">%1$s</xliff:g> lalai telah ditolak"</string>
+ <string name="enhanced_confirmation_dialog_desc_role" msgid="6369601947905234551">"Apl meminta akses kepada kebenaran sensitif yang boleh mengakibatkan risiko terhadap maklumat peribadi dan kewangan anda.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Apl mungkin tidak dapat berfungsi dengan betul tanpa kebenaran terhad ini. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Ketahui cara membenarkan akses&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_settings_default" msgid="1858092969721041576">"Akses apl telah ditolak"</string>
<string name="enhanced_confirmation_dialog_desc_settings_default" msgid="6911632348359332981">"Akses kepada kebenaran ini boleh mengakibatkan risiko terhadap maklumat peribadi dan kewangan anda.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Apl tersebut mungkin tidak dapat berfungsi dengan betul tanpa kebenaran terhad ini. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Ketahui cara membenarkan akses&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_learn_more" msgid="5226619861379095709">"Ketahui lebih lanjut"</string>
diff --git a/PermissionController/res/values-my-v33/strings.xml b/PermissionController/res/values-my-v33/strings.xml
index 33237070b..6487f5647 100644
--- a/PermissionController/res/values-my-v33/strings.xml
+++ b/PermissionController/res/values-my-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"နောက်ထပ် သတိပေးချက်များ"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"ပယ်ထားသော သတိပေးချက်များ"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{ချဲ့ပြီး သတိပေးချက်နောက်တစ်ခု ကြည့်ရန်}other{ချဲ့ပြီး သတိပေးချက်နောက် # ခု ကြည့်ရန်}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"လျှော့ပြပါ"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"သတိပေးချက်။ <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"လုပ်ဆောင်ချက် ပြီးပြီ"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"သင့်စက်တွင် အကာအကွယ်ထည့်သွင်းနိုင်သည့် ဆက်တင်များကို ကြည့်နိုင်သည်"</string>
diff --git a/PermissionController/res/values-my/strings.xml b/PermissionController/res/values-my/strings.xml
index c488a296c..441769416 100644
--- a/PermissionController/res/values-my/strings.xml
+++ b/PermissionController/res/values-my/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> တွင် ဤအက်ပ်အတွက် <xliff:g id="PERM">%1$s</xliff:g> သုံးခွင့်"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"<xliff:g id="APP">%1$s</xliff:g> ခွင့်ပြုချက်အားလုံး ကြည့်ရန်"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ဤခွင့်ပြုချက်ရှိသော အက်ပ်အားလုံးကို ကြည့်ရန်"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"အချက်အလက်"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"ဆက်တင်များ"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Assistant မိုက်ကရိုဖုန်း အသုံးပြုမှုကို ပြပါ"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"အသုံးမပြုသော အက်ပ်ဆက်တင်များ"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"အက်ပ်ကိုအသုံးမပြုလျှင် ခွင့်ပြုချက်များ ဖယ်ရှားရန်"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"လင့်ခ်များကို ဖွင့်ခြင်း"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"အလုပ်အတွက် မူရင်း"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"သီးသန့်နေရာအတွက် မူလအက်ပ်များ"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"စက်အတွက် အကောင်းဆုံးပြင်ထားသည်"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"အခြား"</string>
<string name="default_app_none" msgid="9084592086808194457">"မရှိ"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(စနစ်မူရင်း)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"အက်ပ် မရှိပါ"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"ကန့်သတ်ဆက်တင်များ ခွင့်ပြုရန်"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"ကန့်သတ်ဆက်တင်"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"သင့်လုံခြုံရေးအတွက် ဤဆက်တင်ကို လောလောဆယ် မရနိုင်ပါ။"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"ခေါ်ဆိုနေစဉ် လုပ်ဆောင်ချက်ကို အပြီးသတ်၍မရပါ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n သင့်စက်နှင့် ဒေတာကို ကာကွယ်ရန်အတွက် ဤဆက်တင်ကို ပိတ်ထားသည်"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"လူလိမ်များသည် သတင်းအရင်းအမြစ်မှ အမျိုးအမည်မသိအက်ပ်များကို သင့်အား ထည့်သွင်းခိုင်းခြင်းဖြင့် အန္တရာယ်အက်ပ်များ ထည့်သွင်းရန် ကြိုးပမ်းနိုင်သည်။"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"လူလိမ်များသည် အက်ပ်အတွက် အများသုံးနိုင်ခွင့်ပြုရန် တောင်းဆိုခြင်းဖြင့် သင့်စက်ကို ထိန်းချုပ်ရန် ကြိုးပမ်းနိုင်သည်။"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"လူလိမ်များသည် ဤဆက်တင်ဖြင့် သင့်စက်ကို ထိခိုက်စေရန် ကြိုးပမ်းနိုင်သည်။"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"အက်ပ်ကို <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> သုံးခွင့် ငြင်းပယ်ထားသည်"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"အက်ပ်သည် သင်၏ ပုဂ္ဂိုလ်ရေးနှင့် ငွေကြေးဆိုင်ရာ အချက်အလက်များကို အန္တရာယ်ဖြစ်စေနိုင်သော သတိထားရမည့် ခွင့်ပြုချက်သုံးရန် တောင်းဆိုထားသည်။<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>အက်ပ်သည် ဤကန့်သတ်ထားသော ခွင့်ပြုချက်မရှိပါက ကောင်းစွာမလုပ်ဆောင်ခြင်း ဖြစ်နိုင်သည်။ &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;သုံးခွင့်ပြုနည်းကို လေ့လာရန်&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"အက်ပ်အား မူရင်း <xliff:g id="ROLE_NAME">%1$s</xliff:g> အဖြစ် လုပ်ဆောင်ခွင့် ငြင်းပယ်ထားသည်"</string>
diff --git a/PermissionController/res/values-nb-v33/strings.xml b/PermissionController/res/values-nb-v33/strings.xml
index 570e99d27..9f4910da2 100644
--- a/PermissionController/res/values-nb-v33/strings.xml
+++ b/PermissionController/res/values-nb-v33/strings.xml
@@ -30,9 +30,10 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Flere varsler"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Avviste varsler"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Dobbelttrykk for å se et varsel til}other{Dobbelttrykk for å se # varsler til}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Skjul"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Varsel. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Handlingen er fullført"</string>
- <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Sjekk innstillinger som kan øke beskyttelsen til enheten"</string>
+ <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Sjekk innstillinger som kan gi enheten bedre beskyttelse"</string>
<string name="safety_center_qs_page_landing" msgid="1717368301679228128">"Hurtiginnstillinger for sikkerhet og personvern"</string>
<string name="safety_center_qs_close_button" msgid="1352313308176244599">"Lukk"</string>
<string name="safety_center_qs_expand_action" msgid="2193190557696484169">"Vis alternativer"</string>
diff --git a/PermissionController/res/values-nb/strings.xml b/PermissionController/res/values-nb/strings.xml
index bb6cb00d5..3835d7573 100644
--- a/PermissionController/res/values-nb/strings.xml
+++ b/PermissionController/res/values-nb/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g>-tilgang for denne appen på <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Se alle tillatelsene <xliff:g id="APP">%1$s</xliff:g> har"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Se alle apper med denne tillatelsen"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informasjon"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Innstillinger"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Vis bruk av assistentmikrofonen"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Innstillinger for ubrukte apper"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Fjern tillatelser hvis appen ikke brukes"</string>
@@ -438,8 +440,10 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Åpning av linker"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Jobbstandard"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Standard for privat område"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimalisert for enheten"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Andre"</string>
<string name="default_app_none" msgid="9084592086808194457">"Ingen"</string>
- <string name="default_app_system_default" msgid="6218386768175513760">"(System-&amp;shy;standard)"</string>
+ <string name="default_app_system_default" msgid="6218386768175513760">"(System-&amp;#173;standard)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Ingen apper"</string>
<string name="default_payment_app_other_nfc_services" msgid="5957633798695758917">"Andre NFC-tjenester"</string>
<string name="car_default_app_selected" msgid="5416420830430644174">"Valgt"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Tillat begrensede innstillinger"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Begrenset innstilling"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Av sikkerhetshensyn er denne innstillingen utilgjengelig for øyeblikket."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Kan ikke fullføre handlingen under samtaler"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Denne innstillingen er blokkert for å beskytte enheten din og dataene dine"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Svindlere kan prøve å installere skadelige apper ved å be deg om å installere ukjente apper fra en ny kilde."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Svindlere kan prøve å ta kontroll over enheten din ved å be deg om å gi en app tilgang til tilgjengelighetsfunksjoner."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Svindlere kan prøve å skade enheten din med denne innstillingen."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Appens tilgang til <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> ble avvist"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Appen ba om tilgang til en sensitiv tillatelse som kan utsette den personlige og økonomiske informasjonen din for fare.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Det kan hende at appen ikke fungerer skikkelig uten denne begrensede tillatelsen. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Finn ut hvordan du gir tilgang&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Appen fikk ikke tillatelse til å være standard <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-ne-v33/strings.xml b/PermissionController/res/values-ne-v33/strings.xml
index b16622bd0..b41931d2a 100644
--- a/PermissionController/res/values-ne-v33/strings.xml
+++ b/PermissionController/res/values-ne-v33/strings.xml
@@ -30,9 +30,10 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"थप अलर्टहरू"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"खारेज गरिएका अलर्टहरू"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{एक्स्पान्ड गरेर थप एउटा अलर्ट हेर्नुहोस्}other{एक्स्पान्ड गरेर थप # वटा अलर्ट हेर्नुहोस्}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"कोल्याप्स गर्नुहोस्"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"सतर्कता अपनाउनुहोस्। <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"यो कार्य पूरा भएको छ"</string>
- <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"तपाईंको डिभाइस थप सुरक्षित गर्न सक्ने सेटिङ जाँच्नुहोस्"</string>
+ <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"तपाईंको डिभाइसलाई थप सुरक्षित राख्न सक्ने सेटिङ जाँच्नुहोस्"</string>
<string name="safety_center_qs_page_landing" msgid="1717368301679228128">"सुरक्षा तथा गोपनीयतासम्बन्धी द्रुत सेटिङ"</string>
<string name="safety_center_qs_close_button" msgid="1352313308176244599">"बन्द गर्नुहोस्"</string>
<string name="safety_center_qs_expand_action" msgid="2193190557696484169">"एक्स्पान्ड गर्नुहोस् र विकल्पहरू देखाउनुहोस्"</string>
diff --git a/PermissionController/res/values-ne/strings.xml b/PermissionController/res/values-ne/strings.xml
index 70d474bfc..a2c9dee59 100644
--- a/PermissionController/res/values-ne/strings.xml
+++ b/PermissionController/res/values-ne/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"यो एपलाई <xliff:g id="DEVICE_NAME">%2$s</xliff:g> को <xliff:g id="PERM">%1$s</xliff:g> प्रयोग गर्ने अनुमति"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"<xliff:g id="APP">%1$s</xliff:g> सँग भएका सबै अनुमति हेर्नुहोस्"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"यो अनुमति पाएका सबै एपहरू हेर्नुहोस्"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"जानकारी"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"सेटिङ"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"सहायकको माइक्रोफोन प्रयोगसम्बन्धी डेटा देखाउनुहोस्"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"प्रयोग नगरिएको एपसम्बन्धी सेटिङ"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"यो एप प्रयोग नहुँदा यसलाई दिइएका अनुमतिहरू रद्द गर्नुहोस्"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"लिंकहरू खोल्दा"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"कार्यका लागि डिफल्ट"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"निजी स्पेसका लागि डिफल्ट एपहरू"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"यो डिभाइसका लागि अप्टिमाइज गरिएका"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"अन्य"</string>
<string name="default_app_none" msgid="9084592086808194457">"कुनै पनि होइन"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(सिस्टम डिफल्ट)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"कुनै पनि एप छैन"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> डिबग प्रक्रियासम्बन्धी जानकारी अपलोड गर्न चाहन्छ।"</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"डिबग प्रक्रियासम्बन्धी डेटा सेयर गर्ने हो?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"प्रणालीले कुनै समस्या फेला पारेको छ"</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ले <xliff:g id="DATE">%2$s</xliff:g> <xliff:g id="TIME">%3$s</xliff:g> मा यो यन्त्रबाट लिएको बगको रिपोर्ट अपलोड गर्ने अनुरोध गर्दै छ। बग रिपोर्टमा प्रयोगकर्ताका नाम, स्ठानसम्बन्धी डेटा, डिभाइसका पहिचानकर्ता र नेटवर्कसम्बन्धी जानकारी जस्ता तपाईंको डिभाइसको व्यक्तिगत जानकारी वा एपले लग गरेको जानकारी समावेश छ। तपाईंलाई यो जानकारी दिँदा फरक पर्दैन जस्तो लाग्ने विश्वसनीय मान्छे वा एपसँग मात्र बग रिपोर्टहरू सेयर गर्नुहोस्। <xliff:g id="APP_NAME_1">%4$s</xliff:g> लाई बग रिपोर्ट अपलोड गर्न दिने हो?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> ले <xliff:g id="DATE">%2$s</xliff:g> <xliff:g id="TIME">%3$s</xliff:g> मा यो डिभाइसबाट लिएको बगको रिपोर्ट अपलोड गर्ने अनुरोध गर्दै छ। बग रिपोर्टमा प्रयोगकर्ताका नाम, लोकेसन डेटा, डिभाइसका पहिचानकर्ता र नेटवर्कसम्बन्धी जानकारी जस्ता तपाईंको डिभाइसको व्यक्तिगत जानकारी वा एपले लग गरेको जानकारी समावेश छ। तपाईंलाई यो जानकारी दिँदा फरक पर्दैन जस्तो लाग्ने विश्वसनीय मान्छे वा एपसँग मात्र बग रिपोर्टहरू सेयर गर्नुहोस्। <xliff:g id="APP_NAME_1">%4$s</xliff:g> लाई बग रिपोर्ट अपलोड गर्न दिने हो?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"<xliff:g id="APP_NAME">%1$s</xliff:g> को बगसम्बन्धी रिपोर्ट प्रक्रियामा लैजाने क्रममा त्रुटि भयो। त्यस कारण विस्तृत डिबग प्रक्रियासम्बन्धी डेटा आदान प्रदान गर्ने कार्य अस्वीकार गरिएको छ। व्यवधानका लागि क्षमा गर्नुहोला।"</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"अनुमति दिनुहोस्"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"अनुमति नदिनुहोस्"</string>
@@ -471,10 +475,10 @@
<string name="permgrouprequest_device_aware_storage_isolated" msgid="6463062962458809752">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt; मा भएका फोटो तथा मिडिया एक्सेस गर्ने अनुमति दिने हो?"</string>
<string name="permgrouprequest_contacts" msgid="8391550064551053695">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई तपाईंका कन्ट्याक्टहरू एक्सेस गर्ने अनुमति दिने हो?"</string>
<string name="permgrouprequest_device_aware_contacts" msgid="731025863972535928">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt; मा भएका तपाईंका कन्ट्याक्ट एक्सेस गर्ने अनुमति दिने हो?"</string>
- <string name="permgrouprequest_location" msgid="6990232580121067883">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई यो डिभाइसको लोकेसन प्रयोग दिने हो?"</string>
+ <string name="permgrouprequest_location" msgid="6990232580121067883">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई यो डिभाइसको लोकेसन प्रयोग गर्न दिने हो?"</string>
<string name="permgrouprequest_device_aware_location" msgid="6075412127429878638">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g> को&lt;/b&gt; लोकेसन एक्सेस गर्ने अनुमति दिने हो?"</string>
<string name="permgrouprequestdetail_location" msgid="2635935335778429894">"तपाईंले एप प्रयोग गरिरहेका बेला मात्र उक्त एपले स्थानमाथि पहुँच राख्न सक्ने छ"</string>
- <string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई यो डिभाइसको लोकेसन प्रयोग दिने हो?"</string>
+ <string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई यो डिभाइसको लोकेसन प्रयोग गर्न दिने हो?"</string>
<string name="permgroupbackgroundrequest_device_aware_location" msgid="1264484517831380016">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g> को&lt;/b&gt; लोकेसन एक्सेस गर्ने अनुमति दिने हो?"</string>
<string name="permgroupbackgroundrequestdetail_location" msgid="8021219324989662957">"तपाईं उक्त एप प्रयोग नगरिरहेका बेलामा लगायत जुनसुकै समयमा यो एपले तपाईंको स्थानमाथि पहुँच राख्न सक्छ। "<annotation id="link">"सेटिङमा गई अनुमति दिनुहोस्।"</annotation></string>
<string name="permgroupupgraderequest_location" msgid="8328408946822691636">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; का लागि स्थानसम्बन्धी पहुँच परिवर्तन गर्ने हो?"</string>
@@ -580,10 +584,10 @@
<string name="security_settings" msgid="3808106921175271317">"सुरक्षासम्बन्धी सेटिङ"</string>
<string name="sensor_permissions_qs" msgid="1022267900031317472">"अनुमति"</string>
<string name="safety_privacy_qs_tile_title" msgid="727301867710374052">"सुरक्षा तथा गोपनीयता"</string>
- <string name="safety_privacy_qs_tile_subtitle" msgid="3621544532041936749">"स्थिति जाँच गर्नुहोस्"</string>
+ <string name="safety_privacy_qs_tile_subtitle" msgid="3621544532041936749">"स्थिति जाँच्नुहोस्"</string>
<string name="privacy_controls_qs" msgid="5780144882040591169">"तपाईंका गोपनीयतासम्बन्धी सेटिङ"</string>
<string name="security_settings_button_label_qs" msgid="8280343822465962330">"थप सेटिङ"</string>
- <string name="camera_toggle_label_qs" msgid="3880261453066157285">"क्यामेरा प्रयोग गर्ने अनुमति"</string>
+ <string name="camera_toggle_label_qs" msgid="3880261453066157285">"क्यामेरा एक्सेस"</string>
<string name="microphone_toggle_label_qs" msgid="8132912469813396552">"माइक एक्सेस"</string>
<string name="permissions_removed_qs" msgid="8957319130625294572">"अनुमति हटाइएको छ"</string>
<string name="camera_usage_qs" msgid="4394233566086665994">"हालसालै गरिएको क्यामेराको प्रयोगसम्बन्धी जानकारी हेर्नुहोस्"</string>
@@ -625,7 +629,7 @@
<string name="safety_center_background_location_access_revoked" msgid="6972274943343442213">"अनुमति बदलियो"</string>
<string name="safety_center_view_recent_location_access" msgid="3524391299490678243">"हालसालै कहिले लोकेसनसम्बन्धी प्रयोग गरिएको थियो हेर्नुहोस्"</string>
<string name="privacy_controls_title" msgid="7605929972256835199">"गोपनीयतासम्बन्धी सेटिङ"</string>
- <string name="camera_toggle_title" msgid="1251201397431837666">"क्यामेरा प्रयोग गर्ने अनुमति"</string>
+ <string name="camera_toggle_title" msgid="1251201397431837666">"क्यामेरा एक्सेस"</string>
<string name="mic_toggle_title" msgid="2649991093496110162">"माइक्रोफोन प्रयोग गर्ने अनुमति"</string>
<string name="perm_toggle_description" msgid="7801326363741451379">"एप तथा सेवाहरूका हकमा"</string>
<string name="mic_toggle_description" msgid="9163104307990677157">"एप तथा सेवाहरूका हकमा। यो सेटिङ अफ गरिएको अवस्थामा तपाईंले आपत्‍कालीन नम्बरमा कल गर्नुभयो भने माइक्रोफोनसम्बन्धी डेटा अझै पनि सेयर गरिन सक्छ।"</string>
@@ -644,7 +648,7 @@
<string name="permission_rationale_data_sharing_varies_message" msgid="4224469559084489222">"एपको संस्करण, प्रयोगसम्बन्धी जानकारी, तपाईं बसोबास गर्ने क्षेत्र र तपाईंको उमेरका आधारमा जानकारीको व्यवस्थापनसम्बन्धी अभ्यासहरू फरक हुन सक्छन्। "<annotation id="link">"जानकारी सेयर गर्नेसम्बन्धी अभ्यासका बारेमा थप जानकारी"</annotation></string>
<string name="permission_rationale_data_sharing_varies_message_without_link" msgid="4912763761399025094">"एपको संस्करण, प्रयोगसम्बन्धी जानकारी, तपाईं बसोबास गर्ने क्षेत्र र तपाईंको उमेरका आधारमा जानकारीको व्यवस्थापनसम्बन्धी अभ्यासहरू फरक हुन सक्छन्।"</string>
<string name="permission_rationale_location_settings_title" msgid="7204145004850190953">"तपाईंको लोकेसन डेटा"</string>
- <string name="permission_rationale_permission_settings_message" msgid="631286040979660267"><annotation id="link">"गोपनीयतासम्बन्धी सेटिङ"</annotation>"मा गई यो एपलाई दिइएको अनुमति परिवर्तन गर्नुहोस्"</string>
+ <string name="permission_rationale_permission_settings_message" msgid="631286040979660267"><annotation id="link">"गोपनीयतासम्बन्धी सेटिङ"</annotation>"मा गई यो एपलाई दिइएको एक्सेस परिवर्तन गर्नुहोस्"</string>
<string name="permission_rationale_purpose_app_functionality" msgid="8397736681065841405">"एपका सुविधा उपलब्ध गराउने"</string>
<string name="permission_rationale_purpose_analytics" msgid="2070800501189620712">"Analytics"</string>
<string name="permission_rationale_purpose_developer_communications" msgid="6453047018892062374">"विकासकर्ताबाट जानकारी प्राप्त गर्ने"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"प्रतिबन्ध लगाइएका सेटिङ अनलक गर्नुहोस्"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"प्रतिबन्ध लगाइएका सेटिङ"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"तपाईंको खाताको सुरक्षार्थ यो सेटिङ हाल उपलब्ध छैन।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"कल भइरहेका बेला यो कारबाही पूरा गर्न सकिँदैन"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n तपाईंको डिभाइस र जानकारी सुरक्षित राख्न यो सेटिङ ब्लक गरिएको छ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"स्क्याम गर्ने व्यक्तिहरू तपाईंलाई कुनै नयाँ स्रोतबाट अज्ञात एपहरू इन्स्टल गर्न लगाएर तपाईंको डिभाइसमा हानिकारक एपहरू इन्स्टल गर्ने प्रयास गर्न सक्छन्।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"स्क्याम गर्ने व्यक्तिहरू तपाईंलाई कुनै एपलाई सर्वसुलभता सेवा प्रयोग गर्ने अनुमति दिन लगाएर तपाईंको डिभाइस नियन्त्रण गर्ने प्रयास गर्न सक्छन्।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"स्क्याम गर्ने व्यक्तिहरू यो सेटिङ प्रयोग गरेर तपाईंको डिभाइसमा हानि पुर्‍याउने प्रयास गर्न सक्छन्।"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"एपले <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> प्रयोग गर्न मागेको अनुमति अस्वीकार गरिएको छ"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"यो एपले तपाईंको व्यक्तिगत तथा वित्तीय जानकारी जोखिममा पार्न सक्ने खालको संवेदनशील अनुमति मागेको छ।<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>तपाईले उक्त प्रतिबन्धित अनुमति नदिएका खण्डमा यो एपले राम्रोसँग काम नगर्न सक्छ। &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;अनुमति दिने तरिका सिक्नुहोस्&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"यो एपले डिफल्ट <xliff:g id="ROLE_NAME">%1$s</xliff:g> का रूपमा काम गर्न मागेको अनुमति अस्वीकार गरिएको छ"</string>
diff --git a/PermissionController/res/values-night-v33/themes.xml b/PermissionController/res/values-night-v33/themes.xml
index 9b6f638a6..31940ede5 100644
--- a/PermissionController/res/values-night-v33/themes.xml
+++ b/PermissionController/res/values-night-v33/themes.xml
@@ -52,6 +52,8 @@
@style/SecondarySafetyCenterActionButton.Responsive
</item>
+ <item name="scCardSideMargin">@dimen/sc_spacing_large</item>
+
<item name="textColorScActionButton">@color/sc_primary_action_button_text</item>
<item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
diff --git a/PermissionController/res/values-nl-v33/strings.xml b/PermissionController/res/values-nl-v33/strings.xml
index 5b89f1135..da76670cd 100644
--- a/PermissionController/res/values-nl-v33/strings.xml
+++ b/PermissionController/res/values-nl-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Meer meldingen"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Gesloten meldingen"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Uitvouwen en nog 1 melding bekijken}other{Uitvouwen en nog # meldingen bekijken}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Samenvouwen"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Waarschuwing. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Actie afgerond"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Check de instellingen die je apparaat beter kunnen beschermen"</string>
diff --git a/PermissionController/res/values-nl/strings.xml b/PermissionController/res/values-nl/strings.xml
index 64cba5c57..5437c3c7e 100644
--- a/PermissionController/res/values-nl/strings.xml
+++ b/PermissionController/res/values-nl/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Toegang tot <xliff:g id="PERM">%1$s</xliff:g> voor deze app op <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Alle rechten van <xliff:g id="APP">%1$s</xliff:g> bekijken"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Alle apps met dit recht bekijken"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informatie"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Instellingen"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Gebruik van Assistent-microfoon tonen"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Niet-gebruikte app-instellingen"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Rechten intrekken als app niet wordt gebruikt"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Links openen"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Standaard voor werk"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Standaard voor privégedeelte"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Geoptimaliseerd voor apparaat"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Anders"</string>
<string name="default_app_none" msgid="9084592086808194457">"Geen"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Systeem­standaard)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Geen apps"</string>
@@ -451,10 +455,10 @@
<string name="home_missing_work_profile_support" msgid="1756855847669387977">"Ondersteunt geen werkprofielen"</string>
<string name="encryption_unaware_confirmation_message" msgid="8274491794636402484">"Opmerking: Als je het apparaat opnieuw opstart en een schermvergrendeling hebt ingesteld, kan deze app pas worden gestart nadat je het apparaat hebt ontgrendeld."</string>
<string name="assistant_confirmation_message" msgid="7476540402884416212">"De assistentie-app kan informatie over de gebruikte apps in je systeem lezen, waaronder informatie die zichtbaar is op je scherm of toegankelijk is in de apps."</string>
- <string name="incident_report_channel_name" msgid="3144954065936288440">"Foutopsporingsinformatie delen"</string>
+ <string name="incident_report_channel_name" msgid="3144954065936288440">"Foutopsporings­informatie delen"</string>
<string name="incident_report_notification_title" msgid="4635984625656519773">"Gedetailleerde foutopsporingsinformatie delen?"</string>
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> wil foutopsporingsinformatie uploaden."</string>
- <string name="incident_report_dialog_title" msgid="669104389325204095">"Foutopsporingsinformatie delen?"</string>
+ <string name="incident_report_dialog_title" msgid="669104389325204095">"Foutopsporings­informatie delen?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Het systeem heeft een probleem gedetecteerd."</string>
<string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> wil een bugrapport van dit apparaat uploaden dat is gemaakt op <xliff:g id="DATE">%2$s</xliff:g> om <xliff:g id="TIME">%3$s</xliff:g>. Bugrapporten omvatten persoonlijke informatie over je apparaat of gegevens die zijn geregistreerd door apps, zoals gebruikersnamen, locatiegegevens, apparaat-ID\'s en netwerkgegevens. Deel alleen bugrapporten met mensen en apps die je met deze informatie vertrouwt. Wil je <xliff:g id="APP_NAME_1">%4$s</xliff:g> toestaan een bugrapport te uploaden?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Er is een fout opgetreden bij het verwerken van het bugrapport voor <xliff:g id="APP_NAME">%1$s</xliff:g>. Het delen van de gedetailleerde foutopsporingsinformatie is daarom geweigerd. Onze excuses voor de onderbreking."</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Beperkte instellingen toestaan"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Beperkte instelling"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Ter beveiliging is deze instelling op dit moment niet beschikbaar."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Kan actie niet afronden tijdens gesprek"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Deze instelling is geblokkeerd om je apparaat en gegevens te beschermen"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Scammers kunnen proberen schadelijke apps te installeren door je te vragen onbekende apps te installeren via een nieuwe bron."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Scammers kunnen proberen de controle over je apparaat over te nemen door je te vragen toegankelijkheidstoegang voor een app toe te staan."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Scammers kunnen proberen je apparaat te beschadigen met deze instelling."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"App heeft geen toegang gekregen tot <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"De app heeft toegang gevraagd tot een gevoelig recht, waardoor je persoonlijke en financiële informatie risico kunnen lopen.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Het is mogelijk dat de app niet goed werkt zonder dit beperkte recht. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Meer informatie over hoe je toegang geeft&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"App heeft geen toegang gekregen om de standaard <xliff:g id="ROLE_NAME">%1$s</xliff:g> te worden"</string>
diff --git a/PermissionController/res/values-or-v33/strings.xml b/PermissionController/res/values-or-v33/strings.xml
index 84c07b84e..c77b351d7 100644
--- a/PermissionController/res/values-or-v33/strings.xml
+++ b/PermissionController/res/values-or-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"ଅଧିକ ଆଲର୍ଟ"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"ଖାରଜ କରାଯାଇଥିବା ଆଲର୍ଟଗୁଡ଼ିକ"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{ବିସ୍ତାର କରି ଆଉ ଗୋଟିଏ ଆଲର୍ଟ ଦେଖନ୍ତୁ}other{ବିସ୍ତାର କରି ଆଉ #ଟି ଆଲର୍ଟ ଦେଖନ୍ତୁ}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"ଆଲର୍ଟ। <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"କାର୍ଯ୍ୟ ସମୂର୍ଣ୍ଣ ହୋଇଛି"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"ଆପଣଙ୍କ ଡିଭାଇସରେ ସୁରକ୍ଷା ଯୋଗ କରୁଥିବା ସେଟିଂସକୁ ଦେଖନ୍ତୁ"</string>
diff --git a/PermissionController/res/values-or/strings.xml b/PermissionController/res/values-or/strings.xml
index 69f4fded7..3a9713fbd 100644
--- a/PermissionController/res/values-or/strings.xml
+++ b/PermissionController/res/values-or/strings.xml
@@ -52,7 +52,7 @@
<string name="permission_revoked_none" msgid="9213345075484381180">"କୌଣସି ଅନୁମତି ଅକ୍ଷମ କରାଯାଇନାହିଁ"</string>
<string name="grant_dialog_button_allow" msgid="5314677880021102550">"ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="grant_dialog_button_allow_always" msgid="4485552579273565981">"ସବୁବେଳେ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
- <string name="grant_dialog_button_allow_foreground" msgid="501896824973636533">"ଆପ୍ ବ୍ୟବହାର କରିବା ସମୟରେ"</string>
+ <string name="grant_dialog_button_allow_foreground" msgid="501896824973636533">"ଆପ ବ୍ୟବହାର କରିବା ସମୟରେ"</string>
<string name="grant_dialog_button_change_to_precise_location" msgid="3273115879467236033">"ସଠିକ୍ ଲୋକେସନକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
<string name="grant_dialog_button_keey_approximate_location" msgid="438025182769080011">"ଆନୁମାନିକ ଲୋକେସନ୍ ରଖନ୍ତୁ"</string>
<string name="grant_dialog_button_allow_one_time" msgid="2618088516449706391">"କେବଳ ଏହି ସମୟ ପାଇଁ"</string>
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>ରେ ଏହି ଆପ ପାଇଁ <xliff:g id="PERM">%1$s</xliff:g> ଆକ୍ସେସ ଅଛି"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"ସମସ୍ତ <xliff:g id="APP">%1$s</xliff:g> ଅନୁମତି ଦେଖନ୍ତୁ"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ଏହି ଅନୁମତି ଥିବା ସମସ୍ତ ଆପ୍ସ ଦେଖନ୍ତୁ"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"ସୂଚନା"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"ସେଟିଂସ"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Assistant ମାଇକ୍ରୋଫୋନ୍ ବ୍ୟବହାର ଦେଖାନ୍ତୁ"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ଅବ୍ୟବହୃତ ଆପ ସେଟିଂସ"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"ଯଦି ଆପ୍ ବ୍ୟବହାର କରାଯାଇନାହିଁ, ତେବେ ଅନୁମତିଗୁଡ଼ିକୁ କାଢ଼ି ଦିଅନ୍ତୁ"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"ଓପନିଂ ଲିଙ୍କ୍"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"କାର୍ଯ୍ୟ ପାଇଁ ଡିଫଲ୍ଟ ଅଛି"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"ପ୍ରାଇଭେଟ ସ୍ପେସ ପାଇଁ ଡିଫଲ୍ଟ"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ଡିଭାଇସ ପାଇଁ ଅପ୍ଟିମାଇଜ କରାଯାଇଛି"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"ଅନ୍ୟ"</string>
<string name="default_app_none" msgid="9084592086808194457">"କିଛି ଆପ ସେଟ କରାଯାଇନାହିଁ"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(ସିଷ୍ଟମ୍ ଡିଫଲ୍ଟ)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"କୌଣସି ଆପ୍‌ ନାହିଁ"</string>
@@ -487,7 +491,7 @@
<string name="permgrouprequest_device_aware_fineupgrade" msgid="4453775952305587571">"&lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;ରେ <xliff:g id="APP_NAME">&lt;b&gt;%1$s&lt;/b&gt;</xliff:g>ର ଲୋକେସନ ଆକ୍ସେସକୁ ଆନୁମାନିକରୁ ସଠିକକୁ ପରିବର୍ତ୍ତନ କରିବେ?"</string>
<string name="permgrouprequest_coarselocation" msgid="7244605063736425232">"ଏହି ଡିଭାଇସର ଆନୁମାନିକ ଲୋକେସନ୍ ଆକ୍ସେସ୍ କରିବା ପାଇଁ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
<string name="permgrouprequest_device_aware_coarselocation" msgid="8367540370912066757">"&lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;ର ଆନୁମାନିକ ଲୋକେସନକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
- <string name="permgrouprequest_finelocation_imagetext" msgid="1313062433398914334">"ସଠିକ୍"</string>
+ <string name="permgrouprequest_finelocation_imagetext" msgid="1313062433398914334">"ସଠିକ"</string>
<string name="permgrouprequest_coarselocation_imagetext" msgid="8650605041483025297">"(ଆନୁମାନିକ)"</string>
<string name="permgrouprequest_calendar" msgid="1493150855673603806">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;କୁ ଆପଣଙ୍କ କ୍ୟାଲେଣ୍ଡର୍‌କୁ ଆକ୍ସେସ୍‍ କରିବା ପାଇଁ ଅନୁମତି ଦେବେ କି?"</string>
<string name="permgrouprequest_device_aware_calendar" msgid="7161929851377463612">"&lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;ରେ ଆପଣଙ୍କ କେଲେଣ୍ଡରକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"ପ୍ରତିବନ୍ଧିତ ସେଟିଂସକୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"ପ୍ରତିବନ୍ଧିତ ସେଟିଂ"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"ଆପଣଙ୍କ ସୁରକ୍ଷା ପାଇଁ ଏହି ସେଟିଂ ବର୍ତ୍ତମାନ ଅନୁପଲବ୍ଧ ଅଟେ।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"କଲ ସମୟରେ କାର୍ଯ୍ୟ ସମ୍ପୂର୍ଣ୍ଣ କରାଯାଇପାରିବ ନାହିଁ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n ଆପଣଙ୍କ ଡିଭାଇସ ଏବଂ ଡାଟାକୁ ସୁରକ୍ଷିତ ରଖିବା ପାଇଁ ଏହି ସେଟିଂକୁ ବ୍ଲକ କରାଯାଇଛି"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"ସ୍କାମରମାନେ ଆପଣଙ୍କୁ ଏକ ନୂଆ ସୋର୍ସରୁ ଅଜଣା ଆପ୍ସ ଇନଷ୍ଟଲ କରିବାକୁ କହି କ୍ଷତିକାରକ ଆପ୍ସ ଇନଷ୍ଟଲ କରିବାକୁ ଚେଷ୍ଟା କରିପାରନ୍ତି।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"ସ୍କାମରମାନେ ଆପଣଙ୍କୁ ଏକ ଆପ ପାଇଁ ଆକ୍ସେସିବିଲିଟୀ ଆକ୍ସେସ ଅନୁମତି ଦେବାକୁ କହି ଆପଣଙ୍କ ଡିଭାଇସକୁ ନିୟନ୍ତ୍ରଣ କରିବାକୁ ଚେଷ୍ଟା କରିପାରନ୍ତି।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"ଏହି ସେଟିଂ ମାଧ୍ୟମରେ ସ୍କାମରମାନେ ଆପଣଙ୍କ ଡିଭାଇସକୁ କ୍ଷତି ପହଞ୍ଚାଇବାକୁ ପ୍ରଚେଷ୍ଟା କରିପାରନ୍ତି।"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>କୁ ଆପର ଆକ୍ସେସକୁ ଅଗ୍ରାହ୍ୟ କରାଯାଇଛି"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"ଆପଣଙ୍କ ବ୍ୟକ୍ତିଗତ ଏବଂ ଆର୍ଥିକ ସୂଚନାକୁ ବିପଦରେ ପକାଇପାରୁଥିବା ଏକ ସମ୍ବେଦନଶୀଳ ଅନୁମତିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ ଏହି ଆପଟି ଅନୁରୋଧ କରିଛି।<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>ଏହା ସମ୍ଭବ ଯେ ଏହି ପ୍ରତିବନ୍ଧିତ ଅନୁମତି ବିନା ଆପ ସଠିକ ଭାବେ କାର୍ଯ୍ୟ କରିବ ନାହିଁ। &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;ଆକ୍ସେସକୁ କିପରି ଅନୁମତି ଦେବେ ତାହା ଜାଣନ୍ତୁ&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"ଡିଫଲ୍ଟ <xliff:g id="ROLE_NAME">%1$s</xliff:g> ହେବା ପାଇଁ ଆପର ଆକ୍ସେସକୁ ଅଗ୍ରାହ୍ୟ କରାଯାଇଛି"</string>
diff --git a/PermissionController/res/values-pa-v33/strings.xml b/PermissionController/res/values-pa-v33/strings.xml
index 0aa143302..7a2fc6ca5 100644
--- a/PermissionController/res/values-pa-v33/strings.xml
+++ b/PermissionController/res/values-pa-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"ਹੋਰ ਅਲਰਟ"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"ਖਾਰਜ ਕੀਤੇ ਅਲਰਟ"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{ਵਿਸਤਾਰ ਕਰੋ ਅਤੇ ਇੱਕ ਹੋਰ ਅਲਰਟ ਦੇਖੋ}one{ਵਿਸਤਾਰ ਕਰੋ ਅਤੇ # ਹੋਰ ਅਲਰਟ ਦੇਖੋ}other{ਵਿਸਤਾਰ ਕਰੋ ਅਤੇ # ਹੋਰ ਅਲਰਟ ਦੇਖੋ}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"ਸਮੇਟੋ"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"ਅਲਰਟ। <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"ਕਾਰਵਾਈ ਪੂਰੀ ਹੋਈ"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"ਉਨ੍ਹਾਂ ਸੈਟਿੰਗਾਂ ਦੀ ਜਾਂਚ ਕਰੋ ਜੋ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਵਿੱਚ ਸੁਰੱਖਿਆ ਸ਼ਾਮਲ ਕਰ ਸਕਦੀਆਂ ਹਨ"</string>
diff --git a/PermissionController/res/values-pa/strings.xml b/PermissionController/res/values-pa/strings.xml
index f2f099d61..20f910f74 100644
--- a/PermissionController/res/values-pa/strings.xml
+++ b/PermissionController/res/values-pa/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> \'ਤੇ ਇਸ ਐਪ ਲਈ <xliff:g id="PERM">%1$s</xliff:g> ਦੀ ਪਹੁੰਚ"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"<xliff:g id="APP">%1$s</xliff:g> ਦੀਆਂ ਸਾਰੀਆਂ ਇਜਾਜ਼ਤਾਂ ਦੇਖੋ"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ਇਸ ਇਜਾਜ਼ਤ ਵਾਲੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਦੇਖੋ"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"ਜਾਣਕਾਰੀ"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"ਸੈਟਿੰਗਾਂ"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Assistant ਦੇ ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਦੀ ਵਰਤੋਂ ਦਿਖਾਓ"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ਅਣਵਰਤੀ ਐਪ ਦੀਆਂ ਸੈਟਿੰਗਾਂ"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"ਐਪ ਨੂੰ ਨਾ ਵਰਤੇ ਜਾਣ \'ਤੇ ਇਸ ਲਈ ਦਿੱਤੀਆਂ ਇਜਾਜ਼ਤਾਂ ਨੂੰ ਹਟਾ ਦਿਓ"</string>
@@ -251,7 +253,7 @@
<string name="app_permission_never_accessed_summary" msgid="401346181461975090">"ਕਦੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ"</string>
<string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"ਮਨ੍ਹਾ ਕੀਤਾ ਗਿਆ / ਕਦੇ ਪਹੁੰਚ ਨਹੀਂ ਕੀਤੀ"</string>
<string name="allowed_header" msgid="7769277978004790414">"ਮਨਜ਼ੂਰਸ਼ੁਦਾ"</string>
- <string name="allowed_always_header" msgid="6455903312589013545">"ਹਰ ਵੇਲੇ ਪਹੁੰਚ ਵਾਲੀਆਂ"</string>
+ <string name="allowed_always_header" msgid="6455903312589013545">"ਹਰ ਵੇਲੇ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੈ"</string>
<string name="allowed_foreground_header" msgid="6845655788447833353">"ਸਿਰਫ਼ ਐਪ ਵਰਤੋਂ ਵੇਲੇ ਪਹੁੰਚ ਵਾਲੀਆਂ"</string>
<string name="allowed_storage_scoped" msgid="5383645873719086975">"ਸਿਰਫ਼ ਮੀਡੀਆ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦਿੱਤੀ ਗਈ"</string>
<string name="allowed_storage_full" msgid="5356699280625693530">"ਸਾਰੀਆਂ ਫ਼ਾਈਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰਨ ਦਿੱਤਾ ਗਿਆ"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"ਖੁੱਲ੍ਹਣ ਵਾਲੇ ਲਿੰਕ"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"ਕੰਮ ਲਈ ਪੂਰਵ-ਨਿਰਧਾਰਤ"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"ਪ੍ਰਾਈਵੇਟ ਸਪੇਸ ਲਈ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ਡੀਵਾਈਸ ਲਈ ਸੁਯੋਗ ਬਣਾਈਆਂ"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"ਹੋਰ"</string>
<string name="default_app_none" msgid="9084592086808194457">"ਕੋਈ ਨਹੀਂ"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(ਸਿਸਟਮ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"ਕੋਈ ਐਪਾਂ ਨਹੀਂ"</string>
@@ -471,7 +475,7 @@
<string name="permgrouprequest_device_aware_storage_isolated" msgid="6463062962458809752">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt; \'ਤੇ ਫ਼ੋਟੋਆਂ ਅਤੇ ਮੀਡੀਆ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
<string name="permgrouprequest_contacts" msgid="8391550064551053695">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੇਣੀ ਹੈ?"</string>
<string name="permgrouprequest_device_aware_contacts" msgid="731025863972535928">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt; \'ਤੇ ਆਪਣੇ ਸੰਪਰਕਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
- <string name="permgrouprequest_location" msgid="6990232580121067883">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ ਇਸ ਡੀਵਾਈਸ ਦੇ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੇਣੀ ਹੈ?"</string>
+ <string name="permgrouprequest_location" msgid="6990232580121067883">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ ਇਸ ਡੀਵਾਈਸ ਦੇ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
<string name="permgrouprequest_device_aware_location" msgid="6075412127429878638">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt; ਦੇ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣੀ ਹੈ?"</string>
<string name="permgrouprequestdetail_location" msgid="2635935335778429894">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਐਪ ਦੀ ਵਰਤੋਂ ਕਰਨ ਵੇਲੇ ਹੀ ਐਪ ਕੋਲ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਹੋਵੇਗੀ"</string>
<string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ ਇਸ ਡੀਵਾਈਸ ਦੇ ਟਿਕਾਣੇ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੇਣੀ ਹੈ?"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"ਪ੍ਰਤਿਬੰਧਿਤ ਸੈਟਿੰਗਾਂ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"ਪ੍ਰਤਿਬੰਧਿਤ ਸੈਟਿੰਗਾਂ"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਲਈ, ਫ਼ਿਲਹਾਲ ਇਹ ਸੈਟਿੰਗ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"ਕਾਲ ਦੌਰਾਨ ਇਹ ਕਾਰਵਾਈ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਅਤੇ ਡਾਟੇ ਨੂੰ ਸੁਰੱਖਿਅਤ ਰੱਖਣ ਲਈ ਇਸ ਸੈਟਿੰਗ ਨੂੰ ਬਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"ਘਪਲੇਬਾਜ਼ ਤੁਹਾਨੂੰ ਕਿਸੇ ਨਵੇਂ ਸਰੋਤ ਤੋਂ ਅਗਿਆਤ ਐਪਾਂ ਸਥਾਪਤ ਕਰਨ ਲਈ ਕਹਿ ਕੇ ਨੁਕਸਾਨਦੇਹ ਐਪਾਂ ਸਥਾਪਤ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਸਕਦੇ ਹਨ।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"ਘਪਲੇਬਾਜ਼ ਤੁਹਾਡੇ ਤੋਂ ਕਿਸੇ ਐਪ ਲਈ ਪਹੁੰਚਯੋਗਤਾ ਤੱਕ ਪਹੁੰਚ ਦੀ ਮੰਗ ਕਰ ਕੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨੂੰ ਕੰਟਰੋਲ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਸਕਦੇ ਹਨ।"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"ਘਪਲੇਬਾਜ਼ ਇਸ ਸੈਟਿੰਗ ਨਾਲ ਤੁਹਾਡੇ ਡੀਵਾਈਸ ਨੂੰ ਨੁਕਸਾਨ ਪਹੁੰਚਾਉਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਸਕਦੇ ਹਨ।"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"ਐਪ ਨੂੰ <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੱਤੀ ਗਈ"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"ਐਪ ਨੇ ਸੰਵੇਦਨਸ਼ੀਲ ਇਜਾਜ਼ਤ ਤੱਕ ਪਹੁੰਚ ਦੀ ਬੇਨਤੀ ਕੀਤੀ ਹੈ, ਜਿਸ ਨਾਲ ਤੁਹਾਡੀ ਨਿੱਜੀ ਅਤੇ ਵਿੱਤੀ ਜਾਣਕਾਰੀ ਜੋਖਮ ਵਿੱਚ ਪੈ ਸਕਦੀ ਹੈ।<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>ਇਹ ਸੰਭਵ ਹੈ, ਕਿ ਐਪ ਇਸ ਪ੍ਰਤਿਬੰਧਿਤ ਇਜਾਜ਼ਤ ਤੋਂ ਬਿਨਾਂ ਸਹੀ ਢੰਗ ਨਾਲ ਕੰਮ ਨਾ ਕਰੇ। &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਦੇਣ ਦੇ ਤਰੀਕੇ ਬਾਰੇ ਜਾਣੋ&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"ਐਪ ਨੂੰ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ <xliff:g id="ROLE_NAME">%1$s</xliff:g> ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਨਹੀਂ ਦਿੱਤੀ ਗਈ"</string>
diff --git a/PermissionController/res/values-pl-v33/strings.xml b/PermissionController/res/values-pl-v33/strings.xml
index a6cdbd775..326d9f782 100644
--- a/PermissionController/res/values-pl-v33/strings.xml
+++ b/PermissionController/res/values-pl-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Więcej alertów"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Odrzucone alerty"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Rozwiń i zobacz jeszcze 1 alert}few{Rozwiń i zobacz jeszcze # alerty}many{Rozwiń i zobacz jeszcze # alertów}other{Rozwiń i zobacz jeszcze # alertu}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Zwiń"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alert. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Działanie ukończone"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Sprawdź ustawienia, które mogą zwiększyć bezpieczeństwo Twojego urządzenia"</string>
diff --git a/PermissionController/res/values-pl/strings.xml b/PermissionController/res/values-pl/strings.xml
index 0b40b41f6..0aefe4a69 100644
--- a/PermissionController/res/values-pl/strings.xml
+++ b/PermissionController/res/values-pl/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Aplikacja ma przyznane uprawnienie <xliff:g id="PERM">%1$s</xliff:g> na tym urządzeniu: <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Zobacz wszystkie uprawnienia aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Wyświetl wszystkie aplikacje z tym uprawnieniem"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informacje"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Ustawienia"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Pokaż użycie mikrofonu w Asystencie"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Ustawienia nieużywanych aplikacji"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Usuń uprawnienia, jeśli aplikacja jest nieużywana"</string>
@@ -250,13 +252,13 @@
<string name="app_permission_most_recent_denied_summary" msgid="7659497197737708112">"Aktualnie odmowa / ostatni dostęp: <xliff:g id="TIME_DATE">%1$s</xliff:g>"</string>
<string name="app_permission_never_accessed_summary" msgid="401346181461975090">"Nigdy nie użyto"</string>
<string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"Odmowa / nigdy nie użyto"</string>
- <string name="allowed_header" msgid="7769277978004790414">"Mają dostęp"</string>
+ <string name="allowed_header" msgid="7769277978004790414">"Ma dostęp"</string>
<string name="allowed_always_header" msgid="6455903312589013545">"Mają ciągły dostęp"</string>
<string name="allowed_foreground_header" msgid="6845655788447833353">"Mają dostęp tylko podczas używania"</string>
<string name="allowed_storage_scoped" msgid="5383645873719086975">"Zezwolono na dostęp tylko do multimediów"</string>
<string name="allowed_storage_full" msgid="5356699280625693530">"Zezwolono na zarządzanie wszystkimi plikami"</string>
<string name="ask_header" msgid="2633816846459944376">"Zawsze pytaj"</string>
- <string name="denied_header" msgid="903209608358177654">"Nie mają dostępu"</string>
+ <string name="denied_header" msgid="903209608358177654">"Nie ma dostępu"</string>
<string name="permission_group_name_with_device_name" msgid="8798741850536024820">"<xliff:g id="PERM_GROUP_NAME">%1$s</xliff:g> na tym urządzeniu: <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Zobacz więcej aplikacji z dostępem do wszystkich plików"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dzień}few{# dni}many{# dni}other{# dnia}}"</string>
@@ -346,7 +348,7 @@
<string name="no_apps_allowed" msgid="7718822655254468631">"Nie zezwolono żadnym aplikacjom"</string>
<string name="no_apps_allowed_full" msgid="8011716991498934104">"Brak aplikacji z uprawnieniami dla wszystkich plików"</string>
<string name="no_apps_allowed_scoped" msgid="4908850477787659501">"Brak aplikacji z uprawnieniami tylko dla multimediów"</string>
- <string name="no_apps_denied" msgid="7663435886986784743">"Nie zabroniono dostępu żadnym aplikacjom"</string>
+ <string name="no_apps_denied" msgid="7663435886986784743">"Nie odmówiono dostępu żadnym aplikacjom"</string>
<string name="car_permission_selected" msgid="180837028920791596">"Wybrana"</string>
<string name="settings" msgid="5409109923158713323">"Ustawienia"</string>
<string name="accessibility_service_dialog_title_single" msgid="7956432823014102366">"Usługa <xliff:g id="SERVICE_NAME">%s</xliff:g> ma pełny dostęp do urządzenia"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Otwieranie linków"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Domyślne do pracy"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Domyślne dla przestrzeni prywatnej"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Zoptymalizowane dla danego urządzenia"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Inne"</string>
<string name="default_app_none" msgid="9084592086808194457">"Brak"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Domyślna aplikacja systemowa)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Brak aplikacji"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Zezwól na ustawienia z ograniczonym dostępem"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Ustawienie z ograniczonym dostępem"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Ze względów bezpieczeństwa to ustawienie jest obecnie niedostępne."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Nie można wykonać działania podczas połączenia"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n To ustawienie jest zablokowane, aby chronić Twoje urządzenie i dane"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Oszuści mogą próbować zainstalować szkodliwe aplikacje, prosząc o instalację nieznanych programów z nowego źródła."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Oszuści mogą próbować przejąć kontrolę nad Twoim urządzeniem, prosząc o przyznanie uprawnień do ułatwień dostępu aplikacji."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Oszuści mogą próbować wyrządzić szkody na Twoim urządzeniu, wykorzystując to ustawienie."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Aplikacja nie otrzymała dostępu do uprawnień <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Aplikacja wymaga dostępu do uprawnień newralgicznych, co może zagrażać Twoim danym osobowym i informacjom finansowym.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Możliwe, że bez tego uprawnienia z ograniczeniami aplikacja nie będzie działać poprawnie. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Jak zezwolić na dostęp&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikacja nie otrzymała dostępu jako domyślna <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-pt-rBR-v33/strings.xml b/PermissionController/res/values-pt-rBR-v33/strings.xml
index aeda06c8f..9fba7c078 100644
--- a/PermissionController/res/values-pt-rBR-v33/strings.xml
+++ b/PermissionController/res/values-pt-rBR-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Mais alertas"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Alertas dispensados"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Abra para ver mais um alerta}one{Abra para ver mais # alerta}many{Abra para ver mais # de alertas}other{Abra para ver mais # alertas}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Fechar"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerta. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Ação concluída"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Verifique as configurações que podem adicionar mais proteção ao seu dispositivo"</string>
diff --git a/PermissionController/res/values-pt-rBR/strings.xml b/PermissionController/res/values-pt-rBR/strings.xml
index fea62d7e7..262dd63ee 100644
--- a/PermissionController/res/values-pt-rBR/strings.xml
+++ b/PermissionController/res/values-pt-rBR/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Acesso de <xliff:g id="PERM">%1$s</xliff:g> para esse app no <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Ver todas as permissões do app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Mostrar todos os apps que têm esta permissão"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informações"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Configurações"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Mostrar uso de microfone pelo Assistente"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Configurações de apps não usados"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Remover permissões se o app não for usado"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Abrir links"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Padrão para trabalho"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Padrão para o espaço privado"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Otimizados para o dispositivo"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Outros"</string>
<string name="default_app_none" msgid="9084592086808194457">"Nenhuma"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Padrão do sistema)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nenhum app"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"Solicitação do <xliff:g id="APP_NAME">%1$s</xliff:g> para upload de informações de depuração."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Compartilhar dados de depuração?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"O sistema detectou um problema."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"Há uma solicitação do <xliff:g id="APP_NAME_0">%1$s</xliff:g> para upload de um relatório de bug criado neste dispositivo no dia <xliff:g id="DATE">%2$s</xliff:g>, <xliff:g id="TIME">%3$s</xliff:g>. Os relatórios de bug incluem informações pessoais do seu dispositivo ou registradas por apps, como nomes de usuário, dados de local, identificadores do dispositivo e informações de rede. Compartilhe relatórios de bug apenas com pessoas e apps em que você confia para ter acesso a essas informações. Permitir que o <xliff:g id="APP_NAME_1">%4$s</xliff:g> faça upload do relatório de bug?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"Há uma solicitação do <xliff:g id="APP_NAME_0">%1$s</xliff:g> para upload de um relatório de bug criado neste dispositivo no dia <xliff:g id="DATE">%2$s</xliff:g>, <xliff:g id="TIME">%3$s</xliff:g>. Os relatórios de bug incluem informações pessoais do seu dispositivo ou registradas por apps, como nomes de usuário, dados de local, identificadores do dispositivo e informações de rede. Por isso, só compartilhe relatórios de bug com pessoas e apps de sua confiança. Permitir que o <xliff:g id="APP_NAME_1">%4$s</xliff:g> faça upload do relatório de bug?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Ocorreu um erro ao processar o relatório de bug do app <xliff:g id="APP_NAME">%1$s</xliff:g>. Por isso, o compartilhamento dos dados detalhados de depuração foi negado. Lamentamos a interrupção."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Permitir"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Negar"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Permitir configurações restritas"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Configuração restrita"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Para sua segurança, essa configuração está indisponível no momento."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Não é possível realizar a ação durante uma ligação"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Esta configuração está bloqueada para proteger seu dispositivo e seus dados"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Golpistas podem pedir que você instale apps desconhecidos de uma nova fonte para tentar instalar apps nocivos."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Golpistas podem pedir que você autorize recursos de acessibilidade em um app para tentar controlar seu dispositivo."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Golpistas podem tentar danificar seu dispositivo com essa configuração."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"O app não recebeu a seguinte permissão de acesso: <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"O app solicitou acesso a uma permissão sensível que pode colocar suas informações pessoais e financeiras em risco.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>É possível que o app não funcione corretamente sem essa permissão restrita. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Aprenda a conceder acesso&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"O app não recebeu o acesso para ser a escolha padrão de: <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-pt-rPT-v33/strings.xml b/PermissionController/res/values-pt-rPT-v33/strings.xml
index a228677bf..6b1bd5b56 100644
--- a/PermissionController/res/values-pt-rPT-v33/strings.xml
+++ b/PermissionController/res/values-pt-rPT-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Mais alertas"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Alertas ignorados"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Expandir e ver mais um alerta}many{Expandir e ver mais # alertas}other{Expandir e ver mais # alertas}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Reduzir"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerta. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Ação concluída"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Verifique as definições que podem adicionar proteção ao seu dispositivo"</string>
diff --git a/PermissionController/res/values-pt-rPT/strings.xml b/PermissionController/res/values-pt-rPT/strings.xml
index 0ad01f7a5..9647f1ca8 100644
--- a/PermissionController/res/values-pt-rPT/strings.xml
+++ b/PermissionController/res/values-pt-rPT/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Acesso a <xliff:g id="PERM">%1$s</xliff:g> para esta app no dispositivo <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Ver todas as autorizações da app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Ver todas as apps com esta autorização"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informações"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Definições"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Mostrar utilização do microfone do assistente"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Definições de apps não usadas"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Remover autorizações se a app não for utilizada"</string>
@@ -362,13 +364,13 @@
<string name="role_browser_request_title" msgid="2895200507835937192">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de navegador predefinida?"</string>
<string name="role_browser_request_description" msgid="5888803407905985941">"Não são necessárias autorizações."</string>
<string name="role_dialer_label" msgid="1100224146343237968">"App de telefone predefinida"</string>
- <string name="role_dialer_short_label" msgid="7186888549465352489">"App Telefone"</string>
+ <string name="role_dialer_short_label" msgid="7186888549465352489">"App de telefone"</string>
<string name="role_dialer_description" msgid="8768708633696539612">"Apps que permitem fazer e receber chamadas no seu dispositivo"</string>
<string name="role_dialer_request_title" msgid="5959618560705912058">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de telefone predefinida?"</string>
<string name="role_dialer_request_description" msgid="6288839625724909320">"Esta app fica com acesso à sua Câmara, Contactos, Microfone, Telefone e SMS"</string>
<string name="role_dialer_search_keywords" msgid="3324448983559188087">"telefone"</string>
<string name="role_sms_label" msgid="8456999857547686640">"App de SMS predefinida"</string>
- <string name="role_sms_short_label" msgid="4371444488034692243">"app de SMS"</string>
+ <string name="role_sms_short_label" msgid="4371444488034692243">"App de SMS"</string>
<string name="role_sms_description" msgid="3424020199148153513">"Apps que permitem usar o seu número de telefone para enviar e receber mensagens de texto, fotos, vídeos e muito mais."</string>
<string name="role_sms_request_title" msgid="7953552109601185602">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como app SMS predefinida?"</string>
<string name="role_sms_request_description" msgid="2691004766132144886">"Esta app fica com acesso à sua Câmara, Contactos, Ficheiros e multimédia, Microfone, Telefone e SMS"</string>
@@ -380,7 +382,7 @@
<string name="role_emergency_request_description" msgid="131645948770262850">"Não são necessárias autorizações."</string>
<string name="role_emergency_search_keywords" msgid="1920007722599213358">"em caso de emergência"</string>
<string name="role_home_label" msgid="3871847846649769412">"App página inicial predefinida"</string>
- <string name="role_home_short_label" msgid="8544733747952272337">"App Página inicial"</string>
+ <string name="role_home_short_label" msgid="8544733747952272337">"App de página inicial"</string>
<string name="role_home_description" msgid="7997371519626556675">"Também conhecidas por \"launcher\" são apps que substituem os ecrãs principais no Android e dão acesso aos conteúdos e às funcionalidades do seu dispositivo"</string>
<string name="role_home_request_title" msgid="738136983453341081">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app Página inicial predefinida?"</string>
<string name="role_home_request_description" msgid="2658833966716057673">"Não são necessárias autorizações."</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Abertura de links"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Predefinição para o trabalho"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Predefinição para espaço privado"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Otimizadas para o dispositivo"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Outras"</string>
<string name="default_app_none" msgid="9084592086808194457">"Nenhuma"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Predefinição do sistema)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Sem apps"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Permitir definições restritas"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Definição restrita"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Para sua segurança, esta definição está indisponível atualmente."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Não é possível concluir a ação durante a chamada"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Esta definição está bloqueada para proteger o seu dispositivo e dados"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Os autores de esquemas podem tentar instalar apps prejudiciais pedindo-lhe para instalar apps desconhecidas de uma nova origem."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Os autores de esquemas podem tentar assumir o controlo do seu dispositivo pedindo-lhe para permitir o acesso de acessibilidade para uma app."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Os autores de esquemas podem tentar danificar o seu dispositivo com esta definição."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"O acesso a <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> foi negado à app"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"A app pediu acesso a uma autorização confidencial que pode pôr em risco as suas informações pessoais e financeiras.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>É possível que a app não funcione corretamente sem esta autorização restrita. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Saiba como permitir o acesso&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"O acesso à função predefinida <xliff:g id="ROLE_NAME">%1$s</xliff:g> foi negado à app"</string>
diff --git a/PermissionController/res/values-pt-v33/strings.xml b/PermissionController/res/values-pt-v33/strings.xml
index aeda06c8f..9fba7c078 100644
--- a/PermissionController/res/values-pt-v33/strings.xml
+++ b/PermissionController/res/values-pt-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Mais alertas"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Alertas dispensados"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Abra para ver mais um alerta}one{Abra para ver mais # alerta}many{Abra para ver mais # de alertas}other{Abra para ver mais # alertas}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Fechar"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerta. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Ação concluída"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Verifique as configurações que podem adicionar mais proteção ao seu dispositivo"</string>
diff --git a/PermissionController/res/values-pt/strings.xml b/PermissionController/res/values-pt/strings.xml
index fea62d7e7..262dd63ee 100644
--- a/PermissionController/res/values-pt/strings.xml
+++ b/PermissionController/res/values-pt/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Acesso de <xliff:g id="PERM">%1$s</xliff:g> para esse app no <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Ver todas as permissões do app <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Mostrar todos os apps que têm esta permissão"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informações"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Configurações"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Mostrar uso de microfone pelo Assistente"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Configurações de apps não usados"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Remover permissões se o app não for usado"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Abrir links"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Padrão para trabalho"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Padrão para o espaço privado"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Otimizados para o dispositivo"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Outros"</string>
<string name="default_app_none" msgid="9084592086808194457">"Nenhuma"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Padrão do sistema)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nenhum app"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"Solicitação do <xliff:g id="APP_NAME">%1$s</xliff:g> para upload de informações de depuração."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Compartilhar dados de depuração?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"O sistema detectou um problema."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"Há uma solicitação do <xliff:g id="APP_NAME_0">%1$s</xliff:g> para upload de um relatório de bug criado neste dispositivo no dia <xliff:g id="DATE">%2$s</xliff:g>, <xliff:g id="TIME">%3$s</xliff:g>. Os relatórios de bug incluem informações pessoais do seu dispositivo ou registradas por apps, como nomes de usuário, dados de local, identificadores do dispositivo e informações de rede. Compartilhe relatórios de bug apenas com pessoas e apps em que você confia para ter acesso a essas informações. Permitir que o <xliff:g id="APP_NAME_1">%4$s</xliff:g> faça upload do relatório de bug?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"Há uma solicitação do <xliff:g id="APP_NAME_0">%1$s</xliff:g> para upload de um relatório de bug criado neste dispositivo no dia <xliff:g id="DATE">%2$s</xliff:g>, <xliff:g id="TIME">%3$s</xliff:g>. Os relatórios de bug incluem informações pessoais do seu dispositivo ou registradas por apps, como nomes de usuário, dados de local, identificadores do dispositivo e informações de rede. Por isso, só compartilhe relatórios de bug com pessoas e apps de sua confiança. Permitir que o <xliff:g id="APP_NAME_1">%4$s</xliff:g> faça upload do relatório de bug?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Ocorreu um erro ao processar o relatório de bug do app <xliff:g id="APP_NAME">%1$s</xliff:g>. Por isso, o compartilhamento dos dados detalhados de depuração foi negado. Lamentamos a interrupção."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Permitir"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Negar"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Permitir configurações restritas"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Configuração restrita"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Para sua segurança, essa configuração está indisponível no momento."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Não é possível realizar a ação durante uma ligação"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Esta configuração está bloqueada para proteger seu dispositivo e seus dados"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Golpistas podem pedir que você instale apps desconhecidos de uma nova fonte para tentar instalar apps nocivos."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Golpistas podem pedir que você autorize recursos de acessibilidade em um app para tentar controlar seu dispositivo."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Golpistas podem tentar danificar seu dispositivo com essa configuração."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"O app não recebeu a seguinte permissão de acesso: <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"O app solicitou acesso a uma permissão sensível que pode colocar suas informações pessoais e financeiras em risco.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>É possível que o app não funcione corretamente sem essa permissão restrita. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Aprenda a conceder acesso&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"O app não recebeu o acesso para ser a escolha padrão de: <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-ro-v33/strings.xml b/PermissionController/res/values-ro-v33/strings.xml
index a32a32db5..7eae996fa 100644
--- a/PermissionController/res/values-ro-v33/strings.xml
+++ b/PermissionController/res/values-ro-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Mai multe alerte"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Alerte respinse"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Extinde și mai vezi o alertă}few{Extinde și mai vezi # alerte}other{Extinde și mai vezi # de alerte}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Restrânge"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alertă. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Acțiune încheiată"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Verifică setările care pot spori protecția dispozitivului"</string>
diff --git a/PermissionController/res/values-ro/strings.xml b/PermissionController/res/values-ro/strings.xml
index 5f88c1376..34e100173 100644
--- a/PermissionController/res/values-ro/strings.xml
+++ b/PermissionController/res/values-ro/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Acces de <xliff:g id="PERM">%1$s</xliff:g> pentru această aplicație pe <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Vezi toate permisiunile aplicației <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Vezi toate aplicațiile cu această permisiune"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informații"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Setări"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Afișează datele de utilizare a microfonului cu Asistentul"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Setările aplicațiilor nefolosite"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Elimină permisiunile dacă aplicația nu este folosită"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Deschiderea linkurilor"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Prestabilite pentru serviciu"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Prestabilit pentru spațiul privat"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizate pentru dispozitiv"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Altele"</string>
<string name="default_app_none" msgid="9084592086808194457">"Niciuna"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Prestabilită de sistem)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nicio aplicație"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> dorește să încarce informațiile despre remedierea erorilor."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Trimiți datele despre remedierea erorilor?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Sistemul a detectat o problemă."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> solicită încărcarea unui raport de erori de pe acest dispozitiv creat în data de <xliff:g id="DATE">%2$s</xliff:g> la <xliff:g id="TIME">%3$s</xliff:g>. Rapoartele de erori conțin informații cu caracter personal despre dispozitiv sau înregistrate de aplicații, de exemplu: nume de utilizator, date privind locațiile, identificatori ai dispozitivului și informații despre rețea. Trimite rapoarte de erori doar persoanelor și aplicațiilor de încredere. Permiți ca <xliff:g id="APP_NAME_1">%4$s</xliff:g> să încarce un raport de erori?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> solicită încărcarea unui raport de eroare de pe acest dispozitiv creat în data de <xliff:g id="DATE">%2$s</xliff:g> la <xliff:g id="TIME">%3$s</xliff:g>. Rapoartele de erori conțin informații cu caracter personal despre dispozitiv sau înregistrate de aplicații, de exemplu: nume de utilizator, date privind locațiile, identificatori ai dispozitivului și informații despre rețea. Trimite rapoarte de erori doar persoanelor și aplicațiilor de încredere. Permiți ca <xliff:g id="APP_NAME_1">%4$s</xliff:g> să încarce un raport de eroare?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"A apărut o eroare la procesarea raportului de eroare pentru <xliff:g id="APP_NAME">%1$s</xliff:g>. Astfel, accesul la datele detaliate de remedierea erorilor a fost refuzat. Ne cerem scuze pentru întrerupere."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Permite"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Refuz"</string>
@@ -471,7 +475,7 @@
<string name="permgrouprequest_device_aware_storage_isolated" msgid="6463062962458809752">"Permiți accesul &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; la fotografiile și conținutul media de pe &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_contacts" msgid="8391550064551053695">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să-ți acceseze agenda?"</string>
<string name="permgrouprequest_device_aware_contacts" msgid="731025863972535928">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să acceseze agenda ta de pe &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgrouprequest_location" msgid="6990232580121067883">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să acceseze locația acestui dispozitiv?"</string>
+ <string name="permgrouprequest_location" msgid="6990232580121067883">"Permiți accesarea de către &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; a locației acestui dispozitiv?"</string>
<string name="permgrouprequest_device_aware_location" msgid="6075412127429878638">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să acceseze locația dispozitivului &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequestdetail_location" msgid="2635935335778429894">"Aplicația va avea acces la locație doar când o folosești"</string>
<string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să acceseze locația acestui dispozitiv?"</string>
@@ -525,7 +529,7 @@
<string name="permgroupupgraderequestdetail_camera" msgid="6642747548010962597">"Aplicația dorește să fotografieze și să înregistreze videoclipuri permanent, chiar și când nu o folosești. "<annotation id="link">"Acordă această permisiune din setări."</annotation></string>
<string name="permgrouprequest_calllog" msgid="2065327180175371397">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să-ți acceseze jurnalele de apeluri?"</string>
<string name="permgrouprequest_device_aware_calllog" msgid="8220927190376843309">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să-ți acceseze jurnalele de apeluri telefonice de pe &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgrouprequest_phone" msgid="1829234136997316752">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să dea și să gestioneze apeluri telefonice?"</string>
+ <string name="permgrouprequest_phone" msgid="1829234136997316752">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să și să gestioneze apeluri telefonice?"</string>
<string name="permgrouprequest_device_aware_phone" msgid="590399263670349955">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să inițieze și să gestioneze apeluri telefonice pe &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_sensors" msgid="4397358316850652235">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să acceseze datele de la senzori despre semnele vitale?"</string>
<string name="permgrouprequest_device_aware_sensors" msgid="3874451050573615157">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să acceseze datele de la senzori despre semnele tale vitale de pe &lt;b&gt;<xliff:g id="DEVICE_NAME">%2$s</xliff:g>&lt;/b&gt;?"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Permite setările restricționate"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Setare restricționată"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Pentru securitatea ta, setarea este momentan indisponibilă."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Nu se poate finaliza acțiunea în timpul apelului"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Această setare este blocată pentru a-ți proteja dispozitivul și datele"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Escrocii pot încerca să instaleze aplicații dăunătoare cerându-ți să instalezi aplicații necunoscute dintr-o sursă nouă."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Escrocii pot încerca să preia controlul asupra dispozitivului tău cerându-ți să permiți accesul la funcții de accesibilitate pentru o aplicație."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Escrocii pot încerca să-ți influențeze dispozitivul cu această setare."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Accesul aplicației la <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> a fost refuzat"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Aplicația a solicitat acces la o permisiune de accesare a informațiilor sensibile care îți poate pune în pericol informațiile financiare și cu caracter personal.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Este posibil ca aplicația să nu funcționeze corect fără această permisiune restricționată. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Află cum să permiți accesul&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Accesul aplicației pentru a deveni <xliff:g id="ROLE_NAME">%1$s</xliff:g> prestabilită a fost refuzat"</string>
diff --git a/PermissionController/res/values-ru-v33/strings.xml b/PermissionController/res/values-ru-v33/strings.xml
index a13e4e8d5..5506ccd97 100644
--- a/PermissionController/res/values-ru-v33/strings.xml
+++ b/PermissionController/res/values-ru-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Другие оповещения"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Закрытые оповещения"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Разверните, чтобы увидеть ещё одно оповещение}one{Разверните, чтобы увидеть ещё # оповещение}few{Разверните, чтобы увидеть ещё # оповещения}many{Разверните, чтобы увидеть ещё # оповещений}other{Разверните, чтобы увидеть ещё # оповещения}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Свернуть"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Оповещение. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Действие выполнено"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Изучите настройки, позволяющие усилить защиту устройства"</string>
@@ -40,7 +41,7 @@
<string name="safety_center_qs_privacy_control" msgid="1160682635058529673">"Переключатель: <xliff:g id="PRIVACY_CONTROL_TITLE">%1$s</xliff:g> (<xliff:g id="PRIVACY_CONTROL_STATUS">%2$s</xliff:g>)"</string>
<string name="safety_center_qs_toggle_action" msgid="5920465736488119255">"Переключатель"</string>
<string name="safety_center_qs_open_action" msgid="2760200829912423728">"Открыть"</string>
- <string name="safety_center_review_settings_button" msgid="938981137942443930">"Перейти в настройки"</string>
+ <string name="safety_center_review_settings_button" msgid="938981137942443930">"Открыть настройки"</string>
<string name="safety_center_gear_label" msgid="5175877094379694098">"Настройки"</string>
<string name="safety_center_info_label" msgid="8993181584061825412">"Информация"</string>
</resources>
diff --git a/PermissionController/res/values-ru/strings.xml b/PermissionController/res/values-ru/strings.xml
index a96a76df3..1fef844fe 100644
--- a/PermissionController/res/values-ru/strings.xml
+++ b/PermissionController/res/values-ru/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Разрешение \"<xliff:g id="PERM">%1$s</xliff:g>\" для этого приложения на устройстве \"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>\""</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Все разрешения приложения \"<xliff:g id="APP">%1$s</xliff:g>\""</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Все приложения с этим разрешением"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Информация"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Настройки"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Показывать сведения об использовании микрофона Ассистентом"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Настройки неиспользуемых приложений"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Отзывать разрешения, если приложение не используется"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Переход по ссылкам"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Стандартные для работы"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Приложения по умолчанию для личного пространства"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Оптимизированные для устройства"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Прочие"</string>
<string name="default_app_none" msgid="9084592086808194457">"Нет"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(по умолчанию)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Приложений нет"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" запрашивает разрешение на загрузку данных об отладке."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Отправить данные об отладке?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"В системе обнаружена проблема"</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"Приложение \"<xliff:g id="APP_NAME_0">%1$s</xliff:g>\" запрашивает ваше согласие на загрузку с этого устройства отчета об ошибке, созданного <xliff:g id="DATE">%2$s</xliff:g> в <xliff:g id="TIME">%3$s</xliff:g>. Отчет может содержать персональную информацию с устройства или из установленных приложений, например имена пользователей, сведения о местоположении, идентификаторы устройства и данные сети. Отправляйте отчеты об ошибке только тем приложениям и пользователям, которым доверяете. Разрешить приложению \"<xliff:g id="APP_NAME_1">%4$s</xliff:g>\" загрузить отчет об ошибке?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"Приложение \"<xliff:g id="APP_NAME_0">%1$s</xliff:g>\" запрашивает разрешение загрузить с этого устройства отчет об ошибке, созданный <xliff:g id="DATE">%2$s</xliff:g> в <xliff:g id="TIME">%3$s</xliff:g>. Отчет может содержать персональную информацию об устройстве или сведения, сохраненные приложениями, например имена пользователей, геоданные, идентификаторы устройства и информацию о сетях. Отправляйте отчеты об ошибке только тем приложениям и пользователям, которым доверяете. Разрешить приложению \"<xliff:g id="APP_NAME_1">%4$s</xliff:g>\" загрузить отчет об ошибке?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Не удалось обработать информацию об ошибке в приложении \"<xliff:g id="APP_NAME">%1$s</xliff:g>\". Данные об отладке не отправлены. Приносим извинения за неудобства."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Разрешить"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Запретить"</string>
@@ -674,10 +678,15 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Разрешить доступ к настройкам"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Настройки с ограниченным доступом"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"В целях безопасности эти настройки пока недоступны."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Нельзя выполнить действие во время звонка"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\nФункция заблокирована для защиты устройства и данных."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Мошенники могут попытаться установить вредоносное ПО, попросив вас установить незнакомые приложения из ненадежных источников."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Мошенники могут попытаться завладеть устройством, попросив вас включить специальные возможности для приложения."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Мошенники могут попытаться нанести вред устройству, используя эту функцию."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Для приложения заблокировано разрешение \"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>\""</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Приложение запрашивает разрешение на доступ к конфиденциальной информации. Если вы предоставите его, ваши личные и финансовые данные могут оказаться под угрозой.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Без такого разрешения приложение может работать неправильно. Узнайте, &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;как предоставить доступ к данным&lt;/a&gt;."</string>
- <string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Запрещено использовать приложение в качестве стандартного в категории \"<xliff:g id="ROLE_NAME">%1$s</xliff:g>\""</string>
- <string name="enhanced_confirmation_dialog_desc_role" msgid="6369601947905234551">"Приложение запрашивает разрешения на доступ к конфиденциальной информации. Если вы предоставите их, ваши личные и финансовые данные могут оказаться под угрозой.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Без таких разрешений приложение может работать неправильно. Узнайте, &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;как предоставить доступ к данным&lt;/a&gt;."</string>
+ <string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Запрос на использование в качестве приложения по умолчанию (<xliff:g id="ROLE_NAME">%1$s</xliff:g>) отклонен"</string>
+ <string name="enhanced_confirmation_dialog_desc_role" msgid="6369601947905234551">"Приложение запросило разрешения на доступ к конфиденциальной информации. Такие разрешения могут поставить ваши личные и финансовые данные под угрозу.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Без них приложение может работать неправильно. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Как предоставить доступ к данным.&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_settings_default" msgid="1858092969721041576">"Доступ запрещен"</string>
<string name="enhanced_confirmation_dialog_desc_settings_default" msgid="6911632348359332981">"Если вы предоставите это разрешение, ваши личные и финансовые данные могут оказаться под угрозой.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Без него приложение может работать неправильно. Узнайте, &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;как предоставить доступ к данным&lt;/a&gt;."</string>
<string name="enhanced_confirmation_dialog_learn_more" msgid="5226619861379095709">"Подробнее"</string>
diff --git a/PermissionController/res/values-si-v33/strings.xml b/PermissionController/res/values-si-v33/strings.xml
index b3d1ab958..f83695a16 100644
--- a/PermissionController/res/values-si-v33/strings.xml
+++ b/PermissionController/res/values-si-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"තවත් ඇඟවීම්"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"අස් කළ ඇඟවීම්"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{පුළුල් කර තවත් එක් ඇඟවීමක් බලන්න}one{පුළුල් කර තවත් ඇඟවීම් #ක් බලන්න}other{පුළුල් කර තවත් ඇඟවීම් #ක් බලන්න}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"හකුළන්න"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"ඇඟවීම. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"ක්‍රියාමාර්ගය සම්පූර්ණයි"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"ඔබේ උපාංගයට ආරක්ෂණය එක් කළ හැකි සැකසීම් පරීක්ෂා කරන්න"</string>
diff --git a/PermissionController/res/values-si/strings.xml b/PermissionController/res/values-si/strings.xml
index fbec1af4f..bb8239cb6 100644
--- a/PermissionController/res/values-si/strings.xml
+++ b/PermissionController/res/values-si/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> මත මෙම යෙදුම සඳහා <xliff:g id="PERM">%1$s</xliff:g> ප්‍රවේශය"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"සියලුම <xliff:g id="APP">%1$s</xliff:g> අවසර බලන්න"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"මෙම අවසරය සහිත සියලුම යෙදුම් බලන්න"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"තොරතුරු"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"සැකසීම්"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"සහායක මයික්‍රෆෝන භාවිතය පෙන්වන්න"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"භාවිත නොකළ යෙදුම් සැකසීම්"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"යෙදුම භාවිත කර නැති නම් අවසර ඉවත් කරන්න"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"සබැඳි විවෘත කිරීම"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"වැඩ සඳහා පෙරනිමි"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"රහසිගත අවකාශය සඳහා පෙරනිමිය"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"උපාංගය සඳහා ප්‍රශස්ත කර ඇත"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"වෙනත්"</string>
<string name="default_app_none" msgid="9084592086808194457">"කිසිවක් නැත"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(පද්ධතිය පෙරනිමි)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"යෙදුම් නොමැත"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"සීමා කළ සැකසීම්වලට ඉඩ දෙන්න"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"සීමා කළ සැකසීම"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"ඔබේ ආරක්ෂාව සඳහා, මෙම සැකසීම දැනට නොමැත."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"ඇමතුම අතරතුර ක්‍රියාව සම්පූර්ණ කළ නොහැක."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n ඔබේ උපාංගය සහ දත්ත ආරක්ෂා කිරීමට මෙම සැකසුම අවහිර කර ඇත."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"වංචාකරුවන් නව මූලාශ්‍රයකින් නොදන්නා යෙදුම් ස්ථාපනය කරන ලෙස ඉල්ලා සිටීමෙන් හානිකර යෙදුම් ස්ථාපනය කිරීමට උත්සාහ කළ හැක."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"වංචාකරුවන් යෙදුමක් සඳහා ප්‍රවේශ්‍යතා ප්‍රවේශයට ඉඩ දෙන ලෙස ඉල්ලා සිටීමෙන් ඔබේ උපාංගයේ පාලනය ලබා ගැනීමට උත්සාහ කළ හැක."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"මෙම සැකසීම සමගින් වංචාකරුවන් ඔබේ උපාංගයට හානි කිරීමට උත්සාහ කළ හැක."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"යෙදුම <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> වෙත ප්‍රවේශය ප්‍රතික්ෂේප කරන ලදි"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"යෙදුම ඔබේ පුද්ගලික සහ මූල්‍ය තොරතුරු අවදානමට ලක් කළ හැකි සංවේදී අවසරයකට ප්‍රවේශය ඉල්ලා ඇත.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>මෙම සීමා කළ අවසරය නොමැතිව යෙදුම නිසි ලෙස ක්‍රියා නොකරනු ඇත. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;ප්‍රවේශයට ඉඩ දෙන ආකාරය දැන ගන්න&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"යෙදුම පෙරනිමි <xliff:g id="ROLE_NAME">%1$s</xliff:g> වීමට ප්‍රවේශය ප්‍රතික්ෂේප කරන ලදි"</string>
diff --git a/PermissionController/res/values-sk-v33/strings.xml b/PermissionController/res/values-sk-v33/strings.xml
index f36fd1089..3024de58b 100644
--- a/PermissionController/res/values-sk-v33/strings.xml
+++ b/PermissionController/res/values-sk-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Ďalšie upozornenia"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Odmietnuté upozornenia"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Po rozbalení sa zobrazí jedno ďalšie upozornenie}few{Po rozbalení sa zobrazia # ďalšie upozornenia}many{Expand and see # more alerts}other{Po rozbalení sa zobrazí # ďalších upozornení}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Zbaliť"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Upozornenie. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Akcia je dokončená"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Skontrolujte nastavenia, ktoré môžu pridať ochranu do vášho zariadenia"</string>
diff --git a/PermissionController/res/values-sk-watch/strings.xml b/PermissionController/res/values-sk-watch/strings.xml
index 1a7e89701..52503904f 100644
--- a/PermissionController/res/values-sk-watch/strings.xml
+++ b/PermissionController/res/values-sk-watch/strings.xml
@@ -23,10 +23,10 @@
<string name="generic_yes" msgid="2489207724988649846">"Áno"</string>
<string name="generic_cancel" msgid="2631708607129269698">"Zrušiť"</string>
<string name="permission_access_always" msgid="2107115233573823032">"Po celý čas"</string>
- <string name="permission_access_only_foreground" msgid="4412115020089923986">"Pri používaní"</string>
+ <string name="permission_access_only_foreground" msgid="4412115020089923986">"Pri používaní aplikácie"</string>
<string name="app_permission_button_allow_always" msgid="4920899432212307102">"Po celý čas"</string>
- <string name="app_permission_button_allow_foreground" msgid="7186980598244864830">"Pri používaní"</string>
+ <string name="app_permission_button_allow_foreground" msgid="7186980598244864830">"Pri používaní aplikácie"</string>
<string name="grant_dialog_button_allow_always" msgid="7130695257254694576">"Po celý čas"</string>
- <string name="grant_dialog_button_allow_foreground" msgid="8917595344037255090">"Pri používaní"</string>
+ <string name="grant_dialog_button_allow_foreground" msgid="8917595344037255090">"Pri používaní aplikácie"</string>
<string name="grant_dialog_button_allow_background" msgid="6104993390936535493">"Po celý čas"</string>
</resources>
diff --git a/PermissionController/res/values-sk/strings.xml b/PermissionController/res/values-sk/strings.xml
index 2b0c845c5..92dccece6 100644
--- a/PermissionController/res/values-sk/strings.xml
+++ b/PermissionController/res/values-sk/strings.xml
@@ -74,7 +74,7 @@
<string name="days_ago" msgid="6650359081551335629">"{count,plural, =0{Dnes}=1{Pred 1 dňom}few{Pred # dňami}many{Pred # dňa}other{Pred # dňami}}"</string>
<string name="app_disable_dlg_positive" msgid="7418444149981904940">"Deaktivovať aplikáciu"</string>
<string name="app_disable_dlg_text" msgid="3126943217146120240">"Ak aplikáciu deaktivujete, systém a ďalšie aplikácie už nemusia fungovať podľa očakávaní. Upozorňujeme, že túto aplikáciu nemôžete odstrániť, pretože bola vo vašom zariadení predinštalovaná. Deaktiváciou ju vypnete a skryjete v zariadení."</string>
- <string name="app_permission_manager" msgid="3903811137630909550">"Správca povolení"</string>
+ <string name="app_permission_manager" msgid="3903811137630909550">"Správa povolení"</string>
<string name="never_ask_again" msgid="4728762438198560329">"Nabudúce sa nepýtať"</string>
<string name="no_permissions" msgid="3881676756371148563">"Žiadne povolenia"</string>
<string name="additional_permissions" msgid="5801285469338873430">"Ďalšie povolenia"</string>
@@ -86,7 +86,7 @@
<string name="default_permission_description" msgid="4624464917726285203">"umožňuje vykonať neznámu akciu"</string>
<string name="app_permissions_group_summary" msgid="8788419008958284002">"Povolené pri <xliff:g id="COUNT_0">%1$d</xliff:g> z(o) <xliff:g id="COUNT_1">%2$d</xliff:g> aplik."</string>
<string name="app_permissions_group_summary2" msgid="4329922444840521150">"Počet povolených aplikácií: <xliff:g id="COUNT_0">%1$d</xliff:g>/<xliff:g id="COUNT_1">%2$d</xliff:g>"</string>
- <string name="menu_show_system" msgid="4254021607027872504">"Zobraziť systémové"</string>
+ <string name="menu_show_system" msgid="4254021607027872504">"Zobraziť systémové aplikácie"</string>
<string name="menu_hide_system" msgid="3855390843744028465">"Skryť systémové aplikácie"</string>
<string name="menu_show_7_days_data" msgid="8979611198508523706">"Zobraziť 7 dní"</string>
<string name="menu_show_24_hours_data" msgid="8228054833323380780">"Zobraziť 24 hodín"</string>
@@ -202,11 +202,13 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Prístup k položke <xliff:g id="PERM">%1$s</xliff:g> pre túto aplikáciu v zariadení <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Zobraziť všetky povolenia aplikácie <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Zobraziť všetky aplikácie s týmto povolením"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informácia"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Nastavenia"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Zobraziť používanie mikrofónu Asistentom"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Nastavenia nepoužívaných aplikácií"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Odstrániť povolenia, ak sa aplikácia nepoužíva"</string>
<string name="unused_apps_label" msgid="2595428768404901064">"Odstraňovať povol. a uvoľňovať priestor"</string>
- <string name="unused_apps_label_v2" msgid="7058776770056517980">"Pozastaviť aktivitu v nepoužívaných apl."</string>
+ <string name="unused_apps_label_v2" msgid="7058776770056517980">"Pozastaviť aktivitu pri nepoužívaní"</string>
<string name="unused_apps_label_v3" msgid="693340578642156657">"Spravovať aplikáciu, ak sa nepoužíva"</string>
<string name="unused_apps_summary" msgid="8839466950318403115">"Odstrániť povolenia, vymazať dočasné súbory a zastaviť upozornenia"</string>
<string name="unused_apps_summary_v2" msgid="5011313200815115802">"Odstrániť povolenia, vymazať dočasné súbory, zastaviť upozornenia a archivovať aplikáciu"</string>
@@ -438,7 +440,9 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Otváranie odkazov"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Predvolené na prácu"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Predvolené pre súkromný priestor"</string>
- <string name="default_app_none" msgid="9084592086808194457">"Žiadna"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimalizované pre zariadenie"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Iné"</string>
+ <string name="default_app_none" msgid="9084592086808194457">"Žiadna aplikácia"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Predvolené systémom)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Žiadne aplikácie"</string>
<string name="default_payment_app_other_nfc_services" msgid="5957633798695758917">"Ďalšie služby NFC"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Povoliť obmedzené nastavenia"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Obmedzené nastavenie"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Z bezpečnostných dôvodov nie je toto nastavenie momentálne k dispozícii."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Počas hovoru sa akcia nedá dokončiť"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Toto nastavenie je zablokované, aby sa zaistila ochrana vášho zariadenia a údajov"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Podvodníci sa môžu pokúsiť nainštalovať škodlivé aplikácie tým, že vás požiadajú o inštaláciu neznámych aplikácií z nového zdroja."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Podvodníci sa môžu pokúsiť získať kontrolu nad vaším zariadením tým, že vás v prípade nejakej aplikácie požiadajú o povolenie prístupu k dostupnosti."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Podvodníci sa môžu pomocou tohto nastavenia pokúsiť poškodiť vaše zariadenie."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Aplikácii bol prístup k povoleniu <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> zamietnutý"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Táto aplikácia vyžaduje prístup k citlivému povoleniu, čo môže ohroziť vaše osobné údaje a finančné informácie.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Je možné, že bez tohto obmedzeného povolenia nebude aplikácia správne fungovať. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Ako povoliť prístup&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikácii bol zamietnutý prístup a predvolene nemôže byť <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-sl-v33/strings.xml b/PermissionController/res/values-sl-v33/strings.xml
index 6bc0748fb..9e1e7a979 100644
--- a/PermissionController/res/values-sl-v33/strings.xml
+++ b/PermissionController/res/values-sl-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Več opozoril"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Opuščena opozorila"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Razširi in prikaži še eno opozorilo}one{Razširi in prikaži še # opozorilo}two{Razširi in prikaži še # opozorili}few{Razširi in prikaži še # opozorila}other{Razširi in prikaži še # opozoril}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Strni"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Opozorilo. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Dejanje končano"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Preverite nastavitve, ki lahko izboljšajo varnost naprave."</string>
diff --git a/PermissionController/res/values-sl-watch/strings.xml b/PermissionController/res/values-sl-watch/strings.xml
index f93ba26b5..3f78007ac 100644
--- a/PermissionController/res/values-sl-watch/strings.xml
+++ b/PermissionController/res/values-sl-watch/strings.xml
@@ -21,7 +21,7 @@
<string name="preference_show_system_apps" msgid="1055740303992024300">"Prikaz sistemskih aplikacij"</string>
<string name="permission_summary_enforced_by_policy" msgid="2352478756952948019">"Ni mogoče sprem."</string>
<string name="generic_yes" msgid="2489207724988649846">"Da"</string>
- <string name="generic_cancel" msgid="2631708607129269698">"Prekliči"</string>
+ <string name="generic_cancel" msgid="2631708607129269698">"Preklic"</string>
<string name="permission_access_always" msgid="2107115233573823032">"Ves čas"</string>
<string name="permission_access_only_foreground" msgid="4412115020089923986">"Med uporabo aplikacije"</string>
<string name="app_permission_button_allow_always" msgid="4920899432212307102">"Ves čas"</string>
diff --git a/PermissionController/res/values-sl/strings.xml b/PermissionController/res/values-sl/strings.xml
index 0e83c0d1f..103ffbee7 100644
--- a/PermissionController/res/values-sl/strings.xml
+++ b/PermissionController/res/values-sl/strings.xml
@@ -202,11 +202,13 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Dostop do dovoljenja »<xliff:g id="PERM">%1$s</xliff:g>« za to aplikacijo v napravi <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Ogled vseh dovoljenj za aplikacijo <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Ogled vseh aplikacij s tem dovoljenjem"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informacije"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Nastavitve"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Prikaz uporabe pomožnega mikrofona"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Nastavitve neuporabljenih aplikacij"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Odstrani dovoljenja, če aplikacija ni v uporabi"</string>
<string name="unused_apps_label" msgid="2595428768404901064">"Odstrani dovoljenja in sprosti prostor"</string>
- <string name="unused_apps_label_v2" msgid="7058776770056517980">"Zaustavi dejavnost aplikacije ob neuporabi"</string>
+ <string name="unused_apps_label_v2" msgid="7058776770056517980">"Zaustavi aplikacijo ob neuporabi"</string>
<string name="unused_apps_label_v3" msgid="693340578642156657">"Upravljanje aplikacije ob neuporabi"</string>
<string name="unused_apps_summary" msgid="8839466950318403115">"Dovoljenja se odstranijo, začasne datoteke se izbrišejo in prikazovanje obvestil se ustavi."</string>
<string name="unused_apps_summary_v2" msgid="5011313200815115802">"Odstranitev dovoljenj, izbris začasnih datotek, ustavitev prikazovanja obvestil in arhiviranje aplikacije"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Odpiranje povezav"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Privzeto za delo"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Privzeto za zasebni prostor"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizirano za napravo"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Drugo"</string>
<string name="default_app_none" msgid="9084592086808194457">"Brez"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(privzeta v sistemu)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Ni aplikacij"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Dovoli omejene nastavitve"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Omejena nastavitev"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Zaradi vaše varnosti ta nastavitev trenutno ni na voljo."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Dejanja ni mogoče izvesti med klicem"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Ta nastavitev je blokirana zaradi zaščite naprave in podatkov"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Goljufi lahko poskusijo namestiti škodljive aplikacije tako, da vas prosijo, da namestite neznane aplikacije iz novega vira."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Goljufi lahko poskusijo prevzeti nadzor nad vašo napravo tako, da vas prosijo, da omogočite dostop do funkcij dostopnosti za aplikacijo."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Goljufi lahko s to nastavitvijo poskusijo škodovati vaši napravi."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Aplikaciji je bil zavrnjen dostop do dovoljenja »<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>«"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Aplikacija je zahtevala dostop do občutljivega dovoljenja, ki lahko ogrozi vaše osebne in finančne podatke.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Aplikacija morda ne bo pravilno delovala brez tega omejenega dovoljenja. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Preberite, kako omogočite dostop&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikaciji je bil zavrnjen dostop do privzete vloge »<xliff:g id="ROLE_NAME">%1$s</xliff:g>«"</string>
diff --git a/PermissionController/res/values-sq-v33/strings.xml b/PermissionController/res/values-sq-v33/strings.xml
index bbe8e9a89..038dc8d39 100644
--- a/PermissionController/res/values-sq-v33/strings.xml
+++ b/PermissionController/res/values-sq-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Sinjalizime të tjera"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Sinjalizime të shpërfillura"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Zgjero dhe shiko një sinjalizim tjetër}other{Zgjero dhe shiko # sinjalizime të tjera}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Palos"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Sinjalizim. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Veprimi përfundoi"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Shiko cilësimet që mund të shtojnë mbrojtjen e pajisjes sate"</string>
diff --git a/PermissionController/res/values-sq/strings.xml b/PermissionController/res/values-sq/strings.xml
index 4cca434cb..cfaa864a8 100644
--- a/PermissionController/res/values-sq/strings.xml
+++ b/PermissionController/res/values-sq/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Qasja te <xliff:g id="PERM">%1$s</xliff:g> për këtë aplikacion në <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Shiko të gjitha lejet e aplikacionit \"<xliff:g id="APP">%1$s</xliff:g>\""</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Shiko të gjitha aplikacionet me këtë leje"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Informacione"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Cilësimet"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Shfaq përdorimin e mikrofonit të \"Asistentit\""</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Cilësimet e aplikacioneve të papërdorura"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Hiq lejet nëse aplikacioni nuk është përdorur"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Hapja e lidhjeve"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Të parazgjedhura për punën"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Të parazgjedhurat për hapësirën private"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimizuar për pajisjen"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Të tjera"</string>
<string name="default_app_none" msgid="9084592086808194457">"Asnjë"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Parazgjedhja e sistemit)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Nuk ka aplikacione"</string>
@@ -445,7 +449,7 @@
<string name="car_default_app_selected" msgid="5416420830430644174">"Zgjedhur"</string>
<string name="car_default_app_selected_with_info" msgid="1932204186080593500">"Zgjedhur - <xliff:g id="ADDITIONAL_INFO">%1$s</xliff:g>"</string>
<string name="special_app_access_search_keyword" msgid="8032347212290774210">"qasje e veçantë e aplikacionit"</string>
- <string name="special_app_access" msgid="5019319067120213797">"Qasje e veçantë aplikacioni"</string>
+ <string name="special_app_access" msgid="5019319067120213797">"Qasja e veçantë e apl."</string>
<string name="no_special_app_access" msgid="6950277571805106247">"Jo qasje e veçantë aplikacioni"</string>
<string name="special_app_access_no_apps" msgid="4102911722787886970">"Nuk ka aplikacione"</string>
<string name="home_missing_work_profile_support" msgid="1756855847669387977">"Profili i punës nuk mbështetet"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Lejo cilësimet e kufizuara"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Cilësim i kufizuar"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Për sigurinë tënde, ky cilësim nuk ofrohet për momentin."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Veprimi nuk mund të përfundohet gjatë telefonatës"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Ky cilësim është bllokuar për të mbrojtur pajisjen dhe të dhënat e tua"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Mashtruesit mund të përpiqen të instalojnë aplikacione të dëmshme duke të të kërkuar që të instalosh aplikacione të panjohura nga një burim i ri."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Mashtruesit mund të përpiqen të marrin kontrollin e pajisjes sate duke të të kërkuar që të lejosh qasjen për shërbimin e qasshmërisë për një aplikacion."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Mashtruesit mund të përpiqen të dëmtojnë pajisjen tënde me këtë cilësim."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Aplikacionit iu refuzua qasja te: <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Aplikacioni kërkoi qasje në një leje delikate që mund t\'i vendosë në rrezik informacionet e tua personale dhe financiare.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Ka mundësi që aplikacioni të mos funksionojë si duhet pa këtë leje të kufizuar. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Mëso se si të lejosh qasjen&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Aplikacionit iu refuzua qasja për të qenë parazgjedhja për: <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-sr-v33/strings.xml b/PermissionController/res/values-sr-v33/strings.xml
index 1fe22cee5..d778b2a31 100644
--- a/PermissionController/res/values-sr-v33/strings.xml
+++ b/PermissionController/res/values-sr-v33/strings.xml
@@ -30,9 +30,10 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Још обавештења"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Одбачена обавештења"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Проширите и видите још једно обавештење}one{Проширите и видите још # обавештење}few{Проширите и видите још # обавештења}other{Проширите и видите још # обавештења}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Скупи"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Обавештење. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Радња је довршена"</string>
- <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Проверите подешавања која могу да додају заштиту уређају"</string>
+ <string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Проверите подешавања која могу да допринесу бољој заштити уређаја"</string>
<string name="safety_center_qs_page_landing" msgid="1717368301679228128">"Брза подешавања безбедности и приватности"</string>
<string name="safety_center_qs_close_button" msgid="1352313308176244599">"Затвори"</string>
<string name="safety_center_qs_expand_action" msgid="2193190557696484169">"Прошири и прикажи опције"</string>
diff --git a/PermissionController/res/values-sr/strings.xml b/PermissionController/res/values-sr/strings.xml
index d89ebba68..9b8d404f8 100644
--- a/PermissionController/res/values-sr/strings.xml
+++ b/PermissionController/res/values-sr/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Ова апликација има приступ за: <xliff:g id="PERM">%1$s</xliff:g> на уређају <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Прикажи све дозволе за: <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Прикажи све апликације са овом дозволом"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Информације"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Подешавања"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Прикажи како Помоћник користи микрофон"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Подешавања некоришћених апликација"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Уклони дозволе ако се апликација не користи"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Отварање линкова"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Подразумевана за посао"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Подразумевано за приватан простор"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Оптимизовано за уређај"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Друго"</string>
<string name="default_app_none" msgid="9084592086808194457">"Ништа"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Подразумевана системска)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Нема апликација"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> жели да отпреми информације за отклањање грешака."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Желите да делите податке о отклањању грешака?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Систем је открио проблем."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> тражи да отпреми извештај о грешкама са овог уређаја који је направљен <xliff:g id="DATE">%2$s</xliff:g> у <xliff:g id="TIME">%3$s</xliff:g>. Извештаји о грешкама обухватају личне податке о уређају или податке које су евидентирале апликације, на пример, корисничка имена, податке о локацији, идентификаторе уређаја и информације о мрежи. Делите извештаје о грешкама само са људима и апликацијама којима можете да поверите те информације. Желите ли да дозволите да <xliff:g id="APP_NAME_1">%4$s</xliff:g> отпреми извештај о грешци?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> тражи да отпреми извештај о грешкама са овог уређаја који је направљен <xliff:g id="DATE">%2$s</xliff:g> у <xliff:g id="TIME">%3$s</xliff:g>. Извештаји о грешкама обухватају личне податке о уређају или податке које су евидентирале апликације, на пример, корисничка имена, податке о локацији, идентификаторе уређаја и информације о мрежи. Делите извештаје о грешкама само са људима и апликацијама које сматрате поузданим за те информације. Желите ли да дозволите да <xliff:g id="APP_NAME_1">%4$s</xliff:g> отпреми извештај о грешци?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Дошло је до грешке при обради извештаја о грешци за апликацију <xliff:g id="APP_NAME">%1$s</xliff:g>. Зато је одбијено дељење детаљних података о отклањању грешака. Извињавамо се због прекида."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Дозволи"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Одбиј"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Дозволи ограничена подешавања"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Ограничено подешавање"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Ово подешавање је тренутно недоступно ради ваше безбедности."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Радња не може да се заврши током позива"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Ово подешавање је блокирано да би се заштитили уређај и подаци"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Преваранти могу да покушају да инсталирају штетне апликације тако што ће тражити да инсталирате непознате апликације из новог извора."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Преваранти могу да покушају да преузму контролу над уређајем тако што ће тражити да за апликацију дозволите приступ за приступачност."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Преваранти могу да покушају да нашкоде уређају помоћу овог подешавања."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Апликацији није дозвољен приступ дозволи: <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Апликација је затражила приступ осетљивој дозволи, што може да угрози безбедност личних и финансијских података.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Апликација можда неће радити исправно без ове ограничене дозволе. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Сазнајте како да дозволите приступ&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Апликацији није дозвољен приступ да постане подразумевана: <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-sv-v33/strings.xml b/PermissionController/res/values-sv-v33/strings.xml
index e661efeb0..dd7c6a7df 100644
--- a/PermissionController/res/values-sv-v33/strings.xml
+++ b/PermissionController/res/values-sv-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Fler varningar"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Ignorerade varningar"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Utöka för att visa en varning till}other{Utöka för att visa # varningar till}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Komprimera"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Varning. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Åtgärd slutförd"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Kontrollera inställningar som kan skydda enheten"</string>
diff --git a/PermissionController/res/values-sv/strings.xml b/PermissionController/res/values-sv/strings.xml
index c5d3969de..a8c90db6e 100644
--- a/PermissionController/res/values-sv/strings.xml
+++ b/PermissionController/res/values-sv/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Åtkomst till <xliff:g id="PERM">%1$s</xliff:g> för den här appen på <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Visa alla behörigheter för <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Visa alla appar med den här behörigheten"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Information"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Inställningar"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Visa mikrofonanvändning för assistenten"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Inställningar för appar som inte används"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Ta bort behörigheter om en app inte används"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Öppna länkar"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Standardinställning för jobbet"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Standard för privat utrymme"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Optimerade för enheten"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Andra"</string>
<string name="default_app_none" msgid="9084592086808194457">"Ingen"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(System­standard)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Inga appar"</string>
@@ -454,9 +458,9 @@
<string name="incident_report_channel_name" msgid="3144954065936288440">"Dela felsökningsinformation"</string>
<string name="incident_report_notification_title" msgid="4635984625656519773">"Vill du dela detaljerad felsökningsinformation?"</string>
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> vill ladda upp felsökningsinformation."</string>
- <string name="incident_report_dialog_title" msgid="669104389325204095">"Vill du dela felsökningsinformation?"</string>
+ <string name="incident_report_dialog_title" msgid="669104389325204095">"Vill du dela felsöknings­information?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Ett problem har upptäckts i systemet."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> begär tillstånd att ladda upp en felrapport som sparades den <xliff:g id="DATE">%2$s</xliff:g> kl. <xliff:g id="TIME">%3$s</xliff:g> från den här enheten. Felrapporter innehåller personlig information om enheten eller information som loggats av appar, t.ex. användarnamn, platsdata, enhetsidentifierare och nätverksinformation. Dela bara felrapporter med personer och appar du litar på. Tillåter du att <xliff:g id="APP_NAME_1">%4$s</xliff:g> laddar upp en felrapport?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> begär tillstånd att ladda upp en felrapport som sparades <xliff:g id="DATE">%2$s</xliff:g> kl. <xliff:g id="TIME">%3$s</xliff:g> från den här enheten. Felrapporter innehåller personlig information om enheten eller information som loggats av appar, t.ex. användarnamn, platsdata, enhetsidentifierare och nätverksinformation. Dela bara felrapporter med personer och appar du litar på. Tillåter du att <xliff:g id="APP_NAME_1">%4$s</xliff:g> laddar upp en felrapport?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Ett fel uppstod när felrapporten för <xliff:g id="APP_NAME">%1$s</xliff:g> skulle behandlas. Därför har delning av utförlig felsökningsdata nekats. Ursäkta avbrottet."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Tillåt"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Neka"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Tillåt blockerade inställningar"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Begränsad inställning"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Av säkerhetsskäl är den här inställningen inte tillgänglig för närvarande."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Det går inte att slutföra åtgärden under samtal"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Den här inställningen har blockerats för att skydda enheten och din data"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Bedragare kan försöka installera skadliga appar genom att be dig installera okända appar från en ny källa."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Bedragare kan försöka få kontroll över enheten genom att be dig att tillåta tillgänglighetsåtkomst för en app."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Bedragare kan försöka skada enheten med den här inställningen."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Appen nekades åtkomst till <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Appen begärde åtkomstbehörighet till känsliga uppgifter, vilket kan utsätta din personliga och finansiella information för risk.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Appen kanske inte fungerar som den ska utan denna begränsade behörighet. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&amp;gtLäs mer om hur du tillåter åtkomst&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Appen nekades åtkomst till standardrollen för <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-sw-v33/strings.xml b/PermissionController/res/values-sw-v33/strings.xml
index db02c110b..645c2a44f 100644
--- a/PermissionController/res/values-sw-v33/strings.xml
+++ b/PermissionController/res/values-sw-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Arifa zaidi"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Arifa zilizoondolewa"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Panua na uangalie arifa moja zaidi}other{Panua na uangalie arifa # zaidi}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Kunja"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Tahadhari. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Kitendo kimekamilika"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Angalia mipangilio inayoweza kuongeza usalama wa kifaa chako"</string>
diff --git a/PermissionController/res/values-sw/strings.xml b/PermissionController/res/values-sw/strings.xml
index e774d92c1..3dedd0126 100644
--- a/PermissionController/res/values-sw/strings.xml
+++ b/PermissionController/res/values-sw/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Ufikaiji wa <xliff:g id="PERM">%1$s</xliff:g> katika programu hii kwenye <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Angalia ruhusa zote za <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Angalia programu zote zenye ruhusa hii"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Maelezo"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Mipangilio"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Onyesha matumizi ya maikrofoni ya mratibu"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Mipangilio ya programu zisizotumika"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Ondoa ruhusa ikiwa programu haitumiki"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Kufungua viungo"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Programu chaguomsingi kazini"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Programu chaguomsingi za sehemu ya faragha"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Zilizoboreshwa ili kufaa kifaa chako"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Nyingine"</string>
<string name="default_app_none" msgid="9084592086808194457">"Hakuna"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Programu chaguomsingi ya mfumo)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Hakuna programu"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> inataka kupakia maelezo ya utatuzi."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Ungependa kushiriki data ya utatuzi?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Mfumo umetambua hitilafu."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> inaomba kupakia ripoti ya hitilafu kutoka kifaa hiki, iliyochakatwa tarehe <xliff:g id="DATE">%2$s</xliff:g> saa <xliff:g id="TIME">%3$s</xliff:g>. Ripoti za hitilafu hujumuisha taarifa binafsi kuhusu kifaa chako au iliyohifadhiwa na programu, kwa mfano, majina ya watumiaji, data ya mahali, vitambulishi vya vifaa na maelezo ya mtandao. Shiriki tu ripoti za hitilafu na watu au programu unazoamini kupokea maelezo haya. Ungependa kuruhusu <xliff:g id="APP_NAME_1">%4$s</xliff:g> ipakie ripoti ya hitilafu?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> inaomba kupakia ripoti ya hitilafu kutoka kifaa hiki, iliyochakatwa tarehe <xliff:g id="DATE">%2$s</xliff:g> saa <xliff:g id="TIME">%3$s</xliff:g>. Ripoti za hitilafu hujumuisha taarifa binafsi kuhusu kifaa chako au iliyohifadhiwa na programu, kwa mfano, majina ya watumiaji, data ya mahali, vitambulisho vya vifaa na maelezo ya mtandao. Tuma tu ripoti za hitilafu kwa watu au programu unazoamini. Ungependa kuruhusu <xliff:g id="APP_NAME_1">%4$s</xliff:g> ipakie ripoti ya hitilafu?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Hitilafu imetokea wakati wa kuchakata ripoti ya hitilafu ya <xliff:g id="APP_NAME">%1$s</xliff:g>. Kwa hivyo, kushiriki data ya maelezo ya kina ya utatuzi kumekataliwa. Samahani kwa usumbufu."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Ruhusu"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Kataa"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Ruhusu mipangilio yenye mipaka"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Mipangilio imezuiwa"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Ili kulinda usalama wako, mipangilio hii haipatikani kwa sasa."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Huwezi kukamilisha kitendo hiki simu inapopigwa"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Mipangilio hii imezuiwa ili kulinda kifaa na data yako"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Walaghai wanaweza kujaribu kuweka programu hatari kwenye kifaa chako kwa kukuomba uweke programu zisizojulikana kutoka chanzo kipya."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Walaghai wanaweza kujaribu kudhibiti kifaa chako kwa kukuomba uruhusu programu ifikie vipengele vya ufikivu."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Walaghai wanaweza kujaribu kuathiri kifaa chako kwa kutumia mipangilio hii."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Programu haijapewa idhini ya kufikia <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Programu imeomba kufikia ruhusa nyeti, hali ambayo inaweza kuhatarisha maelezo yako ya kifedha na ya binafsi.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Kuna uwezekano kuwa programu haitafanya kazi vizuri bila ruhusa hii inayodhibitiwa. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Pata maelezo kuhusu jinsi ya kuruhusu ufikiaji&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Programu haijapewa idhini ya kuwa <xliff:g id="ROLE_NAME">%1$s</xliff:g> chaguomsingi"</string>
diff --git a/PermissionController/res/values-ta-v33/strings.xml b/PermissionController/res/values-ta-v33/strings.xml
index 41b6d0923..b528e9bf6 100644
--- a/PermissionController/res/values-ta-v33/strings.xml
+++ b/PermissionController/res/values-ta-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"கூடுதல் விழிப்பூட்டல்கள்"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"நிராகரிக்கப்பட்ட விழிப்பூட்டல்கள்"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{விரிவாக்கி மேலும் ஒரு விழிப்பூட்டலைப் பாருங்கள்}other{விரிவாக்கி மேலும் # விழிப்பூட்டல்களைப் பாருங்கள்}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"சுருக்கு"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"விழிப்பூட்டும். <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"செயல் நிறைவடைந்தது"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"உங்கள் சாதனத்திற்குப் பாதுகாப்பைச் சேர்க்கக்கூடிய அமைப்புகளைப் பாருங்கள்"</string>
diff --git a/PermissionController/res/values-ta/strings.xml b/PermissionController/res/values-ta/strings.xml
index e4f158e6f..fa42187af 100644
--- a/PermissionController/res/values-ta/strings.xml
+++ b/PermissionController/res/values-ta/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> சாதனத்தில் இந்த ஆப்ஸுக்கான <xliff:g id="PERM">%1$s</xliff:g> அணுகல்"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"அனைத்து <xliff:g id="APP">%1$s</xliff:g> அனுமதிகளையும் காட்டு"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"இந்த அனுமதியைக் கொண்டுள்ள அனைத்து ஆப்ஸையும் காட்டு"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"தகவல்கள்"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"அமைப்புகள்"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"அசிஸ்டண்ட் மைக்ரோஃபோன் உபயோகத்தைக் காட்டு"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"பயன்படுத்தப்படாத ஆப்ஸ் அமைப்புகள்"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"இந்த ஆப்ஸ் பயன்படுத்தப்படவில்லை என்றால் அனுமதிகளை அகற்றவும்"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"இணைப்புகளைத் திறத்தல்"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"பணிக்கான இயல்பு நிலை ஆப்ஸ்"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"ரகசிய இடத்திற்கான இயல்பு"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"சாதனத்திற்காக மேம்படுத்தப்பட்டவை"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"மற்றவை"</string>
<string name="default_app_none" msgid="9084592086808194457">"ஏதுமில்லை"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(சிஸ்டத்தின் இயல்புநிலை)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"ஆப்ஸ் இல்லை"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"கட்டுப்படுத்தப்பட்ட அமைப்பை அனுமதி"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"கட்டுப்படுத்தப்பட்ட அமைப்பு"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"உங்கள் பாதுகாப்பிற்காக, இந்த அமைப்பு தற்போது இல்லை."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"அழைப்பின்போது செயலை நிறைவுசெய்ய முடியாது"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n உங்கள் சாதனத்தையும் தரவையும் பாதுகாப்பதற்காக இந்த அமைப்பு தடுக்கப்பட்டுள்ளது"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"புதிய மூலத்தில் இருந்து அறியப்படாத ஆப்ஸை நிறுவுமாறு கேட்பதன் மூலம் மோசடி செய்பவர்கள் தீங்கு விளைவிக்கும் ஆப்ஸை நிறுவ முயலலாம்."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"ஆப்ஸுக்கான அணுகல்தன்மை அம்சங்களை அனுமதிக்குமாறு கேட்பதன் மூலம் மோசடி செய்பவர்கள் உங்கள் சாதனத்தைக் கட்டுப்படுத்த முயலலாம்."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"இந்த அமைப்பு மூலம் மோசடி செய்பவர்கள் உங்கள் சாதனத்திற்குத் தீங்கு விளைவிக்க முயலலாம்."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>க்கான ஆப்ஸ் அணுகல் நிராகரிக்கப்பட்டது"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"பாதுகாக்க வேண்டிய தகவல்களுக்கான அனுமதியை வழங்க ஆப்ஸ் கேட்டுள்ளது, இது உங்கள் தனிப்பட்ட மற்றும் நிதித் தகவல்களை ஆபத்துக்குள்ளாக்கலாம்.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>இந்தக் கட்டுப்படுத்தப்பட்ட அனுமதி இல்லாமல் ஆப்ஸ் சரியாக வேலை செய்யாமல் போகக்கூடும். &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;எப்படி அணுகல் வழங்குவதென அறிக&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"இயல்புநிலை <xliff:g id="ROLE_NAME">%1$s</xliff:g>க்கான ஆப்ஸ் அணுகல் நிராகரிக்கப்பட்டது"</string>
diff --git a/PermissionController/res/values-te-v33/strings.xml b/PermissionController/res/values-te-v33/strings.xml
index 7245ac078..348553dd2 100644
--- a/PermissionController/res/values-te-v33/strings.xml
+++ b/PermissionController/res/values-te-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"మరిన్ని అలర్ట్‌లు"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"విస్మరించబడిన అలర్ట్‌లు"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{విస్తరించి, మరొక అలర్ట్‌ను చూడండి}other{విస్తరించి, మరో # అలర్ట్‌లను చూడండి}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"కుదించండి"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"అలర్ట్. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"చర్య పూర్తయింది"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"మీ పరికరానికి రక్షణను జోడించగల సెట్టింగ్‌లను చెక్ చేయండి"</string>
diff --git a/PermissionController/res/values-te/strings.xml b/PermissionController/res/values-te/strings.xml
index a461319a4..2122e58a7 100644
--- a/PermissionController/res/values-te/strings.xml
+++ b/PermissionController/res/values-te/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> పరికరంలో ఈ యాప్‌నకు <xliff:g id="PERM">%1$s</xliff:g> యాక్సెస్"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"అన్ని \'<xliff:g id="APP">%1$s</xliff:g>\' అనుమతులను చూడండి"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ఈ అనుమతి ఉన్న అన్ని యాప్‌లను చూడండి"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"సమాచారం"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"సెట్టింగ్‌లు"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"అసిస్టెంట్ మైక్రోఫోన్ వినియోగాన్ని చూపు"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"ఉపయోగించని యాప్ సెట్టింగ్‌లు"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"యాప్‌ని ఉపయోగించకపోతే, అనుమతులను తీసివేయండి"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"లింక్‌లను తెరవడం"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"వర్క్‌ ప్లేస్ కోసం ఆటోమేటిక్"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"ఆటోమేటిక్‌గా ప్రైవేట్ స్పేస్"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"పరికరం కోసం ఆప్టిమైజ్ చేసినవి"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"ఇతర యాప్‌లు"</string>
<string name="default_app_none" msgid="9084592086808194457">"ఏదీ కాదు"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(సిస్టమ్ ఆటోమేటిక్)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"ఏ యాప్ లేదు"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"పరిమితం చేసిన సెట్టింగ్‌లను అనుమతించండి"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"పరిమితం చేయబడిన సెట్టింగ్"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"మీ సెక్యూరిటీ కోసం, ఈ సెట్టింగ్ ప్రస్తుతం అందుబాటులో లేదు."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"కాల్ మాట్లాడే సమయంలో చర్యను పూర్తి చేయడం కుదరదు"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n మీ పరికరాన్ని, డేటాను సురక్షితంగా ఉంచేందుకు ఈ సెట్టింగ్ బ్లాక్ చేయబడింది"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"స్కామర్‌లు మిమ్మల్ని కొత్త సోర్స్ నుండి తెలియని యాప్‌లను ఇన్‌స్టాల్ చేయమని అడగడం ద్వారా హానికరమైన యాప్‌లను ఇన్‌స్టాల్ చేయడానికి ట్రై చేయవచ్చు."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"స్కామర్‌లు మిమ్నల్ని యాప్ కోసం యాక్సెసిబిలిటీ యాక్సెస్‌ను అనుమతించమని అడగడం ద్వారా మీ పరికరాన్ని కంట్రోల్ చేసే అవకాశం ఉంది."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"ఈ సెట్టింగ్ ద్వారా స్కామర్‌లు మీ పరికరానికి హాని కలిగించేందుకు ప్రయత్నించవచ్చు."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"యాప్ <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>‌కు యాక్సెస్ తిరస్కరించబడింది"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"మీ వ్యక్తిగత, ఆర్థిక సమాచారాన్ని ప్రమాదంలో పడేసే గోప్యమైన సమాచార యాక్సెస్ అనుమతికి యాప్ యాక్సెస్‌ను రిక్వెస్ట్ చేయడం జరిగింది.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>ఈ అదనపు అవసరాలు గల అనుమతి లేకుండా యాప్ సరిగ్గా పని చేయకపోయే అవకాశం ఉంది. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;యాక్సెస్‌ను అనుమతించడం ఎలాగో తెలుసుకోండి&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"ఆటోమేటిక్ <xliff:g id="ROLE_NAME">%1$s</xliff:g>‌గా ఉండటానికి యాప్‌నకు యాక్సెస్ నిరాకరించబడింది"</string>
diff --git a/PermissionController/res/values-th-v33/strings.xml b/PermissionController/res/values-th-v33/strings.xml
index 1270c9c1c..1f558970c 100644
--- a/PermissionController/res/values-th-v33/strings.xml
+++ b/PermissionController/res/values-th-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"การแจ้งเตือนเพิ่มเติม"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"การแจ้งเตือนที่ปิดไป"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{ขยายและดูการแจ้งเตือนอีก 1 รายการ}other{ขยายและดูการแจ้งเตือนอีก # รายการ}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"ยุบ"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"การแจ้งเตือน <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"การดำเนินการเสร็จสมบูรณ์"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"ตรวจสอบการตั้งค่าที่เพิ่มการปกป้องให้กับอุปกรณ์ได้"</string>
diff --git a/PermissionController/res/values-th/strings.xml b/PermissionController/res/values-th/strings.xml
index 0943c6011..02eb8f9de 100644
--- a/PermissionController/res/values-th/strings.xml
+++ b/PermissionController/res/values-th/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"สิทธิ์เข้าถึง<xliff:g id="PERM">%1$s</xliff:g>สำหรับแอปนี้บน <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"ดูสิทธิ์ทั้งหมดของ \"<xliff:g id="APP">%1$s</xliff:g>\""</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ดูแอปทั้งหมดที่มีสิทธิ์นี้"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"ข้อมูล"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"การตั้งค่า"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"แสดงการใช้ไมโครโฟนของ Assistant"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"การตั้งค่าแอปที่ไม่ได้ใช้"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"นำสิทธิ์ออกหากไม่ได้ใช้งานแอป"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"การเปิดลิงก์"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"ค่าเริ่มต้นสำหรับงาน"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"ค่าเริ่มต้นสำหรับพื้นที่ส่วนตัว"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"ได้รับการเพิ่มประสิทธิภาพสำหรับอุปกรณ์"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"อื่นๆ"</string>
<string name="default_app_none" msgid="9084592086808194457">"ไม่มี"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(ค่าเริ่มต้นของระบบ)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"ไม่มีแอป"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"อนุญาตการตั้งค่าที่จำกัด"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"การตั้งค่าที่จำกัด"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"การตั้งค่านี้ใช้ไม่ได้ในตอนนี้เพื่อความปลอดภัยของคุณ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"ดำเนินการให้เสร็จสิ้นระหว่างการโทรไม่ได้"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n การตั้งค่านี้ถูกบล็อกเพื่อปกป้องอุปกรณ์และข้อมูลของคุณ"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"สแกมเมอร์อาจพยายามติดตั้งแอปที่เป็นอันตรายโดยขอให้คุณติดตั้งแอปที่ไม่รู้จักจากแหล่งที่มาใหม่"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"สแกมเมอร์อาจพยายามควบคุมอุปกรณ์ของคุณโดยขอให้คุณอนุญาตให้สิทธ์เข้าถึงแก่แอป"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"สแกมเมอร์อาจพยายามทำอันตรายอุปกรณ์ของคุณด้วยการตั้งค่านี้"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"แอปถูกปฏิเสธไม่ให้เข้าถึง <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"แอปขอเข้าถึงสิทธิ์ที่มีความละเอียดอ่อนซึ่งอาจทำให้ข้อมูลส่วนบุคคลและข้อมูลทางการเงินของคุณมีความเสี่ยง<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>แอปอาจทำงานได้ไม่ถูกต้องหากไม่มีสิทธิ์ที่จำกัดนี้ &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;ดูวิธีอนุญาตให้เข้าถึง&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"แอปถูกปฏิเสธไม่ให้เข้าถึงเพื่อเป็น<xliff:g id="ROLE_NAME">%1$s</xliff:g> เริ่มต้น"</string>
diff --git a/PermissionController/res/values-tl-v33/strings.xml b/PermissionController/res/values-tl-v33/strings.xml
index 0c23a30f7..ad410e830 100644
--- a/PermissionController/res/values-tl-v33/strings.xml
+++ b/PermissionController/res/values-tl-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Higit pang alerto"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Mga na-dismiss na alerto"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{I-expand at makakita ng isa pang alerto}one{I-expand at makakita ng # pang alerto}other{I-expand at makakita ng # pang alerto}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"I-collapse"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Alerto. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Tapos na ang pagkilos"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Tingnan ang mga setting na makakapagdagdag ng proteksyon sa iyong device"</string>
diff --git a/PermissionController/res/values-tl/strings.xml b/PermissionController/res/values-tl/strings.xml
index f08b97531..63c2a120f 100644
--- a/PermissionController/res/values-tl/strings.xml
+++ b/PermissionController/res/values-tl/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"access sa <xliff:g id="PERM">%1$s</xliff:g> para sa app na ito sa <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Tingnan ang lahat ng pahintulot ng <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Tingnan ang lahat ng app na may ganitong pahintulot"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Impormasyon"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Mga Setting"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Ipakita ang paggamit ng mikropono ng assistant"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Mga setting ng hindi ginagamit na app"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Alisin ang mga pahintulot kung hindi ginagamit ang app"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Pagbubukas ng mga link"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Default para sa trabaho"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Default para sa pribadong space"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Na-optimize para sa device"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Iba pa"</string>
<string name="default_app_none" msgid="9084592086808194457">"Wala"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Default ng system)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Walang app"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Payagan ang mga pinaghihigpitang setting"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Pinaghihigpitang setting"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Para sa iyong seguridad, hindi available ang setting na ito sa ngayon."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Hindi magawa ang aksyon habang tumatawag"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Naka-block ang setting na ito para protektahan ang iyong device at data"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Posibleng subukan ng mga scammer na mag-install ng mga nakakapinsalang app sa pamamagitan ng paghiling sa iyong mag-install ng mga hindi kilalang app mula sa isang bagong source."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Posibleng subukan ng mga scammer na kontrolin ang iyong device sa pamamagitan ng paghiling sa iyong payagan ang access sa accessibility para sa isang app."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Posibleng subukan ng mga scammer na ilagay sa panganib ang iyong device gamit ang setting na ito."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Tinanggihan ang access ng appp sa <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Humiling ang app ng access sa pahintulot sa sensitibong impormasyon na posibleng maglagay ng iyong personal at pinansyal na impormasyon sa panganib.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Posibleng hindi gumana nang maayos ang app kung wala ang pinaghihigpitang pahintulot na ito. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Alamin kung paano payagan ang pag-access&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Tinanggihan ang access ng app na maging default na <xliff:g id="ROLE_NAME">%1$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-tr-v33/strings.xml b/PermissionController/res/values-tr-v33/strings.xml
index d6d2e4286..ee3827f93 100644
--- a/PermissionController/res/values-tr-v33/strings.xml
+++ b/PermissionController/res/values-tr-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Diğer uyarılar"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Reddedilen uyarılar"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Genişletip bir uyarıyı daha görün}other{Genişletip # uyarıyı daha görün}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Daralt"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Uyarı. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"İşlem tamamlandı"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Cihazınızın korumasını artırabilecek ayarlara göz atın"</string>
diff --git a/PermissionController/res/values-tr/strings.xml b/PermissionController/res/values-tr/strings.xml
index 467d98bdc..d0156068d 100644
--- a/PermissionController/res/values-tr/strings.xml
+++ b/PermissionController/res/values-tr/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> adlı cihazda bu uygulamanın <xliff:g id="PERM">%1$s</xliff:g> erişimi"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Tüm <xliff:g id="APP">%1$s</xliff:g> izinlerini göster"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Bu izne sahip tüm uygulamaları göster"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Bilgi"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Ayarlar"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Yardımcı mikrofon kullanımını göster"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Kullanılmayan uygulama ayarları"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Uygulama kullanılmıyorsa izinleri kaldır"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Bağlantıları açma"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"İş için varsayılan"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Özel alan için varsayılan"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Cihaz için optimize edilenler"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Diğer"</string>
<string name="default_app_none" msgid="9084592086808194457">"Yok"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Sistem varsayılanı)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Uygulama yok"</string>
@@ -454,7 +458,7 @@
<string name="incident_report_channel_name" msgid="3144954065936288440">"Hata Ayıklama Verilerini Paylaş"</string>
<string name="incident_report_notification_title" msgid="4635984625656519773">"Ayrıntılı hata ayıklama verileri paylaşılsın mı?"</string>
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g>, hata ayıklama bilgilerini yüklemek istiyor."</string>
- <string name="incident_report_dialog_title" msgid="669104389325204095">"Hata ayıklama Verileri Paylaşılsın mı?"</string>
+ <string name="incident_report_dialog_title" msgid="669104389325204095">"Hata ayıklama verileri paylaşılsın mı?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Sistem bir sorun algıladı."</string>
<string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g>, bu cihazda <xliff:g id="DATE">%2$s</xliff:g> saat <xliff:g id="TIME">%3$s</xliff:g> itibarıyla kaydedilen hata raporunu yüklemek istiyor. Hata raporları cihazınızla ilgili veya uygulamalar tarafından günlüğe kaydedilmiş kişisel bilgiler (örneğin kullanıcı adları, konum verisi, cihaz tanımlayıcılar ve ağ bilgileri) içerir. Hata raporlarını yalnızca bu bilgileri verme konusunda güvendiğiniz kişi ve uygulamalarla paylaşın. <xliff:g id="APP_NAME_1">%4$s</xliff:g> uygulamasının hata raporu yüklemesine izin verilsin mi?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"<xliff:g id="APP_NAME">%1$s</xliff:g> hata raporunu işlemeyle ilgili sorun oluştu. Ayrıntılı hata ayıklama verilerinin paylaşılması reddedildi. Kesinti için üzgünüz."</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Kısıtlanmış ayarlara izin verme"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Kısıtlanmış ayar"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Güvenliğiniz için bu ayar şu anda kullanılamıyor."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"İşlem, arama sırasında tamamlanamaz"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Bu ayar, cihazınızı ve verilerinizi korumak için engellenmiştir"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Dolandırıcılar, yeni bir kaynaktan bilinmeyen uygulamaları yüklemenizi isteyerek zararlı uygulamalar yüklemeye çalışabilir."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Dolandırıcılar, bir uygulama için erişilebilirlik erişimine izin vermenizi isteyerek cihazınızın kontrolünü ele geçirmeye çalışabilir."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Dolandırıcılar bu ayarı kullanarak cihazınıza zarar vermeye çalışabilir."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Uygulamanın <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> iznine erişimi reddedildi"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Uygulama, kişisel ve finansal bilgilerinizi riske atabilecek hassas bir izne erişim istedi.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Bu kısıtlı izin olmadan uygulama düzgün çalışmayabilir. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Erişime nasıl izin vereceğinizi öğrenin&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Uygulamanın varsayılan <xliff:g id="ROLE_NAME">%1$s</xliff:g> olarak kullanılma erişimi reddedildi"</string>
diff --git a/PermissionController/res/values-uk-v33/strings.xml b/PermissionController/res/values-uk-v33/strings.xml
index 0066766f5..24571a6d7 100644
--- a/PermissionController/res/values-uk-v33/strings.xml
+++ b/PermissionController/res/values-uk-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Більше сповіщень"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Закриті сповіщення"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Розгорніть і перегляньте ще одне сповіщення}one{Розгорніть і перегляньте ще # сповіщення}few{Розгорніть і перегляньте ще # сповіщення}many{Розгорніть і перегляньте ще # сповіщень}other{Розгорніть і перегляньте ще # сповіщення}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Згорнути"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Сповіщення. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Дію виконано"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Перевірте налаштування, які можуть посилити захист вашого пристрою"</string>
diff --git a/PermissionController/res/values-uk/strings.xml b/PermissionController/res/values-uk/strings.xml
index b05fb81e4..7380d31ed 100644
--- a/PermissionController/res/values-uk/strings.xml
+++ b/PermissionController/res/values-uk/strings.xml
@@ -202,11 +202,13 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="PERM">%1$s</xliff:g>: доступ для цього додатка (<xliff:g id="DEVICE_NAME">%2$s</xliff:g>)"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Переглянути всі дозволи додатка <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Переглянути всі додатки з цим дозволом"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Інформація"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Налаштування"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Показати статус мікрофона Асистента"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Налаштування невикористовуваних додатків"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Відкликати дозволи, якщо додаток не використовується"</string>
<string name="unused_apps_label" msgid="2595428768404901064">"Вилучати дозволи й звільняти місце"</string>
- <string name="unused_apps_label_v2" msgid="7058776770056517980">"Призупиняти роботу в неактивний період"</string>
+ <string name="unused_apps_label_v2" msgid="7058776770056517980">"Призупиняти, коли додаток неактивний"</string>
<string name="unused_apps_label_v3" msgid="693340578642156657">"Керувати невикористовуваним додатком"</string>
<string name="unused_apps_summary" msgid="8839466950318403115">"Вилучити дозволи, видалити тимчасові файли й зупинити сповіщення"</string>
<string name="unused_apps_summary_v2" msgid="5011313200815115802">"Вилучити дозволи, видалити тимчасові файли, зупинити сповіщення й архівувати додаток"</string>
@@ -279,7 +281,7 @@
<string name="post_drive_permission_decision_reminder_summary_1_app_2_permissions" msgid="671791184670801301">"Під час руху ви надали додатку <xliff:g id="APP">%1$s</xliff:g> дозвіл на доступ до таких об’єктів: <xliff:g id="PERMISSION_1">%2$s</xliff:g> і <xliff:g id="PERMISSION_2">%3$s</xliff:g>"</string>
<string name="post_drive_permission_decision_reminder_summary_1_app_multi_permission" msgid="4080701771111456927">"Під час руху ви надали додатку <xliff:g id="APP">%2$s</xliff:g> стільки дозволів: <xliff:g id="COUNT">%1$d</xliff:g>"</string>
<string name="post_drive_permission_decision_reminder_summary_multi_apps" msgid="5253882771252863902">"{count,plural, =1{Під час руху ви надали дозвіл на доступ додатку <xliff:g id="APP_0">%1$s</xliff:g> і ще одному додатку}one{Під час руху ви надали дозвіл на доступ додатку <xliff:g id="APP_1">%1$s</xliff:g> і ще # додатку}few{Під час руху ви надали дозвіл на доступ додатку <xliff:g id="APP_1">%1$s</xliff:g> і ще # додаткам}many{Під час руху ви надали дозвіл на доступ додатку <xliff:g id="APP_1">%1$s</xliff:g> і ще # додаткам}other{Під час руху ви надали дозвіл на доступ додатку <xliff:g id="APP_1">%1$s</xliff:g> і ще # додатка}}"</string>
- <string name="go_to_settings" msgid="1053735612211228335">"Перейти до налаштувань"</string>
+ <string name="go_to_settings" msgid="1053735612211228335">"Відкрити налаштування"</string>
<string name="auto_revoke_setting_subtitle" msgid="8631720570723050460">"Деякі додатки не використовувалися кілька місяців"</string>
<string name="permissions_removed_category_title" msgid="1064754271178447643">"Скасовані дозволи"</string>
<string name="permission_removed_page_title" msgid="2627436155091001209">"Дозволи скасовано"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Відкривання посилань"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Для роботи за умовчанням"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"За умовчанням для приватного простору"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Оптимізовано для пристрою"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Інші"</string>
<string name="default_app_none" msgid="9084592086808194457">"Немає"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(За умовчанням)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Немає додатків"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> хоче завантажити інформацію про налагодження."</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"Поділитися даними про налагодження?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"Система виявила проблему."</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"Додаток <xliff:g id="APP_NAME_0">%1$s</xliff:g> запитує, чи можна завантажити повідомлення про помилку, створене на цьому пристрої <xliff:g id="TIME">%3$s</xliff:g> <xliff:g id="DATE">%2$s</xliff:g>. Повідомлення про помилки містять особисту інформацію про ваш пристрій або відомості, записані додатками, як-от імена користувачів, місцеположення, ідентифікатори пристроїв та інформацію про мережу. Діліться повідомленнями про помилки лише з тими людьми й додатками, яким довіряєте. Дозволити додатку <xliff:g id="APP_NAME_1">%4$s</xliff:g> завантажити повідомлення про помилку?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"Додаток <xliff:g id="APP_NAME_0">%1$s</xliff:g> запитує, чи можна завантажити повідомлення про помилку, створене на цьому пристрої <xliff:g id="DATE">%2$s</xliff:g> о <xliff:g id="TIME">%3$s</xliff:g>. Повідомлення про помилки містять особисту інформацію про ваш пристрій або відомості, записані додатками, як-от імена користувачів, місцеположення, ідентифікатори пристроїв та інформацію про мережу. Діліться повідомленнями про помилки лише з тими людьми й додатками, яким довіряєте. Дозволити додатку <xliff:g id="APP_NAME_1">%4$s</xliff:g> завантажити повідомлення про помилку?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Не вдалося обробити повідомлення про помилку в додатку <xliff:g id="APP_NAME">%1$s</xliff:g>. Дані про налагодження не надіслано. Вибачте за незручності."</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Дозволити"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Заборонити"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Дозволити налаштування з обмеженнями"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Налаштування з обмеженнями"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"З міркувань безпеки це налаштування наразі недоступне."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Неможливо виконати дію під час виклику"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Це налаштування заблоковано, щоб захистити ваш пристрій і дані"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Шахраї можуть намагатися встановити шкідливі додатки, надсилаючи вам запити на встановлення невідомих додатків із нового джерела."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Шахраї можуть намагатися захопити контроль над вашим пристроєм, попросивши надати їм можливість використовувати функції доступності для певного додатка."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Використавши це налаштування, шахраї можуть спробувати пошкодити ваш пристрій."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Додатку не надано дозвіл \"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>\""</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Додаток запитав дозвіл на доступ до чутливих даних, що може поставити під загрозу вашу особисту й фінансову інформацію.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Без цього обмеженого дозволу додаток може не працювати належним чином. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Як надати доступ&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Додатку не надано доступ до ролі \"<xliff:g id="ROLE_NAME">%1$s</xliff:g>\" за умовчанням"</string>
diff --git a/PermissionController/res/values-ur-v33/strings.xml b/PermissionController/res/values-ur-v33/strings.xml
index 0a56dd190..4d89c92b3 100644
--- a/PermissionController/res/values-ur-v33/strings.xml
+++ b/PermissionController/res/values-ur-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"مزید الرٹس"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"برخاست کردہ الرٹس"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{پھیلائیں اور ایک اور الرٹ دیکھیں}other{پھیلائیں اور # مزید الرٹس دیکھیں}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"سکیڑیں"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"الرٹ۔ <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"کارروائی مکمل ہو گئی"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"اپنے آلہ کو تحفظ دینے والی ترتیبات چیک کریں"</string>
diff --git a/PermissionController/res/values-ur/strings.xml b/PermissionController/res/values-ur/strings.xml
index 3b4fa91db..5c4bb5883 100644
--- a/PermissionController/res/values-ur/strings.xml
+++ b/PermissionController/res/values-ur/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g> پر اس ایپ تک رسائی حاصل کریں<xliff:g id="PERM">%1$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"<xliff:g id="APP">%1$s</xliff:g> کی سبھی اجازتیں دیکھیں"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"اس اجازت والی سبھی ایپس دیکھیں"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"معلومات"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"ترتیبات"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"اسسٹنٹ مائیکروفون کا استعمال دکھائیں"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"غیر استعمال شدہ ایپ کی ترتیبات"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"ایپ کے استعمال نہ ہونے پر اجازتیں ہٹائیں"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"شروعاتی لنکس"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"کام کیلئے ڈیفالٹ"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"پرائیویٹ اسپیس کے لیے ڈیفالٹ"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"آلہ کیلئے بہتر بنایا گیا"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"دیگر"</string>
<string name="default_app_none" msgid="9084592086808194457">"کوئی نہیں"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(سسٹم ڈیفالٹ)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"کوئی ایپس نہیں ہیں"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"پابندی والی ترتیبات کی اجازت دیں"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"محدود ترتیب"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"آپ کی سیکیورٹی کیلئے، یہ ترتیب فی الحال دستیاب نہیں ہے۔"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"کال کے دوران کارروائی مکمل نہیں کر سکتے"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n اس ترتیب کو آپ کے آلے اور ڈیٹا کی حفاظت کے لیے مسدود کر دیا گیا ہے"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"دھوکے باز آپ سے کسی نئے ذریعہ سے نامعلوم ایپس انسٹال کرنے کے لیے کہہ کر نقصان دہ ایپس انسٹال کرنے کی کوشش کر سکتے ہیں۔"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"دھوکے باز آپ سے کسی ایپ کے لیے ایکسیسبیلٹی رسائی کی اجازت دینے کے لیے کہہ کر آپ کے آلے کا کنٹرول حاصل کرنے کی کوشش کر سکتے ہیں۔"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"دھوکے باز اس ترتیب کے ساتھ آپ کے آلے کو نقصان پہنچانے کی کوشش کر سکتے ہیں۔"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g> تک ایپ کی رسائی کو مسترد کر دیا گیا"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"‏ایپ نے ایک حساس اجازت تک رسائی کی درخواست کی ہے جو آپ کی ذاتی اور مالی معلومات کو خطرے میں ڈال سکتی ہے۔<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>یہ ممکن ہے کہ اس محدود اجازت کے بغیر ایپ ٹھیک سے کام نہ کرے۔ &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;رسائی کی اجازت دینے کا طریقہ جانیں&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"ڈیفالٹ <xliff:g id="ROLE_NAME">%1$s</xliff:g> ہونے کے لیے ایپ کی رسائی کو مسترد کر دیا گیا"</string>
diff --git a/PermissionController/res/values-uz-v33/strings.xml b/PermissionController/res/values-uz-v33/strings.xml
index a32387c37..31fc63c98 100644
--- a/PermissionController/res/values-uz-v33/strings.xml
+++ b/PermissionController/res/values-uz-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Boshqa ogohlantirishlar"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Ogohlantirishlar yopildi"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Kengaytirish va yana bitta ogohlantirishni ochish}other{Kengaytirish va yana # ta ogohlantirishni ochish}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Yopish"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Ogohlantirish: <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Amal bajarildi"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Qurilmangizga himoya qoʻshadigan sozlamalarni tekshiring"</string>
diff --git a/PermissionController/res/values-uz/strings.xml b/PermissionController/res/values-uz/strings.xml
index a1f04e4cf..440bd34f6 100644
--- a/PermissionController/res/values-uz/strings.xml
+++ b/PermissionController/res/values-uz/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"<xliff:g id="DEVICE_NAME">%2$s</xliff:g>da bu ilova uchun <xliff:g id="PERM">%1$s</xliff:g> kirish ruxsati"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"<xliff:g id="APP">%1$s</xliff:g> uchun berilgan barcha ruxsatlar"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Bu ruxsatga ega barcha ilovalar"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Axborot"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Sozlamalar"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Assistent uchun mikrofondan foydalanishni koʻrsatish"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Ishlatilmagan ilova sozlamalari"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Ishlatilmayotgan ilovalardan ruxsatlarni olib tashlash"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Havolalarni ochish"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Ish uchun birlamchi"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Maxfiy makon uchun standart"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Qurilma uchun optimallangan"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Boshqalar"</string>
<string name="default_app_none" msgid="9084592086808194457">"Hech qanday"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Birlamchi)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Hech qanday ilova topilmadi"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Cheklangan sozlamalarga ruxsat berish"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Cheklangan sozlama"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Xavfsizlik maqsadida bu sozlama hozir ishlamaydi."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Chaqiruv paytida bu amalni bajarish imkonsiz"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Qurilma va maʼlumotlarni himoyalash maqsadida bu sozlama bloklandi"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Firibgarlar yangi manbadan notanish ilovalarni oʻrnatishga ruxsat soʻrab, zararli ilovalarni oʻrnatishga urinishi mumkin."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Firibgarlar ilova uchun maxsus imkoniyatlarga ruxsat berishni soʻrab, qurilmani boshqarishga urinishi mumkin."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Firibgarlar bu sozlama orqali qurlimaga zarar yetkazishga urinishi mumkin."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Ilovaga <xliff:g id="PERMISSION_NAME">%1$s</xliff:g> ruxsati berilmadi"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Ilova maxfiy maʼlumotlarga kirish uchun ruxsat soʻramoqda. Ruxsat bersangiz, shaxsiy va moliyaviy maʼlumotlaringiz xavf ostida qolishi mumkin.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g> Bu cheklangan ruxsatsiz ilova toʻgʻri ishlamasligi mumkin. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Qanday ruxsat berish haqida batafsil&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Ilovadan <xliff:g id="ROLE_NAME">%1$s</xliff:g> turkumidagi standart sifatida foydalanish taqiqlangan"</string>
diff --git a/PermissionController/res/values-v33/attrs.xml b/PermissionController/res/values-v33/attrs.xml
index 4a417076d..b136ab718 100644
--- a/PermissionController/res/values-v33/attrs.xml
+++ b/PermissionController/res/values-v33/attrs.xml
@@ -39,8 +39,9 @@
<attr name="scStatusButtonStyle" format="reference" />
<attr name="scActionButtonListLayout" format="reference" />
<attr name="scActionButtonTheme" format="reference" />
- <attr name="scActionButtonStyle" format="reference"/>
- <attr name="scSecondaryActionButtonStyle" format="reference"/>
+ <attr name="scActionButtonStyle" format="reference" />
+ <attr name="scSecondaryActionButtonStyle" format="reference" />
+ <attr name="scCardSideMargin" format="dimension" />
<attr name="colorScShieldAccent" format="color" />
</resources> \ No newline at end of file
diff --git a/PermissionController/res/values-v33/strings.xml b/PermissionController/res/values-v33/strings.xml
index 2267d068e..6cf538b48 100644
--- a/PermissionController/res/values-v33/strings.xml
+++ b/PermissionController/res/values-v33/strings.xml
@@ -52,6 +52,8 @@
other {Expand and see # more alerts}
}
</string>
+ <!-- Label for collapsing the "See all alerts"/"More issues" card in Safety Center [CHAR LIMIT=NONE] -->
+ <string name="safety_center_more_issues_card_collapse_action">Collapse</string>
<!-- Content description prefixing the first text view of a safety center issue card preference [CHAR LIMIT=NONE]-->
<string name="safety_center_issue_card_prefix_content_description">Alert. <xliff:g id="issue card title" example="Protect your account">%1$s</xliff:g></string>
diff --git a/PermissionController/res/values-v33/styles.xml b/PermissionController/res/values-v33/styles.xml
index 94344615b..7e959c2f4 100644
--- a/PermissionController/res/values-v33/styles.xml
+++ b/PermissionController/res/values-v33/styles.xml
@@ -317,8 +317,8 @@
<item name="android:paddingEnd">@dimen/sc_spacing_xxxlarge</item>
<item name="android:paddingTop">@dimen/sc_spacing_xxxlarge</item>
<item name="android:paddingBottom">@dimen/sc_card_margin_bottom</item>
- <item name="android:layout_marginStart">@dimen/sc_spacing_large</item>
- <item name="android:layout_marginEnd">@dimen/sc_spacing_large</item>
+ <item name="android:layout_marginStart">?attr/scCardSideMargin</item>
+ <item name="android:layout_marginEnd">?attr/scCardSideMargin</item>
<item name="android:background">@drawable/safety_center_card_background</item>
</style>
diff --git a/PermissionController/res/values-v33/themes.xml b/PermissionController/res/values-v33/themes.xml
index 82a8ef5b6..a3d1b16c3 100644
--- a/PermissionController/res/values-v33/themes.xml
+++ b/PermissionController/res/values-v33/themes.xml
@@ -57,6 +57,8 @@
@style/SecondarySafetyCenterActionButton.Fixed
</item>
+ <item name="scCardSideMargin">@dimen/sc_spacing_large</item>
+
<item name="textColorScActionButton">@color/sc_primary_action_button_text</item>
<item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
@@ -69,6 +71,10 @@
<style name="Theme.SafetyCenterQs" parent="Theme.SafetyCenterQsBase" />
+ <style name="Theme.SafetyCenterQsExpressive" parent="Theme.SafetyCenterQs">
+ <item name="scCardSideMargin">0dp</item>
+ </style>
+
<style name="Theme.SafetyCenterBase" parent="Theme.PermissionController.Settings.FilterTouches">
<item name="colorSurface">@color/sc_surface_light</item>
<item name="colorSurfaceVariant">@color/sc_surface_variant_light</item>
@@ -104,6 +110,8 @@
@style/SecondarySafetyCenterActionButton.Responsive
</item>
+ <item name="scCardSideMargin">@dimen/sc_spacing_large</item>
+
<item name="textColorScActionButton">@color/sc_primary_action_button_text</item>
<item name="textColorScSecondaryActionButton">?android:attr/textColorPrimary</item>
@@ -115,4 +123,8 @@
</style>
<style name="Theme.SafetyCenter" parent="Theme.SafetyCenterBase" />
+
+ <style name="Theme.SafetyCenterExpressive" parent="Theme.SafetyCenter">
+ <item name="scCardSideMargin">0dp</item>
+ </style>
</resources> \ No newline at end of file
diff --git a/PermissionController/res/values-v35/styles.xml b/PermissionController/res/values-v35/styles.xml
index 612125fb1..513754bb8 100644
--- a/PermissionController/res/values-v35/styles.xml
+++ b/PermissionController/res/values-v35/styles.xml
@@ -197,5 +197,221 @@
<item name="android:visibility">gone</item>
</style>
+ <style name="PermissionSelectorWithWidgetPreferenceRootLayoutStyle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item>
+ <item name="android:paddingEnd">?android:attr/listPreferredItemPaddingEnd</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceWidgetFrameStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:paddingHorizontal">20dp</item>
+ <item name="android:gravity">center</item>
+ <item name="android:minWidth">56dp</item>
+ <item name="android:orientation">vertical</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceIconFrameStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:minWidth">32dp</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:layout_marginEnd">16dp</item>
+ <item name="android:paddingTop">4dp</item>
+ <item name="android:paddingBottom">4dp</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceIconStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="maxWidth">@dimen/secondary_app_icon_size</item>
+ <item name="maxHeight">@dimen/secondary_app_icon_size</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceTextContainerStyle">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_weight">1</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:paddingTop">16dp</item>
+ <item name="android:paddingBottom">16dp</item>
+ <item name="android:paddingEnd">?android:attr/listPreferredItemPaddingEnd</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceTitleStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:maxLines">2</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:hyphenationFrequency">normalFast</item>
+ <item name="android:lineBreakWordStyle">phrase</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceListItem</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceSummaryContainerStyle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:visibility">gone</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceSummaryStyle">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_weight">1</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:hyphenationFrequency">normalFast</item>
+ <item name="android:lineBreakWordStyle">phrase</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceAppendixStyle">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_weight">1</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+ <item name="android:textAlignment">viewEnd</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:maxLines">1</item>
+ <item name="android:visibility">gone</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceExtraWidgetContainerStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:gravity">center_vertical</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceExtraWidgetDividerStyle">
+ <item name="android:layout_width">.75dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:layout_marginBottom">16dp</item>
+ <item name="android:background">?android:attr/dividerVertical</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceExtraWidgetImageStyle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:minWidth">@dimen/two_target_min_width</item>
+ <item name="android:layout_height">fill_parent</item>
+ <item name="android:src">@drawable/ic_settings_accent</item>
+ <item name="android:paddingStart">24dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ </style>
+
+ <style name="PermissionSelectorWithWidgetPreferenceWidgetRadioButton">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:background">@null</item>
+ <item name="android:focusable">false</item>
+ <item name="android:clickable">false</item>
+ </style>
+
+ <style name="PermissionTwoTargetPreferenceRootLayoutStyle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:minHeight">?android:attr/listPreferredItemHeightSmall</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:background">?android:attr/selectableItemBackground</item>
+ <item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
+ <item name="android:paddingEnd">?android:attr/listPreferredItemPaddingEnd</item>
+ <item name="android:clipToPadding">false</item>
+ </style>
+
+ <style name="PermissionTwoTargetPreferenceTextContainerStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_weight">1</item>
+ <item name="android:paddingTop">16dp</item>
+ <item name="android:paddingBottom">16dp</item>
+ </style>
+
+ <style name="PermissionTwoTargetPreferenceTitleStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:maxLines">2</item>
+ <item name="android:hyphenationFrequency">normalFast</item>
+ <item name="android:lineBreakWordStyle">phrase</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceListItem</item>
+ <item name="android:ellipsize">marquee</item>
+ </style>
+
+ <style name="PermissionTwoTargetPreferenceSummaryStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_below">@android:id/title</item>
+ <item name="android:layout_alignStart">@android:id/title</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceListItemSecondary</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:hyphenationFrequency">normalFast</item>
+ <item name="android:lineBreakWordStyle">phrase</item>
+ <item name="android:maxLines">10</item>
+ </style>
+
+ <style name="PermissionTwoTargetPreferenceWidgetFrameStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:minWidth">@dimen/two_target_min_width</item>
+ <item name="android:gravity">center</item>
+ <item name="android:orientation">vertical</item>
+ </style>
+
+ <style name="PermissionTwoTargetPreferenceIconFrameStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:minWidth">48dp</item>
+ <item name="android:gravity">start|center_vertical</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:paddingLeft">0dp</item>
+ <item name="android:paddingStart">0dp</item>
+ <item name="android:paddingRight">8dp</item>
+ <item name="android:paddingEnd">8dp</item>
+ <item name="android:paddingTop">4dp</item>
+ <item name="android:paddingBottom">4dp</item>
+ </style>
+
+ <style name="PermissionTwoTargetPreferenceIconStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="maxWidth">48dp</item>
+ <item name="maxHeight">48dp</item>
+ </style>
+
+ <style name="PermissionTwoTargetPreferenceDividerContainerStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:gravity">start|center_vertical</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:paddingStart">?android:attr/listPreferredItemPaddingEnd</item>
+ <item name="android:paddingLeft">?android:attr/listPreferredItemPaddingEnd</item>
+ <item name="android:paddingTop">16dp</item>
+ <item name="android:paddingBottom">16dp</item>
+ </style>
+
+ <style name="PermissionTwoTargetPreferenceDividerStyle">
+ <item name="android:layout_width">1dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:background">?android:attr/listDivider</item>
+ </style>
+
+ <style name="AppPermissionFooterLinkPreferenceRootLayoutStyle"
+ parent="PermissionPreferenceRootLayoutStyle" />
+
+ <style name="AppPermissionFooterLinkPreferenceTextLayoutStyle"
+ parent="PermissionPreferenceTextRelativeLayoutStyle" />
+
+ <style name="AppPermissionFooterLinkPreferenceSummaryStyle"
+ parent="PermissionPreferenceSummaryTextStyle" />
+
<!-- END PREFERENCE STYLES -->
</resources> \ No newline at end of file
diff --git a/PermissionController/res/values-v35/themes.xml b/PermissionController/res/values-v35/themes.xml
index ff6c430c4..995f230e7 100644
--- a/PermissionController/res/values-v35/themes.xml
+++ b/PermissionController/res/values-v35/themes.xml
@@ -28,7 +28,7 @@
TODO(b/309578419): Make activities handle insets properly and then remove this.
-->
<style name="OptOutEdgeToEdgeEnforcement">
- <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
+ <item name="android:windowOptOutEdgeToEdgeEnforcement">false</item>
</style>
<style name="ThemeOverlay.PermissionSettings" parent="" />
diff --git a/PermissionController/res/values-vi-v33/strings.xml b/PermissionController/res/values-vi-v33/strings.xml
index d0c8853c5..a8c70b112 100644
--- a/PermissionController/res/values-vi-v33/strings.xml
+++ b/PermissionController/res/values-vi-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Cảnh báo khác"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Cảnh báo bị loại bỏ"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Mở rộng và xem một cảnh báo khác}other{Mở rộng và xem # cảnh báo khác}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Thu gọn"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Cảnh báo. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Hoàn tất hành động"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Kiểm tra các chế độ cài đặt có thể giúp tăng cường bảo vệ cho thiết bị của bạn"</string>
diff --git a/PermissionController/res/values-vi/strings.xml b/PermissionController/res/values-vi/strings.xml
index b9a077e77..eb1354df3 100644
--- a/PermissionController/res/values-vi/strings.xml
+++ b/PermissionController/res/values-vi/strings.xml
@@ -202,11 +202,13 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Quyền truy cập <xliff:g id="PERM">%1$s</xliff:g> cho ứng dụng này trên <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Xem tất cả các quyền của <xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Xem tất cả ứng dụng có quyền này"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Thông tin"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Cài đặt"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Hiển thị việc sử dụng micrô của Trợ lý"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Chế độ cài đặt cho ứng dụng không dùng đến"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Thu hồi quyền nếu bạn không dùng ứng dụng"</string>
<string name="unused_apps_label" msgid="2595428768404901064">"Thu hồi quyền và giải phóng dung lượng"</string>
- <string name="unused_apps_label_v2" msgid="7058776770056517980">"Tạm dừng hoạt động của ứng dụng nếu không dùng"</string>
+ <string name="unused_apps_label_v2" msgid="7058776770056517980">"Dừng hoạt động ứng dụng nếu không dùng"</string>
<string name="unused_apps_label_v3" msgid="693340578642156657">"Quản lý ứng dụng nếu không dùng"</string>
<string name="unused_apps_summary" msgid="8839466950318403115">"Loại bỏ quyền, xoá tệp tạm thời và dừng thông báo"</string>
<string name="unused_apps_summary_v2" msgid="5011313200815115802">"Loại bỏ quyền, xoá tệp tạm thời, dừng thông báo và lưu trữ ứng dụng"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Mở đường liên kết"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Ứng dụng mặc định cho công việc"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Ứng dụng mặc định cho không gian riêng tư"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Tối ưu hoá cho thiết bị"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Ứng dụng khác"</string>
<string name="default_app_none" msgid="9084592086808194457">"Không có"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Ứng dụng mặc định của hệ thống)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Không có ứng dụng nào"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Cho phép các chế độ cài đặt bị hạn chế"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Chế độ cài đặt bị hạn chế"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Để đảm bảo an toàn cho bạn, chế độ cài đặt này hiện không dùng được."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Không thực hiện được thao tác trong khi gọi điện"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Chế độ cài đặt này bị chặn để bảo vệ thiết bị và dữ liệu của bạn"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Kẻ lừa đảo có thể cố cài đặt các ứng dụng gây hại bằng cách yêu cầu bạn cài đặt các ứng dụng không xác định từ một nguồn mới."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Kẻ lừa đảo có thể cố giành quyền kiểm soát thiết bị của bạn bằng cách yêu cầu bạn cấp quyền truy cập vào tính năng hỗ trợ tiếp cận cho một ứng dụng."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Kẻ lừa đảo có thể dùng chế độ cài đặt này để tìm cách gây hại cho thiết bị của bạn."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"Ứng dụng đã bị từ chối cấp quyền truy cập vào <xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"Ứng dụng này yêu cầu quyền truy cập vào thông tin nhạy cảm. Việc đó có thể khiến thông tin cá nhân và thông tin tài chính của bạn gặp rủi ro.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Có thể ứng dụng sẽ hoạt động không đúng cách nếu không được cấp quyền bị hạn chế này. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Tìm hiểu cách cấp quyền&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"Ứng dụng đã bị từ chối cấp quyền để làm <xliff:g id="ROLE_NAME">%1$s</xliff:g> mặc định"</string>
diff --git a/PermissionController/res/values-watch/donottranslate.xml b/PermissionController/res/values-watch/donottranslate.xml
deleted file mode 100644
index c3ab3cbb1..000000000
--- a/PermissionController/res/values-watch/donottranslate.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <!-- font-family-device-default is expected to be preloaded in the font_customization.xml(/vendor/<OEM>/products/<PRODUCT>/fonts/fonts_customization.xml)-->
- <!-- Falls back to system default when font-family-device-default doesn't exist -->
- <string name="wear_material_compose_display_1_font_family">font-family-device-default</string>
- <string name="wear_material_compose_display_2_font_family">font-family-device-default</string>
- <string name="wear_material_compose_display_3_font_family">font-family-device-default</string>
- <string name="wear_material_compose_title_1_font_family">font-family-medium-device-default</string>
- <string name="wear_material_compose_title_2_font_family">font-family-medium-device-default</string>
- <string name="wear_material_compose_title_3_font_family">font-family-device-default</string>
- <string name="wear_material_compose_body_1_font_family">font-family-text-device-default</string>
- <string name="wear_material_compose_body_2_font_family">font-family-text-device-default</string>
- <string name="wear_material_compose_button_font_family">font-family-text-medium-device-default</string>
- <string name="wear_material_compose_caption_1_font_family">font-family-text-medium-device-default</string>
- <string name="wear_material_compose_caption_2_font_family">font-family-text-medium-device-default</string>
- <string name="wear_material_compose_caption_3_font_family">font-family-text-medium-device-default</string>
-</resources>
diff --git a/PermissionController/res/values-zh-rCN-v33/strings.xml b/PermissionController/res/values-zh-rCN-v33/strings.xml
index 064e8b581..d43167cc9 100644
--- a/PermissionController/res/values-zh-rCN-v33/strings.xml
+++ b/PermissionController/res/values-zh-rCN-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"更多提醒"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"忽略的提醒"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{展开并查看另外一项提醒}other{展开并查看另外 # 项提醒}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"收起"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"提醒:<xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"操作完成"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"查看可提高设备安全性的设置"</string>
diff --git a/PermissionController/res/values-zh-rCN/strings.xml b/PermissionController/res/values-zh-rCN/strings.xml
index ccd1d3183..b498572ab 100644
--- a/PermissionController/res/values-zh-rCN/strings.xml
+++ b/PermissionController/res/values-zh-rCN/strings.xml
@@ -202,14 +202,16 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"此应用在<xliff:g id="DEVICE_NAME">%2$s</xliff:g>上的<xliff:g id="PERM">%1$s</xliff:g>访问权限"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"查看“<xliff:g id="APP">%1$s</xliff:g>”的所有权限"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"查看具有此权限的所有应用"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"信息"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"设置"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"显示 Google 助理麦克风使用情况"</string>
- <string name="unused_apps_category_title" msgid="2988455616845243901">"针对闲置应用的设置"</string>
+ <string name="unused_apps_category_title" msgid="2988455616845243901">"闲置应用设置"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"如果未使用此应用,则移除相关权限"</string>
<string name="unused_apps_label" msgid="2595428768404901064">"撤消权限并释放空间"</string>
<string name="unused_apps_label_v2" msgid="7058776770056517980">"暂停闲置应用的活动"</string>
<string name="unused_apps_label_v3" msgid="693340578642156657">"管理闲置应用"</string>
- <string name="unused_apps_summary" msgid="8839466950318403115">"移除权限、删除临时文件并停止发送通知"</string>
- <string name="unused_apps_summary_v2" msgid="5011313200815115802">"移除权限、删除临时文件、停止发送通知并归档应用"</string>
+ <string name="unused_apps_summary" msgid="8839466950318403115">"撤消权限、删除临时文件并停收通知"</string>
+ <string name="unused_apps_summary_v2" msgid="5011313200815115802">"撤消权限、删除临时文件、停收通知并归档应用"</string>
<string name="auto_revoke_summary" msgid="5867548789805911683">"为了保护您的数据,如果您连续几个月未使用此应用,系统会移除其权限。"</string>
<string name="auto_revoke_summary_with_permissions" msgid="389712086597285013">"为了保护您的数据,如果您连续几个月未使用此应用,系统会移除其以下权限:<xliff:g id="PERMS">%1$s</xliff:g>"</string>
<string name="auto_revoked_apps_page_summary" msgid="6594753657893756536">"为了保护您的数据,对于您连续几个月未使用过的应用,系统已将其权限移除。"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"打开链接"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"默认工作应用"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"私密空间的默认应用"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"针对设备进行了优化"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"其他"</string>
<string name="default_app_none" msgid="9084592086808194457">"无"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(系统默认)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"没有应用"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g>请求上传调试信息。"</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"要分享调试数据吗?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"系统已检测到一个问题。"</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"“<xliff:g id="APP_NAME_0">%1$s</xliff:g>”请求上传此设备在<xliff:g id="DATE">%2$s</xliff:g><xliff:g id="TIME">%3$s</xliff:g>获取的错误报告。错误报告包含与您的设备有关或由应用记录的个人信息,例如:用户名、位置数据、设备标识符和网络信息。请务必只与您认为可以向其透露这些信息的人和应用分享错误报告。是否允许“<xliff:g id="APP_NAME_1">%4$s</xliff:g>”上传错误报告?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> 请求上传此设备在 <xliff:g id="DATE">%2$s</xliff:g><xliff:g id="TIME">%3$s</xliff:g> 获取的错误报告。错误报告包含与您的设备相关或者由应用记录的个人信息,例如用户名、位置数据、设备标识符和网络信息。请务必只与您信任的用户和应用分享错误报告。要允许 <xliff:g id="APP_NAME_1">%4$s</xliff:g> 上传错误报告吗?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"处理<xliff:g id="APP_NAME">%1$s</xliff:g>的错误报告时出错,因此系统已拒绝分享详细的调试数据。不便之处,敬请见谅。"</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"允许"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"拒绝"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"允许受限制的设置"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"受限制的设置"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"出于安全考虑,此设置目前不可用。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"无法在通话期间完成操作"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n 为保护您的设备和数据,系统已屏蔽此设置"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"诈骗者可能会要求您从新的来源安装未知应用,从而试图安装有害应用。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"诈骗者可能会要求您允许某个应用使用无障碍功能,从而试图控制您的设备。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"诈骗者可能会试图利用此设置来危害您的设备。"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"系统已拒绝向此应用授予<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>访问权限"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"该应用请求获得敏感权限,授予这项权限可能会导致您的个人信息和财务信息面临风险。<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>如果不授予这项受限权限,该应用可能无法正常运行。&lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;了解如何授予访问权限&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"系统已拒绝向此应用授予作为默认<xliff:g id="ROLE_NAME">%1$s</xliff:g>的访问权限"</string>
diff --git a/PermissionController/res/values-zh-rHK-v33/strings.xml b/PermissionController/res/values-zh-rHK-v33/strings.xml
index da31b6eae..c9dffa172 100644
--- a/PermissionController/res/values-zh-rHK-v33/strings.xml
+++ b/PermissionController/res/values-zh-rHK-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"更多警示"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"已關閉的警示"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{展開即可查看多一個警示}other{展開即可查看多 # 個警示}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"收合"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"警示:<xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"動作已完成"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"查看可加強保護裝置的設定"</string>
diff --git a/PermissionController/res/values-zh-rHK-v34/strings.xml b/PermissionController/res/values-zh-rHK-v34/strings.xml
index 12f494881..3bd6abe6e 100644
--- a/PermissionController/res/values-zh-rHK-v34/strings.xml
+++ b/PermissionController/res/values-zh-rHK-v34/strings.xml
@@ -19,7 +19,7 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="security_privacy_brand_name" msgid="7303621734258440812">"安全性和私隱"</string>
<string name="privacy_subpage_controls_header" msgid="4152396976713749322">"控制項"</string>
- <string name="health_connect_title" msgid="2132233890867430855">"Health Connect"</string>
+ <string name="health_connect_title" msgid="2132233890867430855">"健康資料同步"</string>
<string name="health_connect_summary" msgid="815473513776882296">"管理應用程式的健康資料存取權"</string>
<string name="location_settings" msgid="8863940440881290182">"位置資料存取權"</string>
<string name="mic_toggle_description" msgid="1504101620086616040">"適用於應用程式和服務。如果關閉此設定,系統仍會在你撥打緊急電話號碼時提供麥克風的資料"</string>
diff --git a/PermissionController/res/values-zh-rHK/strings.xml b/PermissionController/res/values-zh-rHK/strings.xml
index 07a401ab3..86f448e13 100644
--- a/PermissionController/res/values-zh-rHK/strings.xml
+++ b/PermissionController/res/values-zh-rHK/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"此應用程式在<xliff:g id="DEVICE_NAME">%2$s</xliff:g>上的<xliff:g id="PERM">%1$s</xliff:g>存取權"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"查看「<xliff:g id="APP">%1$s</xliff:g>」的所有權限"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"查看擁有此權限的所有應用程式"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"資料"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"設定"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"顯示「Google 助理」麥克風使用情況"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"不使用的應用程式設定"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"如不使用應用程式,即移除權限"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"開啟連結"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"預設用於工作"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"私人空間的預設應用程式"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"已針對你的裝置優化"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"其他"</string>
<string name="default_app_none" msgid="9084592086808194457">"無"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(系統預設)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"沒有應用程式"</string>
@@ -456,7 +460,7 @@
<string name="incident_report_notification_text" msgid="3376480583513587923">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」要求上載偵錯資料。"</string>
<string name="incident_report_dialog_title" msgid="669104389325204095">"分享偵錯資料?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"系統偵測到問題。"</string>
- <string name="incident_report_dialog_text" msgid="5675553296891757523">"「<xliff:g id="APP_NAME_0">%1$s</xliff:g>」要求上載此裝置於 <xliff:g id="DATE">%2$s</xliff:g><xliff:g id="TIME">%3$s</xliff:g>傳送的錯誤報告。錯誤報告包括你的裝置或應用程式記錄的個人資料,例如使用者名稱、位置資料、裝置識別碼和網絡資訊。只與你信任可存取這些資料的使用者和應用程式分享錯誤報告。要允許「<xliff:g id="APP_NAME_1">%4$s</xliff:g>」上載錯誤報告嗎?"</string>
+ <string name="incident_report_dialog_text" msgid="5675553296891757523">"「<xliff:g id="APP_NAME_0">%1$s</xliff:g>」要求上載此裝置於 <xliff:g id="DATE">%2$s</xliff:g> <xliff:g id="TIME">%3$s</xliff:g> 傳送的錯誤報告。錯誤報告包括你的裝置或應用程式記錄的個人資料,例如使用者名稱、位置資料、裝置識別碼和網絡資訊。只與你信任可存取這些資料的使用者和應用程式分享錯誤報告。要允許「<xliff:g id="APP_NAME_1">%4$s</xliff:g>」上載錯誤報告嗎?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"系統處理「<xliff:g id="APP_NAME">%1$s</xliff:g>」的錯誤報告時發生錯誤,因此已拒絕分享詳細的偵錯資料。很抱歉發生中斷情況。"</string>
<string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"允許"</string>
<string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"拒絕"</string>
@@ -674,9 +678,14 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"允許受限設定"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"受限設定"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"為安全起見,系統目前不提供此設定。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"通話期間無法完成操作"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n系統已封鎖此設定,以保護你的裝置和資料"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"騙徒可能會透過要求你從新來源安裝不明應用程式,試圖安裝有害應用程式。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"騙徒可能會透過要求你允許某應用程式存取無障礙功能,試圖控制你的裝置。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"騙徒可能會透過此設定試圖損害你的裝置。"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"系統已拒絕授予應用程式「<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>」存取權"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"應用程式要求存取敏感資料權限,授予此權限可能會危害你的個人和財務資料。<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>如沒有此受限制權限,應用程式可能無法正常運作。&lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;進一步瞭解如何授予存取權&lt;/a&gt;"</string>
- <string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"系統已拒絕授予應用程式預設<xliff:g id="ROLE_NAME">%1$s</xliff:g>存取權"</string>
+ <string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"系統已拒絕授予應用程式作為預設<xliff:g id="ROLE_NAME">%1$s</xliff:g>的存取權"</string>
<string name="enhanced_confirmation_dialog_desc_role" msgid="6369601947905234551">"應用程式要求存取敏感資料權限,授予此權限可能會危害你的個人和財務資料。<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>如沒有此受限制權限,應用程式可能無法正常運作。&lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;進一步瞭解如何授予存取權&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_settings_default" msgid="1858092969721041576">"系統已拒絕授予應用程式存取權"</string>
<string name="enhanced_confirmation_dialog_desc_settings_default" msgid="6911632348359332981">"授予此權限可能會危害你的個人和財務資料。<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>如沒有此受限制權限,應用程式可能無法正常運作。&lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;進一步瞭解如何授予存取權&lt;/a&gt;"</string>
diff --git a/PermissionController/res/values-zh-rTW-v33/strings.xml b/PermissionController/res/values-zh-rTW-v33/strings.xml
index a1ca5c673..40a5b41f4 100644
--- a/PermissionController/res/values-zh-rTW-v33/strings.xml
+++ b/PermissionController/res/values-zh-rTW-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"更多警示"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"已關閉的警示"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{展開並查看另外 1 則快訊}other{展開並查看另外 # 則快訊}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"收合"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"警示:<xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"動作已完成"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"查看可強化裝置防護的設定"</string>
diff --git a/PermissionController/res/values-zh-rTW/strings.xml b/PermissionController/res/values-zh-rTW/strings.xml
index 408107f8a..b8cd25891 100644
--- a/PermissionController/res/values-zh-rTW/strings.xml
+++ b/PermissionController/res/values-zh-rTW/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"這個應用程式在<xliff:g id="DEVICE_NAME">%2$s</xliff:g>上的<xliff:g id="PERM">%1$s</xliff:g>存取權"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"查看「<xliff:g id="APP">%1$s</xliff:g>」的所有權限"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"查看具備此權限的所有應用程式"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"資訊"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"設定"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"顯示 Google 助理的麥克風使用狀況"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"未使用的應用程式設定"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"如果應用程式未使用,讓系統移除相關權限"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"開啟連結"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"預設的工作應用程式"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"私人空間的預設應用程式"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"已針對裝置最佳化"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"其他"</string>
<string name="default_app_none" msgid="9084592086808194457">"無"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(系統預設)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"沒有可用的應用程式"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"解除受限制的設定"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"受限制的設定"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"為了安全起見,目前無法使用這項設定。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"通話期間無法完成動作"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n 為保護裝置和資料,系統已封鎖這項設定"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"詐騙者可能會要求你從新的來源安裝不明應用程式,試圖安裝有害應用程式。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"詐騙者可能會要求你授權讓應用程式存取無障礙功能,試圖控制你的裝置。"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"詐騙者可能會試圖利用這項設定損害你的裝置。"</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"系統已拒絕授予應用程式「<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>」存取權"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"這個應用程式要求取得私密資訊權限,授予這項權限可能導致你的個人資訊和財務資訊面臨風險。<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>如果未取得這項受限制權限,應用程式可能無法正常運作。&lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;瞭解如何授予權限&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"系統已拒絕授予應用程式做為預設「<xliff:g id="ROLE_NAME">%1$s</xliff:g>」的存取權"</string>
diff --git a/PermissionController/res/values-zu-v33/strings.xml b/PermissionController/res/values-zu-v33/strings.xml
index 2b1861644..71ca1c68d 100644
--- a/PermissionController/res/values-zu-v33/strings.xml
+++ b/PermissionController/res/values-zu-v33/strings.xml
@@ -30,6 +30,7 @@
<string name="safety_center_more_issues_card_title" msgid="7425844746197493312">"Ezinye izexwayiso"</string>
<string name="safety_center_dismissed_issues_card_title" msgid="2340129842725145733">"Chitha izexwayiso"</string>
<string name="safety_center_more_issues_card_expand_action" msgid="7109451851052272946">"{count,plural, =1{Nweba futhi ubone esinye isexwayiso}one{Nweba futhi ubone ezinye izexwayiso ezingu-#}other{Nweba futhi ubone ezinye izexwayiso ezingu-#}}"</string>
+ <string name="safety_center_more_issues_card_collapse_action" msgid="7485597582198474637">"Goqa"</string>
<string name="safety_center_issue_card_prefix_content_description" msgid="1447445289637043544">"Isexwayiso. <xliff:g id="ISSUE_CARD_TITLE">%1$s</xliff:g>"</string>
<string name="safety_center_resolved_issue_fallback" msgid="8548932070610766651">"Isenzo siqediwe"</string>
<string name="safety_center_qs_status_summary" msgid="5193925895830451177">"Hlola amasethingi angangeza ukuvikeleka kudivayisi yakho"</string>
diff --git a/PermissionController/res/values-zu/strings.xml b/PermissionController/res/values-zu/strings.xml
index 4669b2506..aa2ac6819 100644
--- a/PermissionController/res/values-zu/strings.xml
+++ b/PermissionController/res/values-zu/strings.xml
@@ -202,6 +202,8 @@
<string name="app_permission_header_with_device_name" msgid="7193042925656173271">"Ukufinyelela kwe-<xliff:g id="PERM">%1$s</xliff:g> kwale app kuvuliwe ku-<xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string>
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"Bona zonke izimvume ze-<xliff:g id="APP">%1$s</xliff:g>"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"Bona zonke izinhlelo zokusebenza ngale mvume"</string>
+ <string name="app_permission_info_button" msgid="8973692370208562556">"Ulwazi"</string>
+ <string name="app_permission_settings_button" msgid="4582916817451973752">"Amasethingi"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"Bonisa ukusetshenziswa kwe-microphone kamsizi"</string>
<string name="unused_apps_category_title" msgid="2988455616845243901">"Amasethingi we-app engasetshenziswanga"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"Susa izimvume uma uhlelo lokusebenza lungasetshenziswa"</string>
@@ -438,6 +440,8 @@
<string name="default_apps_manage_domain_urls" msgid="6775566451561036069">"Ivula amalinki"</string>
<string name="default_apps_for_work" msgid="4970308943596201811">"Okuzenzakalelayo kokusebenza"</string>
<string name="default_apps_for_private_profile" msgid="2022024112144880785">"Okuzenzakalelayo kwendawo engasese"</string>
+ <string name="default_app_recommended" msgid="5669584821778942909">"Ilungiselelwe idivayisi"</string>
+ <string name="default_app_others" msgid="7793029848126079876">"Amanye"</string>
<string name="default_app_none" msgid="9084592086808194457">"Lutho"</string>
<string name="default_app_system_default" msgid="6218386768175513760">"(Okuzenzakalelayo kwesistimu)"</string>
<string name="default_app_no_apps" msgid="115720991680586885">"Azikho izinhlelo zokusebenza"</string>
@@ -674,6 +678,11 @@
<string name="allow_restricted_settings" msgid="8073000189478396881">"Vumela amasethingi akhawulelwe"</string>
<string name="enhanced_confirmation_dialog_title" msgid="7562437438040966351">"Amasethingi akhawulelwe"</string>
<string name="enhanced_confirmation_dialog_desc" msgid="5921240234843839219">"Ukuze uphephe, leli sethingi okwamanje alitholakali."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_title" msgid="5054064107559019689">"Ayikwazi ukuqedela isenzo phakathi nekholi"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_desc" msgid="5049619986796367451">"<xliff:g id="SETTING_DESCRIPTION">%1$s</xliff:g>\n\n Leli sethingi livinjiwe ukuze kuvikelwe idivayisi yakho nedatha"</string>
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc_prefix" msgid="7153600694011441796">"Amaqola angase azame ukufaka ama-app ayingozi ngokukucela ukuthi ufake ama-app angaziwa avela emthonjeni omusha."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc_prefix" msgid="1086282331085551407">"Amaqola angase azame ukulawula idivayisi yakho ngokukucela ukuthi uvumele ukufinyelela kwe-app."</string>
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc_prefix" msgid="8141411486179553156">"Amaqola angase azame ukulimaza idivayisi yakho ngaleli sethingi."</string>
<string name="enhanced_confirmation_dialog_title_permission" msgid="2149144789394238266">"I-app inqatshelwe ukufinyelela ku-<xliff:g id="PERMISSION_NAME">%1$s</xliff:g>"</string>
<string name="enhanced_confirmation_dialog_desc_permission" msgid="3150778951946468945">"I-app icele ukufinyelela emvumweni ezwelayo okungabeka imininingwane yakho siqu neyezimali engozini.<xliff:g id="ID_1">&lt;br&gt;&lt;br&gt;</xliff:g>Kungenzeka ukuthi i-app ingasebenzi kahle ngaphandle kwemvume enomkhawulo. &lt;a href=<xliff:g id="LEARN_MORE_LINK">%1$s</xliff:g>&gt;Funda ukuthi ungakuvumela kanjani ukufinyelela&lt;/a&gt;"</string>
<string name="enhanced_confirmation_dialog_title_role" msgid="1737023798483772780">"I-app iye yanqatshelwa ukufinyelela ukuze ibe yi-<xliff:g id="ROLE_NAME">%1$s</xliff:g> ezenzakalelayo"</string>
diff --git a/PermissionController/res/values/bools.xml b/PermissionController/res/values/bools.xml
index b5f33b081..4483fc48f 100644
--- a/PermissionController/res/values/bools.xml
+++ b/PermissionController/res/values/bools.xml
@@ -20,4 +20,6 @@
<bool name="is_at_least_t">false</bool>
<bool name="is_at_least_u">false</bool>
<bool name="is_at_least_v">false</bool>
+ <bool name="config_usePreferenceForAppPermissionSettings">false</bool>
+ <bool name="config_appPermissionFooterLinkPreferenceSummaryUnderlined">false</bool>
</resources>
diff --git a/PermissionController/res/values/config.xml b/PermissionController/res/values/config.xml
index 675368cba..657dc07dd 100644
--- a/PermissionController/res/values/config.xml
+++ b/PermissionController/res/values/config.xml
@@ -19,6 +19,15 @@
<bool name="config_showBrowserRole">true</bool>
<bool name="config_showDialerRole">true</bool>
<bool name="config_showSmsRole">true</bool>
+ <!--
+ ~ Semicolon separated list of packages that are recommended for the assistant role.
+ ~ <p>
+ ~ This follows the same format as config_defaultAssistant and also requires a signing
+ ~ certificate digest (separated by a colon from the package name) if the app is not a system
+ ~ app.
+ -->
+ <string name="config_recommendedAssistants"></string>
+
<bool name="config_useAlternativePermGroupSummary">false</bool>
<bool name="config_useWindowBlur">false</bool>
<bool name="config_useMaterial3PermissionGrantDialog">false</bool>
diff --git a/PermissionController/res/values/overlayable.xml b/PermissionController/res/values/overlayable.xml
index 9075fa67c..72896fcdb 100644
--- a/PermissionController/res/values/overlayable.xml
+++ b/PermissionController/res/values/overlayable.xml
@@ -48,7 +48,37 @@
<item type="style" name="PermissionFooterPreferenceTitleTextStyle" />
<item type="style" name="PermissionFooterPreferenceLearnMoreTextStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceRootLayoutStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceWidgetFrameStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceIconFrameStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceIconStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceTextContainerStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceTitleStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceSummaryContainerStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceSummaryStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceAppendixStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceExtraWidgetContainerStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceExtraWidgetDividerStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceExtraWidgetImageStyle" />
+ <item type="style" name="PermissionSelectorWithWidgetPreferenceWidgetRadioButton" />
+
+ <item type="style" name="PermissionTwoTargetPreferenceRootLayoutStyle" />
+ <item type="style" name="PermissionTwoTargetPreferenceTextContainerStyle" />
+ <item type="style" name="PermissionTwoTargetPreferenceTitleStyle" />
+ <item type="style" name="PermissionTwoTargetPreferenceSummaryStyle" />
+ <item type="style" name="PermissionTwoTargetPreferenceWidgetFrameStyle" />
+ <item type="style" name="PermissionTwoTargetPreferenceIconFrameStyle" />
+ <item type="style" name="PermissionTwoTargetPreferenceIconStyle" />
+ <item type="style" name="PermissionTwoTargetPreferenceDividerContainerStyle" />
+ <item type="style" name="PermissionTwoTargetPreferenceDividerStyle" />
+
+ <item type="style" name="AppPermissionFooterLinkPreferenceRootLayoutStyle" />
+ <item type="style" name="AppPermissionFooterLinkPreferenceTextLayoutStyle" />
+ <item type="style" name="AppPermissionFooterLinkPreferenceSummaryStyle" />
+
+ <item type="bool" name="config_usePreferenceForAppPermissionSettings" />
<item type="bool" name="config_permissionFooterPreferenceIconVisible" />
+ <item type="bool" name="config_appPermissionFooterLinkPreferenceSummaryUnderlined" />
<item type="dimen" name="permission_preference_app_icon_size" />
<item type="dimen" name="permission_preference_permission_group_icon_size" />
@@ -341,14 +371,15 @@
<item type="style" name="ThemeOverlay.PermissionSettings" />
<!-- END THEMES -->
- <!-- START VISIBILITY CONFIGS -->
- <!-- Assistant role uses: config_showDefaultAssistant -->
- <!-- Home role uses: config_showDefaultHome -->
- <!-- Emergency role uses: config_showDefaultEmergency -->
+ <!-- START ROLE CONFIGS -->
+ <!-- Assistant role uses android:bool/config_showDefaultAssistant -->
+ <!-- Home role uses android:bool/config_showDefaultHome -->
+ <!-- Emergency role uses android:bool/config_showDefaultEmergency -->
<item type="bool" name="config_showBrowserRole" />
<item type="bool" name="config_showDialerRole" />
<item type="bool" name="config_showSmsRole" />
- <!-- END VISIBILITY CONFIGS -->
+ <item type="string" name="config_recommendedAssistants" />
+ <!-- END ROLE CONFIGS -->
<!-- START CAR DIMENS -->
<item type="dimen" name="car_action_bar_height" />
@@ -431,21 +462,6 @@
<item type="style" name="AppDataSharingUpdateSettingsIcon" />
<!-- END SAFETY LABELS STYLE -->
- <!--START WEAR SPECIFIC FONT STRINGS -->
- <item type="string" name="wear_material_compose_display_1_font_family" />
- <item type="string" name="wear_material_compose_display_2_font_family" />
- <item type="string" name="wear_material_compose_display_3_font_family" />
- <item type="string" name="wear_material_compose_title_1_font_family" />
- <item type="string" name="wear_material_compose_title_2_font_family" />
- <item type="string" name="wear_material_compose_title_3_font_family" />
- <item type="string" name="wear_material_compose_body_1_font_family" />
- <item type="string" name="wear_material_compose_body_2_font_family" />
- <item type="string" name="wear_material_compose_button_font_family" />
- <item type="string" name="wear_material_compose_caption_1_font_family" />
- <item type="string" name="wear_material_compose_caption_2_font_family" />
- <item type="string" name="wear_material_compose_caption_3_font_family" />
- <!--END WEAR SPECIFIC FONT STRINGS -->
-
<!-- START ENHANCED CONFIRMATION DIALOG -->
<item type="style" name="Theme.EnhancedConfirmationDialog" />
<item type="style" name="Theme.EnhancedConfirmationDialogFragment" />
diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml
index d67a3582d..f5a997674 100644
--- a/PermissionController/res/values/strings.xml
+++ b/PermissionController/res/values/strings.xml
@@ -638,6 +638,12 @@
<!-- Text for linking to the page that shows the apps with a given permission [CHAR LIMIT=none] -->
<string name="app_permission_footer_permission_apps_link">See all apps with this permission</string>
+ <!-- Content description for button with an info icon [CHAR LIMIT=none] -->
+ <string name="app_permission_info_button">Information</string>
+
+ <!-- Content description for button with a settings icon [CHAR LIMIT=none] -->
+ <string name="app_permission_settings_button">Settings</string>
+
<!-- Label for the assistant mic display switch [CHAR LIMIT=60] -->
<string name="assistant_mic_label">Show assistant microphone usage</string>
@@ -1243,6 +1249,17 @@
<string name="role_wallet_request_title">Set <xliff:g id="app_name" example="Super Wallet">%1$s</xliff:g> as your default wallet app?</string>
<string name="role_wallet_request_description">No permissions needed</string>
+ <!-- Label for the RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY role. [DO NOT TRANSLATE] -->
+ <string name="role_for_testing_profile_group_exclusivity_label" translatable="false">Default test profile group exclusive role app</string>
+ <!-- Short label for the RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY role. [DO NOT TRANSLATE] -->
+ <string name="role_for_testing_profile_group_exclusivity_short_label" translatable="false">Test profile group exclusive role app</string>
+ <!-- Description for the RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY role. [DO NOT TRANSLATE] -->
+ <string name="role_for_testing_profile_group_exclusivity_description" translatable="false">Test profile group exclusive role apps are for tests only and should not be held by production apps.</string>
+ <!-- Request title for the RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY role. [DO NOT TRANSLATE] -->
+ <string name="role_for_testing_profile_group_exclusivity_request_title" translatable="false">Set <xliff:g id="app_name" example="Super test app">%1$s</xliff:g> as your default test profile group exclusive role app?</string>
+ <!-- Request description for the RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY role. [DO NOT TRANSLATE] -->
+ <string name="role_for_testing_profile_group_exclusivity_request_description" translatable="false">No permissions needed</string>
+
<!-- Subtitle for the application that is the current default application [CHAR LIMIT=30] -->
<string name="request_role_current_default">Current default</string>
@@ -1320,6 +1337,12 @@
<!-- Title for category of default apps for private profile [CHAR LIMIT=50] -->
<string name="default_apps_for_private_profile">Default for private space</string>
+ <!-- Title for category of apps that are optimized for the device [CHAR LIMIT=50] -->
+ <string name="default_app_recommended">Optimized for device</string>
+
+ <!-- Title for category of other apps [CHAR LIMIT=50] -->
+ <string name="default_app_others">Others</string>
+
<!-- Summary of a default app when there is no app set [CHAR LIMIT=60] -->
<string name="default_app_none">None</string>
@@ -1386,9 +1409,7 @@
<!-- Content for dialog shown when the user should confirm an incident / bug report.
[CHAR LIMIT=none] -->
- <string name="incident_report_dialog_text">"<xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is requesting to upload a bug report from this device taken on <xliff:g id="date" example="December 26, 2018">%2$s</xliff:g> at <xliff:g id="time" example="1:20 PM">%3$s</xliff:g>. Bug reports include personal information about your device or logged by apps, for example, user names, location data, device identifiers, and network information. Only share bug reports with people and apps you trust with this information.
-
-Allow <xliff:g id="app_name" example="Gmail">%4$s</xliff:g> to upload a bug report?"</string>
+ <string name="incident_report_dialog_text">"<xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is requesting to upload a bug report from this device taken on <xliff:g id="date" example="December 26, 2018">%2$s</xliff:g> at <xliff:g id="time" example="1:20 PM">%3$s</xliff:g>. Bug reports include personal information about your device or logged by apps, for example, user names, location data, device identifiers, and network information. Only share bug reports with people and apps you trust with this information.\n\nAllow <xliff:g id="app_name" example="Gmail">%4$s</xliff:g> to upload a bug report?"</string>
<!-- Content for dialog shown when there was an error parsing the incident / bug report.
[CHAR LIMIT=none] -->
@@ -2005,6 +2026,19 @@ Allow <xliff:g id="app_name" example="Gmail">%4$s</xliff:g> to upload a bug repo
<!--Content for dialog displayed to tell user that settings are blocked by setting restrictions [CHAR LIMIT=NONE] -->
<string name="enhanced_confirmation_dialog_desc">For your security, this setting is currently unavailable.</string>
+ <!--Title for dialog displayed to tell user that settings are blocked due to the phone state (such as being in a call with an unknown number) [CHAR LIMIT=50] -->
+ <string name="enhanced_confirmation_phone_state_dialog_title">Can\u2019t complete action during call</string>
+ <!--Content for dialog displayed to tell user that settings are blocked due to the phone state (such as being in a call with an unknown number) [CHAR LIMIT=NONE] -->
+ <string name="enhanced_confirmation_phone_state_dialog_desc">This setting is blocked to protect your device and data.<xliff:g id="scam_use_setting_description" example="scammers may ask you to allow apps to install other apps">%1$s</xliff:g></string>
+
+ <!--Content explaining that the "install other apps" setting is blocked due to the phone state in a dialog displayed to the user [CHAR LIMIT=NONE] -->
+ <string name="enhanced_confirmation_phone_state_dialog_install_desc"><xliff:g id="empty_line">\n\n</xliff:g>Scammers may try to install harmful apps by asking you to install unknown apps from a new source.</string>
+
+ <!--Content explaining that the "enable accessibility service" setting is blocked due to the phone state in a dialog displayed to the user [CHAR LIMIT=NONE] -->
+ <string name="enhanced_confirmation_phone_state_dialog_a11y_desc"><xliff:g id="empty_line">\n\n</xliff:g>Scammers may try to take control of your device by asking you to allow accessibility access for an app.</string>
+ <!--Content explaining that a generic setting is blocked due to the phone state in a dialog displayed to the user. currently empty [CHAR LIMIT=NONE] -->
+ <string name="enhanced_confirmation_phone_state_dialog_generic_desc" />
+
<!--Title for dialog displayed to tell user that permissions are blocked by setting restrictions [CHAR LIMIT=50] -->
<string name="enhanced_confirmation_dialog_title_permission">App was denied access to <xliff:g id="permission_name" example="contacts">%1$s</xliff:g></string>
<!--Content for dialog displayed to tell user that settings are blocked by setting restrictions [CHAR LIMIT=NONE] -->
diff --git a/PermissionController/res/xml/app_permission.xml b/PermissionController/res/xml-v35/app_permission.xml
index 5e6857185..87315815d 100644
--- a/PermissionController/res/xml/app_permission.xml
+++ b/PermissionController/res/xml-v35/app_permission.xml
@@ -19,46 +19,46 @@
android:key="app_permission_button_category"
android:title="@string/app_permission_header">
- <com.android.permissioncontroller.permission.ui.handheld.PermissionSelectorWithWidgetPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.PermissionSelectorWithWidgetPreference
android:key="app_permission_allow_radio_button"
android:title="@string/app_permission_button_allow"
app:checkboxId="@+id/allow_radio_button" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionSelectorWithWidgetPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.PermissionSelectorWithWidgetPreference
android:key="app_permission_allow_always_radio_button"
android:title="@string/app_permission_button_allow_always"
app:checkboxId="@+id/allow_always_radio_button"
app:isPreferenceVisible="false" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionSelectorWithWidgetPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.PermissionSelectorWithWidgetPreference
android:key="app_permission_allow_foreground_only_radio_button"
android:title="@string/app_permission_button_allow_foreground"
app:checkboxId="@+id/allow_foreground_only_radio_button" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionSelectorWithWidgetPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.PermissionSelectorWithWidgetPreference
android:key="app_permission_select_photos_radio_button"
android:title="@string/app_permission_button_allow_limited_access"
app:checkboxId="@+id/select_radio_button"
app:extraWidgetIcon="@drawable/ic_edit"
app:extraWidgetId="@+id/edit_selected_button" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionSelectorWithWidgetPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.PermissionSelectorWithWidgetPreference
android:key="app_permission_ask_one_time_radio_button"
android:title="@string/app_permission_button_ask"
app:checkboxId="@+id/ask_one_time_radio_button" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionSelectorWithWidgetPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.PermissionSelectorWithWidgetPreference
android:key="app_permission_ask_radio_button"
android:text="@string/app_permission_button_ask"
android:title="@string/app_permission_button_ask"
app:checkboxId="@+id/ask_radio_button" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionSelectorWithWidgetPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.PermissionSelectorWithWidgetPreference
android:key="app_permission_deny_radio_button"
android:title="@string/app_permission_button_deny"
app:checkboxId="@+id/deny_radio_button" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionSelectorWithWidgetPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.PermissionSelectorWithWidgetPreference
android:key="app_permission_deny_foreground_radio_button"
android:title="@string/app_permission_button_deny"
app:checkboxId="@+id/deny_foreground_radio_button" />
@@ -71,7 +71,7 @@
android:title="@string/app_permission_location_accuracy"
app:isPreferenceVisible="false" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionTwoTargetPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.PermissionTwoTargetPreference
android:key="app_permission_details"
android:selectable="false"
android:summary="@string/permission_summary_enabled_system_fixed"
@@ -79,26 +79,24 @@
app:iconSpaceReserved="true"
app:isPreferenceVisible="false" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.AppPermissionFooterLinkPreference
android:key="app_permission_footer_link_1"
android:summary="@string/app_permission_footer_app_permissions_link" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionPreference
+ <com.android.permissioncontroller.permission.ui.handheld.v36.AppPermissionFooterLinkPreference
android:key="app_permission_footer_link_2"
android:summary="@string/app_permission_footer_permission_apps_link" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionPreference
+ <com.android.permissioncontroller.permission.ui.handheld.PermissionFooterPreference
android:key="app_permission_footer_storage_special_app_access"
android:icon="@drawable/ic_info_outline"
- android:selectable="false"
- android:summary="@string/app_permission_footer_special_file_access"
+ android:title="@string/app_permission_footer_special_file_access"
app:isPreferenceVisible="false" />
- <com.android.permissioncontroller.permission.ui.handheld.PermissionPreference
+ <com.android.permissioncontroller.permission.ui.handheld.PermissionFooterPreference
android:key="app_permission_additional_info"
android:icon="@drawable/ic_info_outline"
- android:selectable="false"
- android:summary="@string/exempt_info_label"
+ android:title="@string/exempt_info_label"
app:isPreferenceVisible="false" />
</PreferenceScreen>
diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml
index 1d01f8208..4ba524462 100644
--- a/PermissionController/res/xml/roles.xml
+++ b/PermissionController/res/xml/roles.xml
@@ -107,6 +107,7 @@
defaultHolders="config_defaultAssistant"
description="@string/role_assistant_description"
exclusive="true"
+ exclusivity="user"
fallBackToDefaultHolder="true"
showNone="true"
label="@string/role_assistant_label"
@@ -154,9 +155,13 @@
minSdkVersion="35" optionalMinSdkVersion="34" />
<permission name="android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE"
minSdkVersion="35" />
+ <permission name="android.permission.READ_SYSTEM_PREFERENCES"
+ featureFlag="com.android.settingslib.flags.Flags.settingsCatalyst" />
</permissions>
<app-op-permissions>
<app-op-permission name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <app-op-permission name="android.permission.WRITE_SYSTEM_PREFERENCES"
+ featureFlag="com.android.settingslib.flags.Flags.writeSystemPreferencePermissionEnabled" />
</app-op-permissions>
</role>
@@ -173,6 +178,7 @@
defaultHolders="config_defaultBrowser"
description="@string/role_browser_description"
exclusive="true"
+ exclusivity="user"
label="@string/role_browser_label"
overrideUserWhenGranting="true"
requestDescription="@string/role_browser_request_description"
@@ -216,6 +222,7 @@
defaultHolders="config_defaultDialer"
description="@string/role_dialer_description"
exclusive="true"
+ exclusivity="user"
fallBackToDefaultHolder="true"
label="@string/role_dialer_label"
overrideUserWhenGranting="true"
@@ -304,6 +311,7 @@
defaultHolders="config_defaultSms"
description="@string/role_sms_description"
exclusive="true"
+ exclusivity="user"
label="@string/role_sms_label"
overrideUserWhenGranting="true"
requestDescription="@string/role_sms_request_description"
@@ -394,6 +402,7 @@
behavior="EmergencyRoleBehavior"
description="@string/role_emergency_description"
exclusive="true"
+ exclusivity="user"
label="@string/role_emergency_label"
overrideUserWhenGranting="true"
requestDescription="@string/role_emergency_request_description"
@@ -426,6 +435,7 @@
behavior="HomeRoleBehavior"
description="@string/role_home_description"
exclusive="true"
+ exclusivity="user"
label="@string/role_home_label"
overrideUserWhenGranting="true"
requestDescription="@string/role_home_request_description"
@@ -472,6 +482,7 @@
defaultHolders="config_defaultCallRedirection"
description="@string/role_call_redirection_description"
exclusive="true"
+ exclusivity="user"
label="@string/role_call_redirection_label"
overrideUserWhenGranting="true"
requestDescription="@string/role_call_redirection_request_description"
@@ -493,6 +504,7 @@
defaultHolders="config_defaultCallScreening"
description="@string/role_call_screening_description"
exclusive="true"
+ exclusivity="user"
label="@string/role_call_screening_label"
overrideUserWhenGranting="true"
requestDescription="@string/role_call_screening_request_description"
@@ -518,6 +530,7 @@
name="android.app.role.SYSTEM_GALLERY"
defaultHolders="config_systemGallery"
exclusive="true"
+ exclusivity="user"
static="true"
systemOnly="true"
visible="false">
@@ -537,6 +550,7 @@
behavior="v31.AutomotiveRoleBehavior"
defaultHolders="config_systemAutomotiveCluster"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -554,6 +568,7 @@
behavior="v31.CompanionDeviceWatchRoleBehavior"
description="@string/role_watch_description"
exclusive="false"
+ exclusivity="none"
minSdkVersion="31"
systemOnly="false"
visible="false">
@@ -582,6 +597,7 @@
name="android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION"
defaultHolders="config_systemAutomotiveProjection"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -619,13 +635,13 @@
behavior="v31.SystemShellRoleBehavior"
defaultHolders="config_systemShell"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
visible="false">
<permissions>
<!-- Used for CTS testing -->
- <permission name="android.permission.CREATE_VIRTUAL_DEVICE" minSdkVersion="33" />
<permission name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE" />
<permission name="android.permission.ACCESS_AMBIENT_CONTEXT_EVENT" minSdkVersion="33"/>
<permission name="android.permission.PERFORM_IMS_SINGLE_REGISTRATION" />
@@ -642,8 +658,12 @@
minSdkVersion="33" />
<permission name="android.permission.MANAGE_SAFETY_CENTER"
minSdkVersion="33" />
+ <permission name="android.permission.ASSOCIATE_COMPANION_DEVICES" minSdkVersion="36" />
+ <permission name="android.permission.CREATE_VIRTUAL_DEVICE" minSdkVersion="33" />
<permission name="android.permission.ADD_TRUSTED_DISPLAY" minSdkVersion="33" />
<permission name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY" minSdkVersion="33" />
+ <permission name="android.permission.ADD_MIRROR_DISPLAY"
+ featureFlag="android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole" />
<permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
minSdkVersion="33" />
<permission name="android.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT"
@@ -706,17 +726,19 @@
minSdkVersion="35" />
<permission name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING"
minSdkVersion="35" />
- <permission name="android.permission.EXECUTE_APP_FUNCTIONS"
- featureFlag="android.app.appfunctions.flags.Flags.enableAppFunctionManager" />
- <permission name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED"
- featureFlag="android.app.appfunctions.flags.Flags.enableAppFunctionManager" />
</permissions>
+ <!-- App ops needed to allow background access to audio APIs for CTS -->
+ <app-ops>
+ <app-op name="android:control_audio" mode="allowed" minSdkVersion="36"/>
+ <app-op name="android:control_audio_partial" mode="allowed" minSdkVersion="36"/>
+ </app-ops>
</role>
<role
name="android.app.role.SYSTEM_CONTACTS"
defaultHolders="config_systemContacts"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -733,6 +755,7 @@
allowBypassingQualification="true"
defaultHolders="config_systemSpeechRecognizer"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -754,6 +777,7 @@
name="android.app.role.SYSTEM_WIFI_COEX_MANAGER"
defaultHolders="config_systemWifiCoexManager"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -768,6 +792,7 @@
name="android.app.role.SYSTEM_WELLBEING"
defaultHolders="config_systemWellbeing"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -796,6 +821,7 @@
behavior="v31.TelevisionRoleBehavior"
defaultHolders="config_systemTelevisionNotificationHandler"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -814,6 +840,7 @@
name="android.app.role.SYSTEM_COMPANION_DEVICE_PROVIDER"
defaultHolders="config_systemCompanionDeviceProvider"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -835,7 +862,8 @@
~ In addition, packages MUST NOT:
~ - Request INTERNET permission. Instead packages MUST access the internet through
~ well-defined APIs in an open source project.
- ~ - Perform direct binds to other applications, except the following system packages:
+ ~ - Perform direct binds to other applications, except the following system packages or
+ ~ other preloaded packages conforming with the requirements here:
~ - Bluetooth
~ - Contacts
~ - Media
@@ -849,6 +877,7 @@
name="android.app.role.SYSTEM_UI_INTELLIGENCE"
defaultHolders="config_systemUiIntelligence"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -873,8 +902,8 @@
<permission name="android.permission.READ_SMS" minSdkVersion="33" />
<permission name="android.permission.READ_PEOPLE_DATA" />
<permission name="android.permission.READ_GLOBAL_APP_SEARCH_DATA" />
- <permission name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED"
- featureFlag="android.app.appfunctions.flags.Flags.enableAppFunctionManager" />
+ <permission name="android.permission.READ_BLOCKED_NUMBERS"
+ featureFlag="android.permission.flags.Flags.grantReadBlockedNumbersToSystemUiIntelligence" />
</permissions>
</role>
@@ -904,6 +933,7 @@
name="android.app.role.SYSTEM_AMBIENT_AUDIO_INTELLIGENCE"
defaultHolders="config_systemAmbientAudioIntelligence"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -950,6 +980,7 @@
name="android.app.role.SYSTEM_AUDIO_INTELLIGENCE"
defaultHolders="config_systemAudioIntelligence"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -996,6 +1027,7 @@
name="android.app.role.SYSTEM_NOTIFICATION_INTELLIGENCE"
defaultHolders="config_systemNotificationIntelligence"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -1007,6 +1039,8 @@
<!-- If this role holder has a NotificationListenerService, let that service receive
notifications with sensitive content unredacted-->
<permission name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" minSdkVersion="35"/>
+ <permission name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE" minSdkVersion="36"
+ featureFlag="android.permission.flags.Flags.textClassifierChoiceApiEnabled"/>
</permissions>
<app-ops>
<app-op name="android:receive_sensitive_notifications" mode="allowed" minSdkVersion="35"/>
@@ -1038,6 +1072,7 @@
name="android.app.role.SYSTEM_TEXT_INTELLIGENCE"
defaultHolders="config_systemTextIntelligence"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -1078,6 +1113,7 @@
name="android.app.role.SYSTEM_VISUAL_INTELLIGENCE"
defaultHolders="config_systemVisualIntelligence"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -1103,6 +1139,7 @@
name="android.app.role.SYSTEM_DOCUMENT_MANAGER"
behavior="v33.DocumentManagerRoleBehavior"
exclusive="true"
+ exclusivity="user"
minSdkVersion="33"
static="true"
systemOnly="true"
@@ -1135,6 +1172,7 @@
allowBypassingQualification="true"
defaultHolders="config_systemActivityRecognizer"
exclusive="false"
+ exclusivity="none"
static="true"
systemOnly="true"
visible="false">
@@ -1154,6 +1192,7 @@
name="android.app.role.SYSTEM_UI"
defaultHolders="config_systemUi"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -1180,6 +1219,7 @@
behavior="v31.TelevisionRoleBehavior"
defaultHolders="config_systemTelevisionRemoteService"
exclusive="true"
+ exclusivity="user"
minSdkVersion="31"
static="true"
systemOnly="true"
@@ -1195,10 +1235,10 @@
-->
<role
name="android.app.role.COMPANION_DEVICE_APP_STREAMING"
- allowBypassingQualification="true"
behavior="v33.CompanionDeviceAppStreamingRoleBehavior"
description="@string/role_app_streaming_description"
exclusive="false"
+ exclusivity="none"
minSdkVersion="33"
systemOnly="true"
visible="false">
@@ -1207,7 +1247,9 @@
<permission-set name="virtual_device" />
<!-- For capturing audio from the app on the device. -->
<permission name="android.permission.RECORD_AUDIO" />
-
+ <permission
+ name="android.permission.ADD_MIRROR_DISPLAY"
+ featureFlag="android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole" />
<!--TODO(b/201605314) For calling Telecom framework API for audio streaming-->
<!--<permission name="android.permission.PROVIDE_CALL_ENDPOINTS" />-->
</permissions>
@@ -1223,6 +1265,7 @@
behavior="v33.CompanionDeviceComputerRoleBehavior"
description="@string/role_companion_device_computer_description"
exclusive="false"
+ exclusivity="none"
minSdkVersion="33"
systemOnly="true"
visible="false">
@@ -1242,6 +1285,7 @@
name="android.app.role.COMPANION_DEVICE_GLASSES"
behavior="v34.CompanionDeviceGlassesRoleBehavior"
exclusive="false"
+ exclusivity="none"
minSdkVersion="34"
systemOnly="false"
visible="false">
@@ -1266,14 +1310,17 @@
<role
name="android.app.role.COMPANION_DEVICE_NEARBY_DEVICE_STREAMING"
- allowBypassingQualification="true"
+ behavior="v34.CompanionDeviceNearbyDeviceStreamingRoleBehavior"
exclusive="false"
+ exclusivity="none"
minSdkVersion="34"
systemOnly="true"
visible="false">
<permissions>
<permission-set name="nearby_devices" />
<permission-set name="virtual_device" />
+ <permission-set name="notifications"
+ featureFlag="android.companion.virtualdevice.flags.Flags.notificationsForDeviceStreaming" />
</permissions>
</role>
@@ -1281,6 +1328,7 @@
name="android.app.role.SYSTEM_SUPERVISION"
defaultHolders="config_systemSupervision"
exclusive="true"
+ exclusivity="user"
minSdkVersion="33"
static="true"
systemOnly="true"
@@ -1291,6 +1339,46 @@
<permission name="android.permission.MANAGE_DEFAULT_APPLICATIONS" minSdkVersion="34"/>
<permission name="android.permission.SUSPEND_APPS"/>
<permission name="android.permission.SYSTEM_APPLICATION_OVERLAY"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_DISPLAY"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_FUN"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_KEYGUARD"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_LOCATION"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_LOCK"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_PACKAGE_STATE"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_TIME"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
+ <permission name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
+ featureFlag="android.permission.flags.Flags.supervisionRolePermissionUpdateEnabled"/>
</permissions>
</role>
@@ -1303,6 +1391,7 @@
behavior="v33.DevicePolicyManagementRoleBehavior"
defaultHolders="config_devicePolicyManagement"
exclusive="true"
+ exclusivity="user"
minSdkVersion="33"
static="true"
systemOnly="false"
@@ -1408,6 +1497,10 @@
<permission name="android.permission.MANAGE_DEVICE_POLICY_DISPLAY" minSdkVersion="35" />
<permission name="android.permission.MANAGE_DEVICE_POLICY_LOCALE" minSdkVersion="35" />
<permission name="android.permission.MANAGE_DEVICE_POLICY_SMS" minSdkVersion="35" />
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_APP_FUNCTIONS"
+ featureFlag="android.app.appfunctions.flags.Flags.enableAppFunctionManager" />
+ <permission name="android.permission.MANAGE_DEFAULT_APPLICATIONS" minSdkVersion="36"
+ featureFlag="com.android.permission.flags.Flags.crossUserRoleEnabled" />
</permissions>
</role>
@@ -1415,6 +1508,7 @@
name="android.app.role.SYSTEM_APP_PROTECTION_SERVICE"
defaultHolders="config_systemAppProtectionService"
exclusive="true"
+ exclusivity="user"
minSdkVersion="33"
static="true"
systemOnly="true"
@@ -1443,6 +1537,7 @@
behavior="v31.AutomotiveRoleBehavior"
defaultHolders="config_systemAutomotiveCalendarSyncManager"
exclusive="true"
+ exclusivity="user"
minSdkVersion="33"
static="true"
systemOnly="true"
@@ -1464,6 +1559,7 @@
defaultHolders="config_defaultAutomotiveNavigation"
description="@string/role_automotive_navigation_description"
exclusive="true"
+ exclusivity="user"
label="@string/role_automotive_navigation_label"
minSdkVersion="33"
overrideUserWhenGranting="true"
@@ -1537,6 +1633,7 @@
name="android.app.role.SYSTEM_SETTINGS_INTELLIGENCE"
defaultHolders="config_systemSettingsIntelligence"
exclusive="true"
+ exclusivity="user"
minSdkVersion="33"
static="true"
systemOnly="true"
@@ -1554,6 +1651,7 @@
name="android.app.role.SYSTEM_BLUETOOTH_STACK"
defaultHolders="config_systemBluetoothStack"
exclusive="true"
+ exclusivity="user"
minSdkVersion="33"
static="true"
systemOnly="true"
@@ -1578,6 +1676,7 @@
<role
name="android.app.role.FINANCED_DEVICE_KIOSK"
exclusive="true"
+ exclusivity="user"
minSdkVersion="34"
visible="false">
<permissions>
@@ -1593,6 +1692,7 @@
name="android.app.role.SYSTEM_FINANCED_DEVICE_CONTROLLER"
defaultHolders="config_systemFinancedDeviceController"
exclusive="true"
+ exclusivity="user"
minSdkVersion="34"
static="true"
systemOnly="true"
@@ -1620,6 +1720,7 @@
behavior="v33.SystemWearHealthServiceRoleBehavior"
defaultHolders="config_systemWearHealthService"
exclusive="true"
+ exclusivity="user"
minSdkVersion="33"
static="true"
systemOnly="true"
@@ -1629,6 +1730,12 @@
<permission-set name="location" />
<permission name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<permission name="android.permission.ACTIVITY_RECOGNITION" />
+ <permission
+ name="android.permission.health.READ_HEART_RATE"
+ featureFlag="android.permission.flags.Flags.replaceBodySensorPermissionEnabled" />
+ <permission
+ name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND"
+ featureFlag="android.permission.flags.Flags.replaceBodySensorPermissionEnabled" />
</permissions>
</role>
@@ -1641,6 +1748,7 @@
defaultHolders="config_defaultNotes"
description="@string/role_notes_description"
exclusive="true"
+ exclusivity="user"
label="@string/role_notes_label"
minSdkVersion="34"
overrideUserWhenGranting="true"
@@ -1682,6 +1790,7 @@
allowBypassingQualification="true"
defaultHolders="config_systemCallStreaming"
exclusive="true"
+ exclusivity="user"
minSdkVersion="34"
static="true"
systemOnly="true"
@@ -1704,6 +1813,7 @@
behavior="v35.RetailDemoRoleBehavior"
defaultHolders="config_defaultRetailDemo"
exclusive="true"
+ exclusivity="user"
minSdkVersion="35"
static="true"
visible="false">
@@ -1730,6 +1840,7 @@
defaultHolders="config_defaultWallet"
description="@string/role_wallet_description"
exclusive="true"
+ exclusivity="profileGroup"
label="@string/role_wallet_label"
minSdkVersion="35"
overrideUserWhenGranting="true"
@@ -1740,5 +1851,104 @@
shortLabel="@string/role_wallet_short_label"
uiBehavior="v35.WalletRoleUiBehavior"/>
+ <role
+ name="android.app.role.SYSTEM_DEPENDENCY_INSTALLER"
+ allowBypassingQualification="true"
+ defaultHolders="config_systemDependencyInstaller"
+ exclusive="true"
+ exclusivity="user"
+ featureFlag="android.content.pm.Flags.sdkDependencyInstaller"
+ static="true"
+ systemOnly="true"
+ visible="false">
+ <required-components>
+ <service permission="android.permission.BIND_DEPENDENCY_INSTALLER">
+ <intent-filter>
+ <action name="android.content.pm.action.INSTALL_DEPENDENCY" />
+ </intent-filter>
+ </service>
+ </required-components>
+ <permissions>
+ <permission name="android.permission.ACCESS_SHARED_LIBRARIES" />
+ <permission name="android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES" />
+ </permissions>
+ </role>
+
+ <!---
+ ~ A role for testing cross-user roles (exclusivity="profileGroup"). This should never be used
+ ~ to gate any actual functionality.
+ -->
+ <role
+ name="android.app.role.RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY"
+ behavior="v36.ReservedForTestingProfileGroupExclusivityRoleBehavior"
+ description="@string/role_for_testing_profile_group_exclusivity_description"
+ exclusive="true"
+ exclusivity="profileGroup"
+ fallBackToDefaultHolder="true"
+ featureFlag="com.android.permission.flags.Flags.crossUserRoleEnabled"
+ label="@string/role_for_testing_profile_group_exclusivity_label"
+ minSdkVersion="36"
+ requestable="true"
+ requestDescription="@string/role_for_testing_profile_group_exclusivity_request_description"
+ requestTitle="@string/role_for_testing_profile_group_exclusivity_request_title"
+ shortLabel="@string/role_for_testing_profile_group_exclusivity_short_label"
+ showNone="true"
+ uiBehavior="v36.ReservedForTestingProfileGroupExclusivityRoleUiBehavior"
+ visible="false"/>
+
+ <!---
+ ~ A role for the vendor package that provides privacy-preserving intelligent processor for
+ ~ vendor specific features.
+ ~
+ ~ A package holding this role MUST comply with requirements outlined in the Android CDD
+ ~ section "9.8.6 Content Capture".
+ ~ Example link for Android 15:
+ ~ https://source.android.com/docs/compatibility/15/android-15-cdd#986_os-level_and_ambient_data
+ ~
+ ~ In addition, packages MUST NOT:
+ ~ - Request INTERNET permission. Instead packages MUST access the internet through
+ ~ well-defined APIs in an open source project.
+ ~ - Perform direct binds to other applications, except the following system packages or
+ ~ other preloaded packages conforming with the requirements here:
+ ~ - Bluetooth
+ ~ - Contacts
+ ~ - Media
+ ~ - Telephony
+ ~ - System UI
+ ~ - Component providing internet APIs (see above)
+ ~ To achieve this packages MUST set up explicit <allow-association> configuration in the
+ ~ system config.
+ -->
+ <role
+ name="android.app.role.SYSTEM_VENDOR_INTELLIGENCE"
+ defaultHolders="config_systemVendorIntelligence"
+ exclusive="true"
+ exclusivity="user"
+ featureFlag="android.permission.flags.Flags.systemVendorIntelligenceRoleEnabled"
+ static="true"
+ systemOnly="true"
+ visible="false">
+ <permissions>
+ <permission name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <permission name="android.permission.ACCESS_COARSE_LOCATION" />
+ <permission name="android.permission.ACCESS_FINE_LOCATION" />
+ <permission name="android.permission.READ_MEDIA_IMAGES" />
+ <permission name="android.permission.READ_MEDIA_VIDEO" />
+ </permissions>
+ </role>
+
+ <role
+ name="android.app.role.COMPANION_DEVICE_SENSOR_DEVICE_STREAMING"
+ exclusive="false"
+ exclusivity="none"
+ featureFlag="android.companion.virtualdevice.flags.Flags.enableLimitedVdmRole"
+ systemOnly="true"
+ visible="false">
+ <permissions>
+ <permission-set name="nearby_devices" />
+ <permission-set name="notifications" />
+ <permission name="android.permission.CREATE_VIRTUAL_DEVICE"/>
+ </permissions>
+ </role>
</roles>
diff --git a/PermissionController/role-controller/Android.bp b/PermissionController/role-controller/Android.bp
index 166823b08..f89fd140c 100644
--- a/PermissionController/role-controller/Android.bp
+++ b/PermissionController/role-controller/Android.bp
@@ -17,6 +17,9 @@ package {
default_applicable_licenses: ["Android-Apache-2.0"],
}
+// Any place that role-controller is added as a dependency must also include
+// "com.android.permission.flags-aconfig-java" or
+// "com.android.permission.flags-aconfig-java-export",
java_library {
name: "role-controller",
srcs: [
@@ -24,13 +27,18 @@ java_library {
],
libs: [
"androidx.annotation_annotation",
+ "com.android.permission.flags-aconfig-java",
+ "framework-annotations-lib",
],
static_libs: [
"modules-utils-build_system",
+ "aconfig_settingslib_exported_flags_java_lib",
"android.app.appfunctions.exported-flags-aconfig-java",
"android.companion.virtualdevice.flags-aconfig-java-export",
+ "android.content.pm.flags-aconfig-java-export",
"android.permission.flags-aconfig-java-export",
"android.os.flags-aconfig-java-export",
+ "device_policy_aconfig_flags_java_export",
],
apex_available: [
"com.android.permission",
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java
index d025d1b03..a99b1ade2 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java
@@ -17,7 +17,6 @@
package com.android.role.controller.behavior;
import android.app.ActivityManager;
-import android.app.appfunctions.flags.Flags;
import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
@@ -37,18 +36,15 @@ import android.util.Xml;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.role.controller.model.Permissions;
import com.android.role.controller.model.Role;
import com.android.role.controller.model.RoleBehavior;
import com.android.role.controller.model.VisibilityMixin;
-import com.android.role.controller.util.PackageUtils;
import com.android.role.controller.util.UserUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -59,14 +55,6 @@ public class AssistantRoleBehavior implements RoleBehavior {
private static final String LOG_TAG = AssistantRoleBehavior.class.getSimpleName();
- /**
- * Permissions to be granted if the application fulfilling the assistant role is also a system
- * or preinstalled app.
- */
- private static final List<String> SYSTEM_ASSISTANT_PERMISSIONS = Arrays.asList(
- android.Manifest.permission.EXECUTE_APP_FUNCTIONS
- );
-
@Override
public void onRoleAddedAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
@@ -88,28 +76,6 @@ public class AssistantRoleBehavior implements RoleBehavior {
return !UserUtils.isProfile(user, context);
}
- @Override
- public void grantAsUser(@NonNull Role role, @NonNull String packageName,
- @NonNull UserHandle user, @NonNull Context context) {
- if (Flags.enableAppFunctionManager()) {
- if (PackageUtils.isSystemPackageAsUser(packageName, user, context)) {
- Permissions.grantAsUser(packageName, SYSTEM_ASSISTANT_PERMISSIONS, false, false,
- true, false, false, user, context);
- }
- }
- }
-
- @Override
- public void revokeAsUser(@NonNull Role role, @NonNull String packageName,
- @NonNull UserHandle user, @NonNull Context context) {
- if (Flags.enableAppFunctionManager()) {
- if (PackageUtils.isSystemPackageAsUser(packageName, user, context)) {
- Permissions.revokeAsUser(packageName, SYSTEM_ASSISTANT_PERMISSIONS, true, false,
- false, user, context);
- }
- }
- }
-
@Nullable
@Override
public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user,
@@ -230,7 +196,7 @@ public class AssistantRoleBehavior implements RoleBehavior {
}
@Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
return VisibilityMixin.isVisible("config_showDefaultAssistant", false, user, context);
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java
index 0261e1eee..95b86f0ca 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java
@@ -157,7 +157,7 @@ public class BrowserRoleBehavior implements RoleBehavior {
}
@Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
return VisibilityMixin.isVisible("config_showBrowserRole", true, user, context);
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java
index 153f4a6b4..aa0902136 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java
@@ -80,7 +80,7 @@ public class DialerRoleBehavior implements RoleBehavior {
}
@Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
return VisibilityMixin.isVisible("config_showDialerRole", true, user, context);
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java
index f19c86596..4a52b0b03 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java
@@ -71,7 +71,7 @@ public class EmergencyRoleBehavior implements RoleBehavior {
}
@Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
return VisibilityMixin.isVisible("config_showDefaultEmergency", false, user, context);
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java
index 8c1446b50..b226674a0 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java
@@ -186,7 +186,7 @@ public class HomeRoleBehavior implements RoleBehavior {
}
@Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
return VisibilityMixin.isVisible("config_showDefaultHome", false, user, context);
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java
index 4aff8a163..ee429c10e 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java
@@ -133,7 +133,7 @@ public class SmsRoleBehavior implements RoleBehavior {
}
@Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
return VisibilityMixin.isVisible("config_showSmsRole", true, user, context);
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/v33/CompanionDeviceAppStreamingRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/v33/CompanionDeviceAppStreamingRoleBehavior.java
index 275387d57..85c4be569 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/v33/CompanionDeviceAppStreamingRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/v33/CompanionDeviceAppStreamingRoleBehavior.java
@@ -20,7 +20,9 @@ import android.content.Context;
import android.os.UserHandle;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.android.modules.utils.build.SdkLevel;
import com.android.role.controller.model.Role;
import com.android.role.controller.model.RoleBehavior;
import com.android.role.controller.util.NotificationUtils;
@@ -46,4 +48,10 @@ public class CompanionDeviceAppStreamingRoleBehavior implements RoleBehavior {
NotificationUtils.revokeNotificationAccessForPackageAsUser(packageName, user, context);
}
}
+
+ @Override
+ @Nullable
+ public Boolean shouldAllowBypassingQualification(@NonNull Role role, @NonNull Context context) {
+ return !SdkLevel.isAtLeastB();
+ }
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/v34/CompanionDeviceNearbyDeviceStreamingRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/v34/CompanionDeviceNearbyDeviceStreamingRoleBehavior.java
new file mode 100644
index 000000000..f941ec68d
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/v34/CompanionDeviceNearbyDeviceStreamingRoleBehavior.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.behavior.v34;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+
+/**
+ * Class for behavior of the "Nearby Device Streaming" Companion device profile role.
+ */
+public class CompanionDeviceNearbyDeviceStreamingRoleBehavior implements RoleBehavior {
+
+ @Override
+ @Nullable
+ public Boolean shouldAllowBypassingQualification(@NonNull Role role, @NonNull Context context) {
+ return !SdkLevel.isAtLeastB();
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/v35/WalletRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/v35/WalletRoleBehavior.java
index d01e59456..aed71564d 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/v35/WalletRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/v35/WalletRoleBehavior.java
@@ -41,6 +41,7 @@ import com.android.modules.utils.build.SdkLevel;
import com.android.role.controller.model.Role;
import com.android.role.controller.model.RoleBehavior;
import com.android.role.controller.util.CollectionUtils;
+import com.android.role.controller.util.RoleFlags;
import com.android.role.controller.util.UserUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -62,8 +63,25 @@ public class WalletRoleBehavior implements RoleBehavior {
@Override
public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
- return SdkLevel.isAtLeastV() && Flags.walletRoleEnabled()
- && !UserUtils.isProfile(user, context);
+ if (!(SdkLevel.isAtLeastV() && Flags.walletRoleEnabled())) {
+ return false;
+ }
+
+ if (Flags.walletRoleCrossUserEnabled() && RoleFlags.isProfileGroupExclusivityAvailable()) {
+ return !UserUtils.isPrivateProfile(user, context);
+ } else {
+ return !UserUtils.isProfile(user, context);
+ }
+ }
+
+ @Nullable
+ @Override
+ public Integer getExclusivity() {
+ if (Flags.walletRoleCrossUserEnabled() && RoleFlags.isProfileGroupExclusivityAvailable()) {
+ return Role.EXCLUSIVITY_PROFILE_GROUP;
+ }
+
+ return Role.EXCLUSIVITY_USER;
}
@Nullable
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/v36/ReservedForTestingProfileGroupExclusivityRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/v36/ReservedForTestingProfileGroupExclusivityRoleBehavior.java
new file mode 100644
index 000000000..e50f33ffd
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/v36/ReservedForTestingProfileGroupExclusivityRoleBehavior.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.behavior.v36;
+
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.PackageUtils;
+import com.android.role.controller.util.RoleFlags;
+import com.android.role.controller.util.UserUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RequiresApi(Build.VERSION_CODES.BAKLAVA)
+public class ReservedForTestingProfileGroupExclusivityRoleBehavior implements RoleBehavior {
+ @Nullable
+ @Override
+ public List<String> getDefaultHoldersAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ RoleManager userRoleManager = userContext.getSystemService(RoleManager.class);
+ return userRoleManager.getDefaultHoldersForTest(role.getName());
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ RoleManager userRoleManager = userContext.getSystemService(RoleManager.class);
+ return userRoleManager.isRoleVisibleForTest(role.getName());
+ } else {
+ return false;
+ }
+ }
+
+ @Nullable
+ @Override
+ public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ RoleManager userRoleManager = userContext.getSystemService(RoleManager.class);
+ List<String> qualifyingPackageNames =
+ userRoleManager.getDefaultHoldersForTest(role.getName());
+
+ // When getQualifyingPackagesAsUser returns a package that isn't installed, Default App
+ // Settings fails to load. Only return available packages.
+ List<String> availableQualifyingPackageNames = new ArrayList<>();
+ for (int i = 0; i < qualifyingPackageNames.size(); i++) {
+ String qualifyingPackage = qualifyingPackageNames.get(i);
+ ApplicationInfo applicationInfo =
+ PackageUtils.getApplicationInfoAsUser(qualifyingPackage, user, context);
+ if (applicationInfo != null) {
+ availableQualifyingPackageNames.add(qualifyingPackage);
+ }
+ }
+ return availableQualifyingPackageNames;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java
index 1c71e0c99..a0007dcc0 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java
@@ -23,6 +23,7 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.role.controller.util.PackageUtils;
@@ -130,13 +131,14 @@ public class AppOp {
return Permissions.setAppOpUidModeAsUser(packageName, mName, defaultMode, user, context);
}
- boolean isAvailableByFeatureFlagAndSdkVersion() {
+ @VisibleForTesting
+ public boolean isAvailableByFeatureFlagAndSdkVersion() {
if (mFeatureFlag != null && !mFeatureFlag.get()) {
return false;
}
return Build.VERSION.SDK_INT >= mMinSdkVersion
- // Workaround to match the value 35 for V in roles.xml before SDK finalization.
- || (mMinSdkVersion == 35 && SdkLevel.isAtLeastV());
+ // Workaround to match the value 36 for B in roles.xml before SDK finalization.
+ || (mMinSdkVersion == 36 && SdkLevel.isAtLeastB());
}
private boolean isAvailableAsUser(@NonNull String packageName,
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java b/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java
index 05b19ff94..c3404be8b 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java
@@ -97,8 +97,8 @@ public class Permission {
return false;
}
if (Build.VERSION.SDK_INT >= mMinSdkVersion
- // Workaround to match the value 35 for V in roles.xml before SDK finalization.
- || (mMinSdkVersion == 35 && SdkLevel.isAtLeastV())) {
+ // Workaround to match the value 36 for B in roles.xml before SDK finalization.
+ || (mMinSdkVersion == 36 && SdkLevel.isAtLeastB())) {
return true;
}
if (Build.VERSION.SDK_INT >= mOptionalMinSdkVersion) {
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java b/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java
index e788fdce1..820ff3d4e 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java
@@ -51,11 +51,13 @@ public class Permissions {
private static final boolean DEBUG = false;
+ private static final Object sPermissionInfoLock = new Object();
+ private static final ArrayMap<String, Boolean> sIsRuntimePermission = new ArrayMap<>();
+ private static final ArrayMap<String, Boolean> sIsRestrictedPermission = new ArrayMap<>();
+
+ private static final Object sForegroundBackgroundPermissionMappingsLock = new Object();
private static ArrayMap<String, String> sForegroundToBackgroundPermission;
private static ArrayMap<String, List<String>> sBackgroundToForegroundPermissions;
- private static final Object sForegroundBackgroundPermissionMappingsLock = new Object();
-
- private static final ArrayMap<String, Boolean> sRestrictedPermissions = new ArrayMap<>();
/**
* Filter a list of permissions based on their SDK versions.
@@ -86,8 +88,9 @@ public class Permissions {
*
* @param packageName the package name of the application to be granted permissions to
* @param permissions the list of permissions to be granted
- * @param overrideDisabledSystemPackage whether to ignore the permissions of a disabled system
- * package (if this package is an updated system package)
+ * @param ignoreDisabledSystemPackage whether to ignore the requested permissions of a disabled
+ * system package (if this package is an updated system
+ * package) when granting runtime permissions
* @param overrideUserSetAndFixed whether to override user set and fixed flags on the permission
* @param setGrantedByRole whether the permissions will be granted as granted-by-role
* @param setGrantedByDefault whether the permissions will be granted as granted-by-default
@@ -101,7 +104,7 @@ public class Permissions {
* PackageInfo, java.util.Set, boolean, boolean, int)
*/
public static boolean grantAsUser(@NonNull String packageName,
- @NonNull List<String> permissions, boolean overrideDisabledSystemPackage,
+ @NonNull List<String> permissions, boolean ignoreDisabledSystemPackage,
boolean overrideUserSetAndFixed, boolean setGrantedByRole, boolean setGrantedByDefault,
boolean setSystemFixed, @NonNull UserHandle user, @NonNull Context context) {
if (setGrantedByRole == setGrantedByDefault) {
@@ -145,15 +148,17 @@ public class Permissions {
// choice to grant this app the permissions needed to function. For all other
// apps, (default grants on first boot and user creation) we don't grant default
// permissions if the version on the system image does not declare them.
- if (!overrideDisabledSystemPackage && isUpdatedSystemApp(packageInfo)) {
+ if (!ignoreDisabledSystemPackage && isUpdatedSystemApp(packageInfo)) {
PackageInfo disabledSystemPackageInfo = getFactoryPackageInfoAsUser(packageName, user,
context);
if (disabledSystemPackageInfo != null) {
- if (ArrayUtils.isEmpty(disabledSystemPackageInfo.requestedPermissions)) {
- return false;
+ for (int i = permissionsToGrant.size() - 1; i >= 0; i--) {
+ String permission = permissionsToGrant.valueAt(i);
+ if (isRuntimePermission(permission, context) && !ArrayUtils.contains(
+ disabledSystemPackageInfo.requestedPermissions, permission)) {
+ permissionsToGrant.removeAt(i);
+ }
}
- CollectionUtils.retainAll(permissionsToGrant,
- disabledSystemPackageInfo.requestedPermissions);
if (permissionsToGrant.isEmpty()) {
return false;
}
@@ -258,7 +263,8 @@ public class Permissions {
if (!wasPermissionOrAppOpGranted) {
// If we've granted a permission which wasn't granted, it's no longer user set or fixed.
newMask |= PackageManager.FLAG_PERMISSION_USER_FIXED
- | PackageManager.FLAG_PERMISSION_USER_SET;
+ | PackageManager.FLAG_PERMISSION_USER_SET
+ | PackageManager.FLAG_PERMISSION_ONE_TIME;
}
// If a component gets a permission for being the default handler A and also default handler
// B, we grant the weaker grant form. This only applies to default permission grant.
@@ -629,7 +635,8 @@ public class Permissions {
}
if (!overrideUserSetAndFixed) {
fixedFlags |= PackageManager.FLAG_PERMISSION_USER_FIXED
- | PackageManager.FLAG_PERMISSION_USER_SET;
+ | PackageManager.FLAG_PERMISSION_USER_SET
+ | PackageManager.FLAG_PERMISSION_ONE_TIME;
}
return (flags & fixedFlags) != 0;
}
@@ -701,6 +708,50 @@ public class Permissions {
return true;
}
+ private static boolean isRuntimePermission(@NonNull String permission,
+ @NonNull Context context) {
+ synchronized (sPermissionInfoLock) {
+ Boolean isRuntimePermission = sIsRuntimePermission.get(permission);
+ if (isRuntimePermission != null) {
+ return isRuntimePermission;
+ }
+ fetchPermissionInfoLocked(permission, context);
+ return sIsRuntimePermission.get(permission);
+ }
+ }
+
+ private static boolean isRestrictedPermission(@NonNull String permission,
+ @NonNull Context context) {
+ synchronized (sPermissionInfoLock) {
+ Boolean isRestrictedPermission = sIsRestrictedPermission.get(permission);
+ if (isRestrictedPermission != null) {
+ return isRestrictedPermission;
+ }
+ fetchPermissionInfoLocked(permission, context);
+ return sIsRestrictedPermission.get(permission);
+ }
+ }
+
+ private static void fetchPermissionInfoLocked(@NonNull String permission,
+ @NonNull Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ PermissionInfo permissionInfo = null;
+ try {
+ permissionInfo = packageManager.getPermissionInfo(permission, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Cannot get PermissionInfo for permission: " + permission);
+ }
+
+ // Don't expect that to be a transient error, so we can still cache the failed information.
+ boolean isRuntimePermission = permissionInfo != null
+ && permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS;
+ boolean isRestrictedPermission = permissionInfo != null
+ && (permissionInfo.flags & (PermissionInfo.FLAG_SOFT_RESTRICTED
+ | PermissionInfo.FLAG_HARD_RESTRICTED)) != 0;
+ sIsRuntimePermission.put(permission, isRuntimePermission);
+ sIsRestrictedPermission.put(permission, isRestrictedPermission);
+ }
+
private static boolean isForegroundPermission(@NonNull String permission,
@NonNull Context context) {
ensureForegroundBackgroundPermissionMappings(context);
@@ -731,40 +782,13 @@ public class Permissions {
synchronized (sForegroundBackgroundPermissionMappingsLock) {
if (sForegroundToBackgroundPermission == null
&& sBackgroundToForegroundPermissions == null) {
- createForegroundBackgroundPermissionMappings(context);
+ createForegroundBackgroundPermissionMappingsLocked(context);
}
}
}
- private static boolean isRestrictedPermission(@NonNull String permission,
+ private static void createForegroundBackgroundPermissionMappingsLocked(
@NonNull Context context) {
- synchronized (sRestrictedPermissions) {
- if (sRestrictedPermissions.containsKey(permission)) {
- return sRestrictedPermissions.get(permission);
- }
- }
-
- PackageManager packageManager = context.getPackageManager();
- PermissionInfo permissionInfo = null;
- try {
- permissionInfo = packageManager.getPermissionInfo(permission, 0);
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(LOG_TAG, "Cannot get PermissionInfo for permission: " + permission);
- }
-
- // Don't expect that to be a transient error, so we can still cache the failed information.
- boolean isRestrictedPermission = permissionInfo != null
- && (permissionInfo.flags & (PermissionInfo.FLAG_SOFT_RESTRICTED
- | PermissionInfo.FLAG_HARD_RESTRICTED)) != 0;
-
- synchronized (sRestrictedPermissions) {
- sRestrictedPermissions.put(permission, isRestrictedPermission);
- }
-
- return isRestrictedPermission;
- }
-
- private static void createForegroundBackgroundPermissionMappings(@NonNull Context context) {
List<String> permissions = new ArrayList<>();
sBackgroundToForegroundPermissions = new ArrayMap<>();
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
index 04fd615e1..0f79b19c0 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
@@ -26,7 +26,6 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
-import android.content.pm.Signature;
import android.content.res.Resources;
import android.os.Build;
import android.os.UserHandle;
@@ -37,17 +36,25 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseBooleanArray;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
+import androidx.annotation.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
import com.android.role.controller.util.CollectionUtils;
+import com.android.role.controller.util.IntentCompat;
import com.android.role.controller.util.PackageUtils;
+import com.android.role.controller.util.RoleFlags;
import com.android.role.controller.util.RoleManagerCompat;
+import com.android.role.controller.util.SignedPackageUtils;
import com.android.role.controller.util.UserUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -78,9 +85,35 @@ public class Role {
private static final String PACKAGE_NAME_ANDROID_SYSTEM = "android";
- private static final String DEFAULT_HOLDER_SEPARATOR = ";";
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ EXCLUSIVITY_NONE,
+ EXCLUSIVITY_USER,
+ EXCLUSIVITY_PROFILE_GROUP
+ })
+ public @interface Exclusivity {}
- private static final String CERTIFICATE_SEPARATOR = ":";
+ /**
+ * Does not enforce any exclusivity, which means multiple apps may hold this role in a user.
+ */
+ public static final int EXCLUSIVITY_NONE = 0;
+
+ /** Enforces exclusivity within one user. */
+ public static final int EXCLUSIVITY_USER = 1;
+
+ /**
+ * Enforces exclusivity across all users (including profile users) in the same profile group.
+ */
+ public static final int EXCLUSIVITY_PROFILE_GROUP = 2;
+
+ /** Set of valid exclusivity values. */
+ private static final SparseBooleanArray sExclusivityValues = new SparseBooleanArray();
+ static {
+ sExclusivityValues.put(EXCLUSIVITY_NONE, true);
+ sExclusivityValues.put(EXCLUSIVITY_USER, true);
+ sExclusivityValues.put(EXCLUSIVITY_PROFILE_GROUP,
+ RoleFlags.isProfileGroupExclusivityAvailable());
+ }
/**
* The name of this role. Must be unique.
@@ -109,9 +142,10 @@ public class Role {
private final int mDescriptionResource;
/**
- * Whether this role is exclusive, i.e. allows at most one holder.
+ * The exclusivity of this role, i.e. whether this role allows multiple holders, or allows at
+ * most one holder within a user or a profile group.
*/
- private final boolean mExclusive;
+ private final int mExclusivity;
/**
* Whether this role should fall back to the default holder.
@@ -185,8 +219,8 @@ public class Role {
/**
* Whether the UI for this role will show the "None" item. Only valid if this role is
- * {@link #mExclusive exclusive}, and {@link #getFallbackHolder(Context)} should also return
- * empty to allow actually selecting "None".
+ * {@link #isExclusive()}, and {@link #getFallbackHolder(Context)} should
+ * also return empty to allow actually selecting "None".
*/
private final boolean mShowNone;
@@ -240,14 +274,14 @@ public class Role {
public Role(@NonNull String name, boolean allowBypassingQualification,
@Nullable RoleBehavior behavior, @Nullable String defaultHoldersResourceName,
- @StringRes int descriptionResource, boolean exclusive, boolean fallBackToDefaultHolder,
- @Nullable Supplier<Boolean> featureFlag, @StringRes int labelResource,
- int maxSdkVersion, int minSdkVersion, boolean onlyGrantWhenAdded,
- boolean overrideUserWhenGranting, @StringRes int requestDescriptionResource,
- @StringRes int requestTitleResource, boolean requestable,
- @StringRes int searchKeywordsResource, @StringRes int shortLabelResource,
- boolean showNone, boolean statik, boolean systemOnly, boolean visible,
- @NonNull List<RequiredComponent> requiredComponents,
+ @StringRes int descriptionResource, @Exclusivity int exclusivity,
+ boolean fallBackToDefaultHolder, @Nullable Supplier<Boolean> featureFlag,
+ @StringRes int labelResource, int maxSdkVersion, int minSdkVersion,
+ boolean onlyGrantWhenAdded, boolean overrideUserWhenGranting,
+ @StringRes int requestDescriptionResource, @StringRes int requestTitleResource,
+ boolean requestable, @StringRes int searchKeywordsResource,
+ @StringRes int shortLabelResource, boolean showNone, boolean statik, boolean systemOnly,
+ boolean visible, @NonNull List<RequiredComponent> requiredComponents,
@NonNull List<Permission> permissions, @NonNull List<Permission> appOpPermissions,
@NonNull List<AppOp> appOps, @NonNull List<PreferredActivity> preferredActivities,
@Nullable String uiBehaviorName) {
@@ -256,7 +290,7 @@ public class Role {
mBehavior = behavior;
mDefaultHoldersResourceName = defaultHoldersResourceName;
mDescriptionResource = descriptionResource;
- mExclusive = exclusive;
+ mExclusivity = exclusivity;
mFallBackToDefaultHolder = fallBackToDefaultHolder;
mFeatureFlag = featureFlag;
mLabelResource = labelResource;
@@ -297,7 +331,29 @@ public class Role {
}
public boolean isExclusive() {
- return mExclusive;
+ return getExclusivity() != EXCLUSIVITY_NONE;
+ }
+
+ @Exclusivity
+ public int getExclusivity() {
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled() && mBehavior != null) {
+ Integer exclusivity = mBehavior.getExclusivity();
+ if (exclusivity != null) {
+ if (!sExclusivityValues.get(exclusivity)) {
+ throw new IllegalArgumentException("Invalid exclusivity: " + exclusivity);
+ }
+ if (mShowNone && exclusivity == EXCLUSIVITY_NONE) {
+ throw new IllegalArgumentException(
+ "Role cannot be non-exclusive when showNone is true: " + exclusivity);
+ }
+ if (!mPreferredActivities.isEmpty() && exclusivity == EXCLUSIVITY_PROFILE_GROUP) {
+ throw new IllegalArgumentException(
+ "Role cannot have preferred activities when exclusivity is profileGroup");
+ }
+ return exclusivity;
+ }
+ }
+ return mExclusivity;
}
@Nullable
@@ -413,6 +469,19 @@ public class Role {
if (!isAvailableByFeatureFlagAndSdkVersion()) {
return false;
}
+ if (getExclusivity() == EXCLUSIVITY_PROFILE_GROUP) {
+ if (UserUtils.isPrivateProfile(user, context)) {
+ return false;
+ }
+
+ // A profile group exclusive role may only be available in a profile when it's
+ // available in the profile parent.
+ UserHandle profileParent = UserUtils.getProfileParentOrSelf(user, context);
+ if (!Objects.equals(user, profileParent)
+ && !isAvailableAsUser(profileParent, context)) {
+ return false;
+ }
+ }
if (mBehavior != null) {
return mBehavior.isAvailableAsUser(this, user, context);
}
@@ -424,18 +493,25 @@ public class Role {
*
* @return whether this role is available based on SDK version
*/
- boolean isAvailableByFeatureFlagAndSdkVersion() {
+ @VisibleForTesting
+ public boolean isAvailableByFeatureFlagAndSdkVersion() {
if (mFeatureFlag != null && !mFeatureFlag.get()) {
return false;
}
return (Build.VERSION.SDK_INT >= mMinSdkVersion
- // Workaround to match the value 35 for V in roles.xml before SDK finalization.
- || (mMinSdkVersion == 35 && SdkLevel.isAtLeastV()))
+ // Workaround to match the value 36 for B in roles.xml before SDK finalization.
+ || (mMinSdkVersion == 36 && SdkLevel.isAtLeastB()))
&& Build.VERSION.SDK_INT <= mMaxSdkVersion;
}
- public boolean isStatic() {
- return mStatic;
+ /**
+ * Check whether this role is static, which may change due to bypassing qualification.
+ *
+ * @param context the {@code Context} to retrieve system services
+ * @return whether this role is static
+ */
+ public boolean isStatic(@NonNull Context context) {
+ return mStatic && !isBypassingQualification(context);
}
/**
@@ -449,6 +525,12 @@ public class Role {
@NonNull
public List<String> getDefaultHoldersAsUser(@NonNull UserHandle user,
@NonNull Context context) {
+ // Do not allow default role holder for non-active user if the role is exclusive to profile
+ // group
+ if (isNonActiveUserForProfileGroupExclusiveRole(user, context)) {
+ return Collections.emptyList();
+ }
+
if (mBehavior != null) {
List<String> defaultHolders = mBehavior.getDefaultHoldersAsUser(this, user, context);
if (defaultHolders != null) {
@@ -481,71 +563,13 @@ public class Role {
}
if (isExclusive()) {
- String packageName = getQualifiedDefaultHolderPackageNameAsUser(defaultHolders, user,
- context);
- if (packageName == null) {
- return Collections.emptyList();
- }
- return Collections.singletonList(packageName);
+ return CollectionUtils.singletonOrEmpty(
+ SignedPackageUtils.getPackageNameAsUser(defaultHolders, user, context));
} else {
- List<String> packageNames = new ArrayList<>();
- for (String defaultHolder : defaultHolders.split(DEFAULT_HOLDER_SEPARATOR)) {
- String packageName = getQualifiedDefaultHolderPackageNameAsUser(defaultHolder,
- user, context);
- if (packageName != null) {
- packageNames.add(packageName);
- }
- }
- return packageNames;
+ return SignedPackageUtils.getPackageNamesAsUser(defaultHolders, user, context);
}
}
- @Nullable
- private String getQualifiedDefaultHolderPackageNameAsUser(@NonNull String defaultHolder,
- @NonNull UserHandle user, @NonNull Context context) {
- String packageName;
- byte[] certificate;
- int certificateSeparatorIndex = defaultHolder.indexOf(CERTIFICATE_SEPARATOR);
- if (certificateSeparatorIndex != -1) {
- packageName = defaultHolder.substring(0, certificateSeparatorIndex);
- String certificateString = defaultHolder.substring(certificateSeparatorIndex + 1);
- try {
- certificate = new Signature(certificateString).toByteArray();
- } catch (IllegalArgumentException e) {
- Log.w(LOG_TAG, "Cannot parse signing certificate: " + defaultHolder, e);
- return null;
- }
- } else {
- packageName = defaultHolder;
- certificate = null;
- }
-
- if (certificate != null) {
- Context userContext = UserUtils.getUserContext(context, user);
- PackageManager userPackageManager = userContext.getPackageManager();
- if (!userPackageManager.hasSigningCertificate(packageName, certificate,
- PackageManager.CERT_INPUT_SHA256)) {
- Log.w(LOG_TAG, "Default holder doesn't have required signing certificate: "
- + defaultHolder);
- return null;
- }
- } else {
- ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
- user, context);
- if (applicationInfo == null) {
- Log.w(LOG_TAG, "Cannot get ApplicationInfo for default holder: " + packageName);
- return null;
- }
- if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
- Log.w(LOG_TAG, "Default holder didn't specify a signing certificate and isn't a"
- + " system app: " + packageName);
- return null;
- }
- }
-
- return packageName;
- }
-
/**
* Get the fallback holder of this role, which will be added whenever there are no role holders.
* <p>
@@ -560,6 +584,10 @@ public class Role {
if (!RoleManagerCompat.isRoleFallbackEnabledAsUser(this, user, context)) {
return null;
}
+ // Do not fall back for non-active user if the role is exclusive to profile group
+ if (isNonActiveUserForProfileGroupExclusiveRole(user, context)) {
+ return null;
+ }
if (mFallBackToDefaultHolder) {
return CollectionUtils.firstOrNull(getDefaultHoldersAsUser(user, context));
}
@@ -569,6 +597,17 @@ public class Role {
return null;
}
+ private boolean isNonActiveUserForProfileGroupExclusiveRole(@NonNull UserHandle user,
+ @NonNull Context context) {
+ if (RoleFlags.isProfileGroupExclusivityAvailable()
+ && getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ RoleManager userRoleManager = userContext.getSystemService(RoleManager.class);
+ return !Objects.equals(userRoleManager.getActiveUserForRole(mName), user);
+ }
+ return false;
+ }
+
/**
* Check whether this role is allowed to bypass qualification, if enabled globally.
*
@@ -587,6 +626,12 @@ public class Role {
return mAllowBypassingQualification;
}
+ private boolean isBypassingQualification(@NonNull Context context) {
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ return shouldAllowBypassingQualification(context)
+ && RoleManagerCompat.isBypassingRoleQualification(roleManager);
+ }
+
/**
* Check whether a package is qualified for this role, i.e. whether it contains all the required
* components (plus meeting some other general restrictions).
@@ -599,9 +644,7 @@ public class Role {
*/
public boolean isPackageQualifiedAsUser(@NonNull String packageName, @NonNull UserHandle user,
@NonNull Context context) {
- RoleManager roleManager = context.getSystemService(RoleManager.class);
- if (shouldAllowBypassingQualification(context)
- && RoleManagerCompat.isBypassingRoleQualification(roleManager)) {
+ if (isBypassingQualification(context)) {
return true;
}
@@ -949,7 +992,14 @@ public class Role {
*/
public void onHolderAddedAsUser(@NonNull String packageName, @NonNull UserHandle user,
@NonNull Context context) {
- RoleManagerCompat.setRoleFallbackEnabledAsUser(this, true, user, context);
+ if (RoleFlags.isProfileGroupExclusivityAvailable()
+ && com.android.permission.flags.Flags.crossUserRoleUxBugfixEnabled()
+ && getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ UserHandle profileParent = UserUtils.getProfileParentOrSelf(user, context);
+ RoleManagerCompat.setRoleFallbackEnabledAsUser(this, true, profileParent, context);
+ } else {
+ RoleManagerCompat.setRoleFallbackEnabledAsUser(this, true, user, context);
+ }
}
/**
@@ -988,6 +1038,11 @@ public class Role {
*/
public void onNoneHolderSelectedAsUser(@NonNull UserHandle user, @NonNull Context context) {
RoleManagerCompat.setRoleFallbackEnabledAsUser(this, false, user, context);
+ if (RoleFlags.isProfileGroupExclusivityAvailable()
+ && getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ roleManager.setActiveUserForRole(mName, user, 0);
+ }
}
/**
@@ -999,11 +1054,23 @@ public class Role {
* @return whether this role should be visible to user
*/
public boolean isVisibleAsUser(@NonNull UserHandle user, @NonNull Context context) {
- RoleBehavior behavior = getBehavior();
- if (behavior == null) {
- return isVisible();
+ if (mBehavior != null) {
+ Boolean isVisibleAsUser = mBehavior.isVisibleAsUser(this, user, context);
+ if (isVisibleAsUser != null) {
+ if (isVisibleAsUser && mStatic) {
+ throw new IllegalArgumentException("static=\"true\" is invalid for a visible "
+ + "role: " + mName);
+ }
+ if (isVisibleAsUser && (mDescriptionResource == 0
+ || mLabelResource == 0
+ || mShortLabelResource == 0)) {
+ throw new IllegalArgumentException("description, label, and shortLabel are "
+ + "required for a visible role: " + mName);
+ }
+ return isVisibleAsUser;
+ }
}
- return isVisible() && behavior.isVisibleAsUser(this, user, context);
+ return isVisible();
}
/**
@@ -1039,13 +1106,38 @@ public class Role {
*/
@Nullable
public Intent getRestrictionIntentAsUser(@NonNull UserHandle user, @NonNull Context context) {
- if (SdkLevel.isAtLeastU() && mExclusive) {
- UserManager userManager = context.getSystemService(UserManager.class);
- if (userManager.hasUserRestrictionForUser(UserManager.DISALLOW_CONFIG_DEFAULT_APPS,
- user)) {
- return new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS)
- .putExtra(DevicePolicyManager.EXTRA_RESTRICTION,
- UserManager.DISALLOW_CONFIG_DEFAULT_APPS);
+ if (SdkLevel.isAtLeastU() && isExclusive()) {
+ boolean crossUserRoleUxBugfixEnabled =
+ com.android.permission.flags.Flags.crossUserRoleUxBugfixEnabled();
+ if (crossUserRoleUxBugfixEnabled && getExclusivity() == EXCLUSIVITY_PROFILE_GROUP) {
+ DevicePolicyManager devicePolicyManager =
+ context.getSystemService(DevicePolicyManager.class);
+ if (!devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) {
+ // For profileGroup exclusive roles users on BYOD are free to choose personal or
+ // work profile app regardless of DISALLOW_CONFIG_DEFAULT_APPS
+ return null;
+ }
+ }
+
+ // Otherwise if role is profileGroup exclusive check DISALLOW_CONFIG_DEFAULT_APPS for
+ // all users
+ List<UserHandle> profiles =
+ (crossUserRoleUxBugfixEnabled && getExclusivity() == EXCLUSIVITY_PROFILE_GROUP)
+ ? UserUtils.getUserProfiles(user, context, true)
+ : List.of(user);
+ final int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; i++) {
+ UserHandle profile = profiles.get(i);
+ UserManager userManager = context.getSystemService(UserManager.class);
+ if (userManager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_CONFIG_DEFAULT_APPS, profile)) {
+ return new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS)
+ .putExtra(
+ DevicePolicyManager.EXTRA_RESTRICTION,
+ UserManager.DISALLOW_CONFIG_DEFAULT_APPS)
+ .putExtra(Intent.EXTRA_USER, profile)
+ .putExtra(IntentCompat.EXTRA_USER_ID, profile.getIdentifier());
+ }
}
}
return null;
@@ -1102,7 +1194,7 @@ public class Role {
+ ", mBehavior=" + mBehavior
+ ", mDefaultHoldersResourceName=" + mDefaultHoldersResourceName
+ ", mDescriptionResource=" + mDescriptionResource
- + ", mExclusive=" + mExclusive
+ + ", mExclusivity=" + mExclusivity
+ ", mFallBackToDefaultHolder=" + mFallBackToDefaultHolder
+ ", mFeatureFlag=" + mFeatureFlag
+ ", mLabelResource=" + mLabelResource
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
index 3849a50e3..3b08265d1 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
@@ -23,7 +23,6 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import java.util.Collections;
import java.util.List;
/**
@@ -32,6 +31,14 @@ import java.util.List;
public interface RoleBehavior {
/**
+ * @see Role#getExclusivity()
+ */
+ @Nullable
+ default Integer getExclusivity() {
+ return null;
+ }
+
+ /**
* @see Role#onRoleAddedAsUser(UserHandle, Context)
*/
default void onRoleAddedAsUser(@NonNull Role role, @NonNull UserHandle user,
@@ -121,11 +128,12 @@ public interface RoleBehavior {
* @param user the user to check for
* @param context the `Context` to retrieve system services
*
- * @return whether this role should be visible to user
+ * @return whether this role should be visible to user, or {@code null} if not overridden
*/
- default boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @Nullable
+ default Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
- return true;
+ return null;
}
/**
diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java b/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java
index f1a275daf..f0a730647 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java
@@ -19,12 +19,9 @@ package com.android.role.controller.model;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Build;
-import android.os.Process;
import android.permission.flags.Flags;
import android.util.ArrayMap;
import android.util.Log;
@@ -92,6 +89,7 @@ public class RoleParser {
private static final String ATTRIBUTE_DEFAULT_HOLDERS = "defaultHolders";
private static final String ATTRIBUTE_DESCRIPTION = "description";
private static final String ATTRIBUTE_EXCLUSIVE = "exclusive";
+ private static final String ATTRIBUTE_EXCLUSIVITY = "exclusivity";
private static final String ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER = "fallBackToDefaultHolder";
private static final String ATTRIBUTE_FEATURE_FLAG = "featureFlag";
private static final String ATTRIBUTE_LABEL = "label";
@@ -138,6 +136,10 @@ public class RoleParser {
sModeNameToMode.put(MODE_NAME_FOREGROUND, AppOpsManager.MODE_FOREGROUND);
}
+ private static final String EXCLUSIVITY_NONE = "none";
+ private static final String EXCLUSIVITY_USER = "user";
+ private static final String EXCLUSIVITY_PROFILE_GROUP = "profileGroup";
+
private static final Supplier<Boolean> sFeatureFlagFallback = () -> false;
private static final ArrayMap<Class<?>, Class<?>> sPrimitiveToWrapperClass = new ArrayMap<>();
@@ -156,16 +158,16 @@ public class RoleParser {
@NonNull
private final Context mContext;
- private final boolean mValidationEnabled;
+ private final boolean mThrowOnError;
public RoleParser(@NonNull Context context) {
this(context, false);
}
@VisibleForTesting
- public RoleParser(@NonNull Context context, boolean validationEnabled) {
+ public RoleParser(@NonNull Context context, boolean throwOnError) {
mContext = context;
- mValidationEnabled = validationEnabled;
+ mThrowOnError = throwOnError;
}
/**
@@ -175,18 +177,21 @@ public class RoleParser {
*/
@NonNull
public ArrayMap<String, Role> parse() {
+ Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = parseRolesXml();
+ if (xml == null) {
+ return new ArrayMap<>();
+ }
+ return xml.second;
+ }
+
+ @Nullable
+ @VisibleForTesting
+ public Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> parseRolesXml() {
try (XmlResourceParser parser = getRolesXml()) {
- Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = parseXml(parser);
- if (xml == null) {
- return new ArrayMap<>();
- }
- ArrayMap<String, PermissionSet> permissionSets = xml.first;
- ArrayMap<String, Role> roles = xml.second;
- validateResult(permissionSets, roles);
- return roles;
+ return parseXml(parser);
} catch (XmlPullParserException | IOException e) {
throwOrLogMessage("Unable to parse roles.xml", e);
- return new ArrayMap<>();
+ return null;
}
}
@@ -408,18 +413,52 @@ public class RoleParser {
skipCurrentTag(parser);
return null;
}
+ } else if (behavior != null) {
+ labelResource = getAttributeResourceValue(parser, ATTRIBUTE_LABEL, 0);
+ shortLabelResource = getAttributeResourceValue(parser, ATTRIBUTE_SHORT_LABEL, 0);
} else {
labelResource = 0;
shortLabelResource = 0;
}
- Boolean exclusive = requireAttributeBooleanValue(parser, ATTRIBUTE_EXCLUSIVE, true,
- TAG_ROLE);
- if (exclusive == null) {
- skipCurrentTag(parser);
- return null;
+ int exclusivity;
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ String exclusivityName = requireAttributeValue(parser, ATTRIBUTE_EXCLUSIVITY, TAG_ROLE);
+ if (exclusivityName == null) {
+ skipCurrentTag(parser);
+ return null;
+ }
+ switch (exclusivityName) {
+ case EXCLUSIVITY_NONE:
+ exclusivity = Role.EXCLUSIVITY_NONE;
+ break;
+ case EXCLUSIVITY_USER:
+ exclusivity = Role.EXCLUSIVITY_USER;
+ break;
+ case EXCLUSIVITY_PROFILE_GROUP:
+ // EXCLUSIVITY_PROFILE behavior only available for B+
+ // fallback to default of EXCLUSIVITY_USER
+ exclusivity = SdkLevel.isAtLeastB()
+ ? Role.EXCLUSIVITY_PROFILE_GROUP
+ : Role.EXCLUSIVITY_USER;
+ break;
+ default:
+ throwOrLogMessage("Invalid value for \"exclusivity\" on <role>: " + name
+ + ", exclusivity: " + exclusivityName);
+ skipCurrentTag(parser);
+ return null;
+ }
+ } else {
+ Boolean exclusive =
+ requireAttributeBooleanValue(parser, ATTRIBUTE_EXCLUSIVE, true, TAG_ROLE);
+ if (exclusive == null) {
+ skipCurrentTag(parser);
+ return null;
+ }
+ exclusivity = exclusive ? Role.EXCLUSIVITY_USER : Role.EXCLUSIVITY_NONE;
}
+
boolean fallBackToDefaultHolder = getAttributeBooleanValue(parser,
ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER, false);
@@ -470,7 +509,7 @@ public class RoleParser {
0);
boolean showNone = getAttributeBooleanValue(parser, ATTRIBUTE_SHOW_NONE, false);
- if (showNone && !exclusive) {
+ if (showNone && exclusivity == Role.EXCLUSIVITY_NONE) {
throwOrLogMessage("showNone=\"true\" is invalid for a non-exclusive role: " + name);
skipCurrentTag(parser);
return null;
@@ -543,6 +582,12 @@ public class RoleParser {
skipCurrentTag(parser);
continue;
}
+ if (exclusivity == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ throwOrLogMessage("<preferred-activities> is not supported for a"
+ + " profile-group-exclusive role: " + name);
+ skipCurrentTag(parser);
+ continue;
+ }
preferredActivities = parsePreferredActivities(parser);
break;
default:
@@ -567,12 +612,12 @@ public class RoleParser {
preferredActivities = Collections.emptyList();
}
return new Role(name, allowBypassingQualification, behavior, defaultHoldersResourceName,
- descriptionResource, exclusive, fallBackToDefaultHolder, featureFlag, labelResource,
- maxSdkVersion, minSdkVersion, onlyGrantWhenAdded, overrideUserWhenGranting,
- requestDescriptionResource, requestTitleResource, requestable,
- searchKeywordsResource, shortLabelResource, showNone, statik, systemOnly, visible,
- requiredComponents, permissions, appOpPermissions, appOps, preferredActivities,
- uiBehaviorName);
+ descriptionResource, exclusivity, fallBackToDefaultHolder, featureFlag,
+ labelResource, maxSdkVersion, minSdkVersion, onlyGrantWhenAdded,
+ overrideUserWhenGranting, requestDescriptionResource, requestTitleResource,
+ requestable, searchKeywordsResource, shortLabelResource, showNone, statik,
+ systemOnly, visible, requiredComponents, permissions, appOpPermissions, appOps,
+ preferredActivities, uiBehaviorName);
}
@NonNull
@@ -624,7 +669,7 @@ public class RoleParser {
int queryFlags = getAttributeIntValue(parser, ATTRIBUTE_QUERY_FLAGS, 0);
IntentFilterData intentFilterData = null;
List<RequiredMetaData> metaData = new ArrayList<>();
- List<String> validationMetaDataNames = mValidationEnabled ? new ArrayList<>() : null;
+ List<String> validationMetaDataNames = mThrowOnError ? new ArrayList<>() : null;
int type;
int depth;
@@ -651,7 +696,7 @@ public class RoleParser {
if (metaDataName == null) {
continue;
}
- if (mValidationEnabled) {
+ if (mThrowOnError) {
validateNoDuplicateElement(metaDataName, validationMetaDataNames,
"meta data");
}
@@ -668,7 +713,7 @@ public class RoleParser {
RequiredMetaData requiredMetaData = new RequiredMetaData(metaDataName,
metaDataValue, metaDataProhibited);
metaData.add(requiredMetaData);
- if (mValidationEnabled) {
+ if (mThrowOnError) {
validationMetaDataNames.add(metaDataName);
}
break;
@@ -1204,7 +1249,7 @@ public class RoleParser {
}
private void throwOrLogMessage(String message) {
- if (mValidationEnabled) {
+ if (mThrowOnError) {
throw new IllegalArgumentException(message);
} else {
Log.wtf(LOG_TAG, message);
@@ -1212,7 +1257,7 @@ public class RoleParser {
}
private void throwOrLogMessage(String message, Throwable cause) {
- if (mValidationEnabled) {
+ if (mThrowOnError) {
throw new IllegalArgumentException(message, cause);
} else {
Log.wtf(LOG_TAG, message, cause);
@@ -1222,168 +1267,4 @@ public class RoleParser {
private void throwOrLogForUnknownTag(@NonNull XmlResourceParser parser) {
throwOrLogMessage("Unknown tag: " + parser.getName());
}
-
- /**
- * Validates the permission names with {@code PackageManager} and ensures that all app ops with
- * a permission in {@code AppOpsManager} have declared that permission in its role and ensures
- * that all preferred activities are listed in the required components.
- */
- private void validateResult(@NonNull ArrayMap<String, PermissionSet> permissionSets,
- @NonNull ArrayMap<String, Role> roles) {
- if (!mValidationEnabled) {
- return;
- }
-
- int permissionSetsSize = permissionSets.size();
- for (int permissionSetsIndex = 0; permissionSetsIndex < permissionSetsSize;
- permissionSetsIndex++) {
- PermissionSet permissionSet = permissionSets.valueAt(permissionSetsIndex);
-
- List<Permission> permissions = permissionSet.getPermissions();
- int permissionsSize = permissions.size();
- for (int permissionsIndex = 0; permissionsIndex < permissionsSize; permissionsIndex++) {
- Permission permission = permissions.get(permissionsIndex);
-
- validatePermission(permission);
- }
- }
-
- int rolesSize = roles.size();
- for (int rolesIndex = 0; rolesIndex < rolesSize; rolesIndex++) {
- Role role = roles.valueAt(rolesIndex);
-
- if (!role.isAvailableByFeatureFlagAndSdkVersion()) {
- continue;
- }
-
- List<RequiredComponent> requiredComponents = role.getRequiredComponents();
- int requiredComponentsSize = requiredComponents.size();
- for (int requiredComponentsIndex = 0; requiredComponentsIndex < requiredComponentsSize;
- requiredComponentsIndex++) {
- RequiredComponent requiredComponent = requiredComponents.get(
- requiredComponentsIndex);
-
- String permission = requiredComponent.getPermission();
- if (permission != null) {
- validatePermission(permission);
- }
- }
-
- List<Permission> permissions = role.getPermissions();
- int permissionsSize = permissions.size();
- for (int i = 0; i < permissionsSize; i++) {
- Permission permission = permissions.get(i);
-
- validatePermission(permission);
- }
-
- List<AppOp> appOps = role.getAppOps();
- int appOpsSize = appOps.size();
- for (int i = 0; i < appOpsSize; i++) {
- AppOp appOp = appOps.get(i);
-
- validateAppOp(appOp);
- }
-
- List<Permission> appOpPermissions = role.getAppOpPermissions();
- int appOpPermissionsSize = appOpPermissions.size();
- for (int i = 0; i < appOpPermissionsSize; i++) {
- validateAppOpPermission(appOpPermissions.get(i));
- }
-
- List<PreferredActivity> preferredActivities = role.getPreferredActivities();
- int preferredActivitiesSize = preferredActivities.size();
- for (int preferredActivitiesIndex = 0;
- preferredActivitiesIndex < preferredActivitiesSize;
- preferredActivitiesIndex++) {
- PreferredActivity preferredActivity = preferredActivities.get(
- preferredActivitiesIndex);
-
- if (!role.getRequiredComponents().contains(preferredActivity.getActivity())) {
- throw new IllegalArgumentException("<activity> of <preferred-activity> not"
- + " required in <required-components>, role: " + role.getName()
- + ", preferred activity: " + preferredActivity);
- }
- }
- }
- }
-
- private void validatePermission(@NonNull Permission permission) {
- if (!permission.isAvailableAsUser(Process.myUserHandle(), mContext)) {
- return;
- }
- validatePermission(permission.getName(), true);
- }
-
- private void validatePermission(@NonNull String permission) {
- validatePermission(permission, false);
- }
-
- private void validatePermission(@NonNull String permission, boolean enforceIsRuntimeOrRole) {
- PackageManager packageManager = mContext.getPackageManager();
- boolean isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
- // Skip validation for car permissions which may not be available on all build targets.
- if (!isAutomotive && permission.startsWith("android.car")) {
- return;
- }
-
- PermissionInfo permissionInfo;
- try {
- permissionInfo = packageManager.getPermissionInfo(permission, 0);
- } catch (PackageManager.NameNotFoundException e) {
- throw new IllegalArgumentException("Unknown permission: " + permission, e);
- }
-
- if (enforceIsRuntimeOrRole) {
- if (!(permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS
- || (permissionInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_ROLE)
- == PermissionInfo.PROTECTION_FLAG_ROLE)) {
- throw new IllegalArgumentException(
- "Permission is not a runtime or role permission: " + permission);
- }
- }
- }
-
- private void validateAppOpPermission(@NonNull Permission appOpPermission) {
- if (!appOpPermission.isAvailableAsUser(Process.myUserHandle(), mContext)) {
- return;
- }
- validateAppOpPermission(appOpPermission.getName());
- }
-
- private void validateAppOpPermission(@NonNull String appOpPermission) {
- PackageManager packageManager = mContext.getPackageManager();
- PermissionInfo permissionInfo;
- try {
- permissionInfo = packageManager.getPermissionInfo(appOpPermission, 0);
- } catch (PackageManager.NameNotFoundException e) {
- throw new IllegalArgumentException("Unknown app op permission: " + appOpPermission, e);
- }
- if ((permissionInfo.getProtectionFlags() & PermissionInfo.PROTECTION_FLAG_APPOP)
- != PermissionInfo.PROTECTION_FLAG_APPOP) {
- throw new IllegalArgumentException("Permission is not an app op permission: "
- + appOpPermission);
- }
- }
-
- private void validateAppOp(@NonNull AppOp appOp) {
- if (!appOp.isAvailableByFeatureFlagAndSdkVersion()) {
- return;
- }
- // This throws IllegalArgumentException if app op is unknown.
- String permission = AppOpsManager.opToPermission(appOp.getName());
- if (permission != null) {
- PackageManager packageManager = mContext.getPackageManager();
- PermissionInfo permissionInfo;
- try {
- permissionInfo = packageManager.getPermissionInfo(permission, 0);
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException(e);
- }
- if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS) {
- throw new IllegalArgumentException("App op has an associated runtime permission: "
- + appOp.getName());
- }
- }
- }
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java b/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
index bc7562c11..1a0c83ab8 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/service/RoleControllerServiceImpl.java
@@ -16,6 +16,7 @@
package com.android.role.controller.service;
+import android.annotation.UserIdInt;
import android.app.role.RoleControllerService;
import android.app.role.RoleManager;
import android.content.Context;
@@ -34,6 +35,7 @@ import com.android.role.controller.model.Roles;
import com.android.role.controller.util.CollectionUtils;
import com.android.role.controller.util.LegacyRoleFallbackEnabledUtils;
import com.android.role.controller.util.PackageUtils;
+import com.android.role.controller.util.RoleFlags;
import com.android.role.controller.util.UserUtils;
import java.util.ArrayList;
@@ -49,11 +51,21 @@ public class RoleControllerServiceImpl extends RoleControllerService {
private static final boolean DEBUG = false;
+ public static volatile SetActiveUserForRoleMethod sSetActiveUserForRoleMethod;
private UserHandle mUser;
private Context mContext;
private RoleManager mUserRoleManager;
+ /** Method for setting active user from role controller */
+ public interface SetActiveUserForRoleMethod {
+ /**
+ * Sets user as active for the given role.
+ * @see RoleManager#setActiveUserForRole(String, UserHandle, int)
+ */
+ void setActiveUserForRole(@NonNull String roleName, @UserIdInt int userId, int flags);
+ }
+
public RoleControllerServiceImpl() {}
public RoleControllerServiceImpl(@NonNull UserHandle user, @NonNull Context context) {
@@ -121,6 +133,19 @@ public class RoleControllerServiceImpl extends RoleControllerService {
String roleName = role.getName();
+ if (RoleFlags.isProfileGroupExclusivityAvailable()
+ && role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ if (mUserRoleManager.getActiveUserForRole(roleName) == null) {
+ UserHandle profileParent = UserUtils.getProfileParentOrSelf(mUser, mContext);
+ if (Objects.equals(mUser, profileParent)) {
+ Log.i(LOG_TAG, "No active user for role: " + roleName + ", setting "
+ + "active user to user: " + mUser.getIdentifier());
+ sSetActiveUserForRoleMethod.setActiveUserForRole(roleName,
+ mUser.getIdentifier(), 0);
+ }
+ }
+ }
+
// For each of the current holders, check if it is still qualified, redo grant if so, or
// remove it otherwise.
List<String> currentPackageNames = mUserRoleManager.getRoleHolders(roleName);
@@ -148,7 +173,7 @@ public class RoleControllerServiceImpl extends RoleControllerService {
// or fallback holders, if any.
currentPackageNames = mUserRoleManager.getRoleHolders(roleName);
currentPackageNamesSize = currentPackageNames.size();
- boolean isStaticRole = role.isStatic();
+ boolean isStaticRole = role.isStatic(mContext);
if (currentPackageNamesSize == 0 || isStaticRole) {
List<String> packageNamesToAdd = null;
if (addedRoleNames.contains(roleName) || isStaticRole) {
@@ -232,6 +257,11 @@ public class RoleControllerServiceImpl extends RoleControllerService {
boolean added = false;
if (role.isExclusive()) {
+ if (role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ sSetActiveUserForRoleMethod.setActiveUserForRole(roleName, mUser.getIdentifier(),
+ flags);
+ }
+
List<String> currentPackageNames = mUserRoleManager.getRoleHolders(roleName);
int currentPackageNamesSize = currentPackageNames.size();
for (int i = 0; i < currentPackageNamesSize; i++) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/package-info.java b/PermissionController/role-controller/java/com/android/role/controller/util/IntentCompat.java
index b724ba43f..9771ad4cf 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/package-info.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/IntentCompat.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,6 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.role.controller.util;
-@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.S)
-package com.android.permissioncontroller.permission.model.livedatatypes.v31;
+/** Compat class for {@link android.content.Intent} */
+public final class IntentCompat {
+ /**
+ * An int representing the user ID to be used. Copy of
+ * {@link android.content.Intent#EXTRA_USER_ID}
+ */
+ public static final String EXTRA_USER_ID = "android.intent.extra.USER_ID";
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/PackageUtils.java b/PermissionController/role-controller/java/com/android/role/controller/util/PackageUtils.java
index cbffd451a..512015972 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/util/PackageUtils.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/PackageUtils.java
@@ -57,7 +57,7 @@ public final class PackageUtils {
}
/**
- * Retrieve if a package is a system package.
+ * Check whether a package is a system package.
*
* @param packageName the name of the package
* @param user the user of the package
@@ -93,4 +93,22 @@ public final class PackageUtils {
return null;
}
}
+
+ /**
+ * Check whether a package has a signing certificate.
+ *
+ * @param packageName the name of the package
+ * @param certificate the signing certificate
+ * @param user the user of the package
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return whether the package has the signing certificate.
+ */
+ public static boolean hasSigningCertificateAsUser(@NonNull String packageName,
+ @NonNull byte[] certificate, @NonNull UserHandle user, @NonNull Context context) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ PackageManager userPackageManager = userContext.getPackageManager();
+ return userPackageManager.hasSigningCertificate(packageName, certificate,
+ PackageManager.CERT_INPUT_SHA256);
+ }
}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java b/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java
new file mode 100644
index 000000000..23e3a2c65
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/RoleFlags.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import android.os.Build;
+
+import androidx.annotation.ChecksSdkIntAtLeast;
+
+import com.android.modules.utils.build.SdkLevel;
+
+/** Util class for getting shared feature flag check logic. */
+public final class RoleFlags {
+ private RoleFlags() { /* cannot be instantiated */ }
+
+ /**
+ * Returns whether profile group exclusive roles are available. Profile exclusive roles are
+ * available on B+
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.BAKLAVA)
+ public static boolean isProfileGroupExclusivityAvailable() {
+ return SdkLevel.isAtLeastB() && com.android.permission.flags.Flags.crossUserRoleEnabled();
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/SignedPackage.java b/PermissionController/role-controller/java/com/android/role/controller/util/SignedPackage.java
new file mode 100644
index 000000000..b13ac3731
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/SignedPackage.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A package name with an optional signing certificate.
+ */
+public class SignedPackage {
+
+ private static final String LOG_TAG = SignedPackage.class.getSimpleName();
+
+ private static final String SIGNED_PACKAGE_SEPARATOR = ";";
+ private static final String CERTIFICATE_SEPARATOR = ":";
+
+ /**
+ * The name of the package.
+ */
+ @NonNull
+ private final String mPackageName;
+
+ /**
+ * The signing certificate of the package.
+ */
+ @Nullable
+ private final byte[] mCertificate;
+
+ public SignedPackage(@NonNull String packageName, @Nullable byte[] certificate) {
+ mPackageName = packageName;
+ mCertificate = certificate;
+ }
+
+ @NonNull
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ @Nullable
+ public byte[] getCertificate() {
+ return mCertificate;
+ }
+
+ /**
+ * Parse a {@link SignedPackage} from a string input.
+ *
+ * @param input the package name, optionally followed by a colon and a signing certificate
+ * digest if it's not a system app
+ *
+ * @return the parsed {@link SignedPackage}, or {@code null} if the input is invalid
+ */
+ @Nullable
+ public static SignedPackage parse(@NonNull String input) {
+ String packageName;
+ byte[] certificate;
+ int certificateSeparatorIndex = input.indexOf(CERTIFICATE_SEPARATOR);
+ if (certificateSeparatorIndex != -1) {
+ packageName = input.substring(0, certificateSeparatorIndex);
+ String certificateString = input.substring(certificateSeparatorIndex + 1);
+ try {
+ certificate = new Signature(certificateString).toByteArray();
+ } catch (IllegalArgumentException e) {
+ Log.w(LOG_TAG, "Cannot parse signing certificate: " + input, e);
+ return null;
+ }
+ } else {
+ packageName = input;
+ certificate = null;
+ }
+ return new SignedPackage(packageName, certificate);
+ }
+
+ /**
+ * Parse a list of {@link SignedPackage}s from a string input.
+ *
+ * @param input the package names, each optionally followed by a colon and a signing certificate
+ * digest if it's not a system app
+ *
+ * @return the parsed list of valid {@link SignedPackage}s
+ */
+ @NonNull
+ public static List<SignedPackage> parseList(@NonNull String input) {
+ if (TextUtils.isEmpty(input)) {
+ return Collections.emptyList();
+ }
+ List<SignedPackage> signedPackages = new ArrayList<>();
+ for (String signedPackageInput : input.split(SIGNED_PACKAGE_SEPARATOR)) {
+ SignedPackage signedPackage = parse(signedPackageInput);
+ if (signedPackage != null) {
+ signedPackages.add(signedPackage);
+ }
+ }
+ return signedPackages;
+ }
+
+ /**
+ * Checks whether this signed package is available, i.e. it is installed, and either has the
+ * specified signing certificate or is a system app if no signing certificate is specified.
+ *
+ * @param user the user of the package
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return whether this signed package is available
+ */
+ public boolean isAvailableAsUser(@NonNull UserHandle user, @NonNull Context context) {
+ if (mCertificate != null) {
+ if (!PackageUtils.hasSigningCertificateAsUser(mPackageName, mCertificate, user,
+ context)) {
+ Log.w(LOG_TAG, "Package doesn't have required signing certificate: "
+ + mPackageName);
+ return false;
+ }
+ } else {
+ ApplicationInfo applicationInfo =
+ PackageUtils.getApplicationInfoAsUser(mPackageName, user, context);
+ if (applicationInfo == null) {
+ Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + mPackageName);
+ return false;
+ }
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ Log.w(LOG_TAG, "Package didn't specify a signing certificate and isn't a" +
+ " system app: " + mPackageName);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks whether an {@link ApplicationInfo} matches this signed package, i.e. it has the same
+ * package name, and either has the specified signing certificate or is a system app if no
+ * signing certificate is specified.
+ *
+ * @param applicationInfo the {@link ApplicationInfo} to check for
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return whether the {@link ApplicationInfo} matches this signed package
+ */
+ public boolean matches(@NonNull ApplicationInfo applicationInfo, @NonNull Context context) {
+ if (!Objects.equals(applicationInfo.packageName, mPackageName)) {
+ return false;
+ }
+ if (mCertificate != null) {
+ UserHandle user = UserHandle.getUserHandleForUid(applicationInfo.uid);
+ if (!PackageUtils.hasSigningCertificateAsUser(mPackageName, mCertificate, user,
+ context)) {
+ Log.w(LOG_TAG, "Package doesn't have required signing certificate: "
+ + mPackageName);
+ return false;
+ }
+ } else {
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ Log.w(LOG_TAG, "Package didn't specify a signing certificate and isn't a" +
+ " system app: " + mPackageName);
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/SignedPackageUtils.java b/PermissionController/role-controller/java/com/android/role/controller/util/SignedPackageUtils.java
new file mode 100644
index 000000000..169bed347
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/SignedPackageUtils.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility methods about {@link SignedPackage}.
+ */
+public final class SignedPackageUtils {
+
+ private SignedPackageUtils() {}
+
+ /**
+ * Get a list of package names from a string input for {@link SignedPackage}s.
+ * <p>
+ * This method only returns packages that are
+ * {@link SignedPackage#isAvailableAsUser(UserHandle, Context) available}.
+ *
+ * @param input the string input
+ * @param user the user of the packages
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return the package names
+ */
+ @NonNull
+ public static List<String> getPackageNamesAsUser(@NonNull String input,
+ @NonNull UserHandle user, @NonNull Context context) {
+ List<SignedPackage> signedPackages = SignedPackage.parseList(input);
+ List<String> packageNames = new ArrayList<>();
+ int signedPackagesSize = signedPackages.size();
+ for (int i = 0; i < signedPackagesSize; i++) {
+ SignedPackage signedPackage = signedPackages.get(i);
+ if (signedPackage.isAvailableAsUser(user, context)) {
+ packageNames.add(signedPackage.getPackageName());
+ }
+ }
+ return packageNames;
+ }
+
+ /**
+ * Get a package name from a string input for a {@link SignedPackage}.
+ * <p>
+ * This method only returns a package if it is
+ * {@link SignedPackage#isAvailableAsUser(UserHandle, Context) available}.
+ *
+ * @param input the string input
+ * @param user the user of the package
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return the package name, or {@code null} otherwise
+ */
+ @Nullable
+ public static String getPackageNameAsUser(@NonNull String input, @NonNull UserHandle user,
+ @NonNull Context context) {
+ SignedPackage signedPackage = SignedPackage.parse(input);
+ if (signedPackage == null || !signedPackage.isAvailableAsUser(user, context)) {
+ return null;
+ }
+ return signedPackage.getPackageName();
+ }
+
+ /**
+ * Check whether an {@link ApplicationInfo} matches any of the {@link SignedPackage}s.
+ *
+ * @param applicationInfo the {@link ApplicationInfo} to check for
+ * @param signedPackages the list of {@link SignedPackage}s to check against
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return whether the {@link ApplicationInfo} matches any of the {@link SignedPackage}s
+ */
+ public static boolean matchesAny(@NonNull ApplicationInfo applicationInfo,
+ @NonNull List<SignedPackage> signedPackages, @NonNull Context context) {
+ int signedPackagesSize = signedPackages.size();
+ for (int i = 0; i < signedPackagesSize; i++) {
+ SignedPackage signedPackage = signedPackages.get(i);
+ if (signedPackage.matches(applicationInfo, context)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java b/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java
index 1b6926ef8..41233a23e 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java
@@ -24,9 +24,13 @@ import android.os.UserHandle;
import android.os.UserManager;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
+import java.util.ArrayList;
+import java.util.List;
+
/** Utility class to deal with Android users. */
public final class UserUtils {
@@ -111,4 +115,51 @@ public final class UserUtils {
return context.createContextAsUser(user, 0);
}
}
+
+ /**
+ * Returns the parent of a given user, or user if it has no parent (e.g. it is the primary
+ * user)
+ */
+ @NonNull
+ public static UserHandle getProfileParentOrSelf(@NonNull UserHandle user,
+ @NonNull Context context) {
+ UserHandle profileParent = getProfileParent(user, context);
+ // If profile parent user is null, then original user is the parent
+ return profileParent != null ? profileParent : user;
+ }
+
+ /** Returns the parent of a given user. */
+ @Nullable
+ public static UserHandle getProfileParent(UserHandle user, @NonNull Context context) {
+ Context userContext = getUserContext(context, user);
+ UserManager userManager = userContext.getSystemService(UserManager.class);
+ return userManager.getProfileParent(user);
+ }
+
+ /**
+ * Returns all the enabled user profiles on the device
+ *
+ * @param user the {@link UserHandle} to get profiles for
+ * @param context the {@link Context}
+ * @param excludePrivate {@code true} to exclude private profiles from returned list of users
+ */
+ @NonNull
+ public static List<UserHandle> getUserProfiles(@NonNull UserHandle user,
+ @NonNull Context context, boolean excludePrivate) {
+ Context userContext = getUserContext(context, user);
+ UserManager userUserManager = userContext.getSystemService(UserManager.class);
+ List<UserHandle> profiles = userUserManager.getUserProfiles();
+ if (!excludePrivate) {
+ return profiles;
+ }
+ List<UserHandle> filteredProfiles = new ArrayList<>();
+ final int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; i++) {
+ UserHandle profile = profiles.get(i);
+ if (!isPrivateProfile(profile, userContext)) {
+ filteredProfiles.add(profile);
+ }
+ }
+ return filteredProfiles;
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/appops/data/repository/v31/AppOpRepository.kt b/PermissionController/src/com/android/permissioncontroller/appops/data/repository/v31/AppOpRepository.kt
index 3057f05e9..21ced3b28 100644
--- a/PermissionController/src/com/android/permissioncontroller/appops/data/repository/v31/AppOpRepository.kt
+++ b/PermissionController/src/com/android/permissioncontroller/appops/data/repository/v31/AppOpRepository.kt
@@ -112,7 +112,9 @@ class AppOpRepositoryImpl(
fun sendUpdate() {
if (job == null || job?.isActive == false) {
- job = coroutineScope.launch { trySend(getDiscreteOps(opNames)) }
+ job = coroutineScope.launch(dispatcher) {
+ trySend(getDiscreteOps(opNames))
+ }
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt b/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt
index bc6f774ad..c5191938e 100644
--- a/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt
+++ b/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt
@@ -18,6 +18,7 @@ package com.android.permissioncontroller.ecm
import android.annotation.SuppressLint
import android.app.AlertDialog
+import android.app.AppOpsManager
import android.app.Dialog
import android.app.ecm.EnhancedConfirmationManager
import android.content.Context
@@ -54,6 +55,7 @@ import com.android.role.controller.model.Roles
class EnhancedConfirmationDialogActivity : FragmentActivity() {
companion object {
private const val KEY_WAS_CLEAR_RESTRICTION_ALLOWED = "KEY_WAS_CLEAR_RESTRICTION_ALLOWED"
+ private const val REASON_PHONE_STATE = "phone_state"
}
private var wasClearRestrictionAllowed: Boolean = false
@@ -65,6 +67,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
finish()
return
}
+
if (savedInstanceState != null) {
wasClearRestrictionAllowed =
savedInstanceState.getBoolean(KEY_WAS_CLEAR_RESTRICTION_ALLOWED)
@@ -75,15 +78,24 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
val packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)
val settingIdentifier = intent.getStringExtra(Intent.EXTRA_SUBJECT)
val isEcmInApp = intent.getBooleanExtra(EXTRA_IS_ECM_IN_APP, false)
+ val reason = intent.getStringExtra(Intent.EXTRA_REASON)
require(uid != Process.INVALID_UID) { "EXTRA_UID cannot be null or invalid" }
require(!packageName.isNullOrEmpty()) { "EXTRA_PACKAGE_NAME cannot be null or empty" }
require(!settingIdentifier.isNullOrEmpty()) { "EXTRA_SUBJECT cannot be null or empty" }
-
wasClearRestrictionAllowed =
setClearRestrictionAllowed(packageName, UserHandle.getUserHandleForUid(uid))
- val setting = Setting.fromIdentifier(this, settingIdentifier, isEcmInApp)
+ val setting = Setting.fromIdentifier(this, settingIdentifier, isEcmInApp, reason)
+ if (
+ SettingType.fromIdentifier(this, settingIdentifier, isEcmInApp, reason) ==
+ SettingType.BLOCKED_DUE_TO_PHONE_STATE &&
+ !Flags.unknownCallPackageInstallBlockingEnabled()
+ ) {
+ finish()
+ return
+ }
+
if (DeviceUtils.isWear(this)) {
WearEnhancedConfirmationDialogFragment.newInstance(setting.title, setting.message)
.show(supportFragmentManager, WearEnhancedConfirmationDialogFragment.TAG)
@@ -116,15 +128,17 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
fun fromIdentifier(
context: Context,
settingIdentifier: String,
- isEcmInApp: Boolean
+ isEcmInApp: Boolean,
+ reason: String?,
): Setting {
- val settingType = SettingType.fromIdentifier(context, settingIdentifier, isEcmInApp)
+ val settingType =
+ SettingType.fromIdentifier(context, settingIdentifier, isEcmInApp, reason)
val label =
when (settingType) {
SettingType.PLATFORM_PERMISSION ->
KotlinUtils.getPermGroupLabel(
context,
- PermissionMapping.getGroupOfPlatformPermission(settingIdentifier)!!
+ PermissionMapping.getGroupOfPlatformPermission(settingIdentifier)!!,
)
SettingType.PLATFORM_PERMISSION_GROUP ->
KotlinUtils.getPermGroupLabel(context, settingIdentifier)
@@ -132,14 +146,37 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
context.getString(
Roles.get(context)[settingIdentifier]!!.shortLabelResource
)
+ SettingType.BLOCKED_DUE_TO_PHONE_STATE,
SettingType.OTHER -> null
}
- val url =
- context.getString(R.string.help_url_action_disabled_by_restricted_settings)
- return Setting(
- title = settingType.titleRes?.let { context.getString(it, label) },
+ var title: String?
+ var message: CharSequence?
+ if (settingType == SettingType.BLOCKED_DUE_TO_PHONE_STATE) {
+ title = settingType.titleRes?.let { context.getString(it) }
+ val settingMessage = getPhoneStateSettingMessage(context, settingIdentifier)
+ message = settingType.messageRes?.let { context.getString(it, settingMessage) }
+ } else {
+ val url =
+ context.getString(R.string.help_url_action_disabled_by_restricted_settings)
+ title = (settingType.titleRes?.let { context.getString(it, label) })
message =
settingType.messageRes?.let { Html.fromHtml(context.getString(it, url), 0) }
+ }
+ return Setting(title, message)
+ }
+
+ private fun getPhoneStateSettingMessage(
+ context: Context,
+ settingsIdentifier: String,
+ ): String {
+ return context.getString(
+ when (settingsIdentifier) {
+ AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE ->
+ R.string.enhanced_confirmation_phone_state_dialog_a11y_desc
+ AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES ->
+ R.string.enhanced_confirmation_phone_state_dialog_install_desc
+ else -> R.string.enhanced_confirmation_phone_state_dialog_generic_desc
+ }
)
}
}
@@ -148,29 +185,35 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
private enum class SettingType(val titleRes: Int?, val messageRes: Int?) {
PLATFORM_PERMISSION(
R.string.enhanced_confirmation_dialog_title_permission,
- R.string.enhanced_confirmation_dialog_desc_permission
+ R.string.enhanced_confirmation_dialog_desc_permission,
),
PLATFORM_PERMISSION_GROUP(
R.string.enhanced_confirmation_dialog_title_permission,
- R.string.enhanced_confirmation_dialog_desc_permission
+ R.string.enhanced_confirmation_dialog_desc_permission,
),
ROLE(
R.string.enhanced_confirmation_dialog_title_role,
- R.string.enhanced_confirmation_dialog_desc_role
+ R.string.enhanced_confirmation_dialog_desc_role,
),
OTHER(
R.string.enhanced_confirmation_dialog_title_settings_default,
- R.string.enhanced_confirmation_dialog_desc_settings_default
+ R.string.enhanced_confirmation_dialog_desc_settings_default,
+ ),
+ BLOCKED_DUE_TO_PHONE_STATE(
+ R.string.enhanced_confirmation_phone_state_dialog_title,
+ R.string.enhanced_confirmation_phone_state_dialog_desc,
);
companion object {
fun fromIdentifier(
context: Context,
settingIdentifier: String,
- isEcmInApp: Boolean
+ isEcmInApp: Boolean,
+ restrictionReason: String?,
): SettingType {
- if (!isEcmInApp) return SettingType.OTHER
return when {
+ restrictionReason == REASON_PHONE_STATE -> BLOCKED_DUE_TO_PHONE_STATE
+ !isEcmInApp -> OTHER
PermissionMapping.isRuntimePlatformPermission(settingIdentifier) &&
PermissionMapping.getGroupOfPlatformPermission(settingIdentifier) != null ->
PLATFORM_PERMISSION
@@ -178,7 +221,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
PLATFORM_PERMISSION_GROUP
settingIdentifier.startsWith("android.app.role.") &&
Roles.get(context).containsKey(settingIdentifier) -> ROLE
- else -> SettingType.OTHER
+ else -> OTHER
}
}
}
@@ -188,7 +231,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
this.dialogResult = dialogResult
setResult(
RESULT_OK,
- Intent().apply { putExtra(Intent.EXTRA_RETURN_RESULT, dialogResult.statsLogValue) }
+ Intent().apply { putExtra(Intent.EXTRA_RETURN_RESULT, dialogResult.statsLogValue) },
)
finish()
}
@@ -200,7 +243,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID),
settingIdentifier = intent.getStringExtra(Intent.EXTRA_SUBJECT)!!,
firstShowForApp = !wasClearRestrictionAllowed,
- dialogResult = dialogResult
+ dialogResult = dialogResult,
)
}
}
@@ -234,7 +277,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
return AlertDialog.Builder(dialogActivity)
.setView(createDialogView(dialogActivity, title, message))
- .setPositiveButton(R.string.enhanced_confirmation_dialog_ok) { _, _ ->
+ .setPositiveButton(R.string.dialog_close) { _, _ ->
dialogActivity.onDialogResult(DialogResult.Okay)
}
.create()
@@ -249,7 +292,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
private fun createDialogView(
context: Context,
title: String?,
- message: CharSequence?
+ message: CharSequence?,
): View =
LayoutInflater.from(context)
.inflate(R.layout.enhanced_confirmation_dialog, null)
diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
index d23225ed3..8b11036e8 100644
--- a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
+++ b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
@@ -73,6 +73,7 @@ import android.service.dreams.DreamService
import android.service.notification.NotificationListenerService
import android.service.voice.VoiceInteractionService
import android.service.wallpaper.WallpaperService
+import android.telecom.TelecomManager
import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
import android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS
import android.util.Log
@@ -658,6 +659,36 @@ suspend fun isPackageHibernationExemptBySystem(
return true
}
+ // Note that it's fine to check permissions instead of app ops as all these permissions were
+ // introduced before auto-revoke / hibernation in R.
+ val hasCallRelatedPermissions =
+ context.checkPermission(Manifest.permission.MANAGE_OWN_CALLS, -1 /* pid */, pkg.uid) ==
+ PERMISSION_GRANTED
+ && context.checkPermission(Manifest.permission.RECORD_AUDIO, -1 /* pid */, pkg.uid) ==
+ PERMISSION_GRANTED
+ && context.checkPermission(Manifest.permission.WRITE_CALL_LOG, -1 /* pid */, pkg.uid) ==
+ PERMISSION_GRANTED
+ if (hasCallRelatedPermissions) {
+ val phoneAccounts = context.getSystemService(TelecomManager::class.java)!!
+ .selfManagedPhoneAccounts
+ var hasRegisteredPhoneAccount = false
+ for (phoneAccount in phoneAccounts) {
+ if (pkg.packageName == phoneAccount.componentName.packageName) {
+ hasRegisteredPhoneAccount = true
+ break
+ }
+ }
+ if (hasRegisteredPhoneAccount) {
+ if (DEBUG_HIBERNATION_POLICY) {
+ DumpableLog.i(
+ LOG_TAG,
+ "Exempted ${pkg.packageName} - caller app"
+ )
+ }
+ return true
+ }
+ }
+
if (SdkLevel.isAtLeastS()) {
val hasInstallOrUpdatePermissions =
context.checkPermission(Manifest.permission.INSTALL_PACKAGES, -1 /* pid */, pkg.uid) ==
diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING
index 69a8f74be..038b2f992 100644
--- a/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/hibernation/TEST_MAPPING
@@ -11,5 +11,15 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "PermissionControllerMockingTests",
+ "options": [
+ {
+ "include-filter": "com.android.permissioncontroller.tests.mocking.hibernation"
+ }
+ ]
+ }
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt b/PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt
index 8e58d48d9..2428abb4a 100644
--- a/PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt
@@ -29,14 +29,15 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
-import androidx.wear.compose.foundation.lazy.ScalingLazyListState
import androidx.wear.compose.material.CircularProgressIndicator
-import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog
-import com.android.permissioncontroller.permission.ui.wear.elements.SingleButtonAlertDialog
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
+import com.android.permissioncontroller.wear.permission.components.material3.DialogButtonContent
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionConfirmationDialog
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionTheme
@Composable
fun WearConfirmationScreen(viewModel: WearConfirmationActivityViewModel) {
+ val materialUIVersion = ResourceHelper.materialUIVersionInSettings
// Wear screen doesn't show incident/bug report's optional reasons and images.
val showDialog = viewModel.showDialogLiveData.observeAsState(false)
val showDenyReport = viewModel.showDenyReportLiveData.observeAsState(false)
@@ -47,27 +48,25 @@ fun WearConfirmationScreen(viewModel: WearConfirmationActivityViewModel) {
if (isLoading) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
} else {
- if (showDenyReport.value) {
- contentArgs.value?.let {
- SingleButtonAlertDialog(
- showDialog = showDialog.value,
- title = it.title,
- message = it.message,
- onButtonClick = it.onDenyClick,
- scalingLazyListState = ScalingLazyListState(0)
+ contentArgs.value?.apply {
+ if (showDenyReport.value) {
+ WearPermissionConfirmationDialog(
+ materialUIVersion = materialUIVersion,
+ show = showDialog.value,
+ title = title,
+ message = message,
+ positiveButtonContent = DialogButtonContent(onClick = onDenyClick),
+ )
+ } else {
+ WearPermissionConfirmationDialog(
+ materialUIVersion = materialUIVersion,
+ show = showDialog.value,
+ title = title,
+ message = message,
+ positiveButtonContent = DialogButtonContent(onClick = onOkClick),
+ negativeButtonContent = DialogButtonContent(onClick = onCancelClick),
)
}
- return
- }
- contentArgs.value?.let {
- AlertDialog(
- showDialog = showDialog.value,
- title = it.title,
- message = it.message,
- onOKButtonClick = it.onOkClick,
- onCancelButtonClick = it.onCancelClick,
- scalingLazyListState = ScalingLazyListState(0)
- )
}
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java b/PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java
new file mode 100644
index 000000000..05d69e998
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/compat/AppOpsManagerCompat.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.compat;
+
+import android.annotation.SuppressLint;
+import android.app.AppOpsManager;
+import android.permission.flags.Flags;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Helper AppOpsManager compat class
+ */
+public class AppOpsManagerCompat {
+
+ private AppOpsManagerCompat() {}
+
+ /**
+ * For platform version <= V, call the deprecated unsafeCheckOpRawNoThrow. For newer platforms,
+ * call the new API checkOpRawNoThrow.
+ *
+ * @return the raw mode of the op
+ */
+ // TODO: b/379749734
+ @SuppressWarnings("deprecation")
+ @SuppressLint("NewApi")
+ public static int checkOpRawNoThrow(@NonNull AppOpsManager appOpsManager, @NonNull String op,
+ int uid, @NonNull String packageName) {
+ if (Flags.checkOpOverloadApiEnabled()) {
+ return appOpsManager.checkOpRawNoThrow(op, uid, packageName, null);
+ } else {
+ return appOpsManager.unsafeCheckOpRawNoThrow(op, uid, packageName);
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/compat/AppPermissionFragmentCompat.java b/PermissionController/src/com/android/permissioncontroller/permission/compat/AppPermissionFragmentCompat.java
index 188e3a9d0..50e3688e2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/compat/AppPermissionFragmentCompat.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/compat/AppPermissionFragmentCompat.java
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.permission.compat;
import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME;
+import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
@@ -29,6 +30,7 @@ import androidx.preference.PreferenceFragmentCompat;
import com.android.modules.utils.build.SdkLevel;
import com.android.permission.flags.Flags;
+import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.ui.handheld.max35.LegacyAppPermissionFragment;
import com.android.permissioncontroller.permission.ui.handheld.v36.AppPermissionFragment;
@@ -41,8 +43,10 @@ public class AppPermissionFragmentCompat {
* Create an instance of this fragment
*/
@NonNull
- public static PreferenceFragmentCompat createFragment() {
- if (SdkLevel.isAtLeastV() && Flags.appPermissionFragmentUsesPreferences()) {
+ public static PreferenceFragmentCompat createFragment(@NonNull Context context) {
+ if ((SdkLevel.isAtLeastB() && Flags.appPermissionFragmentUsesPreferences())
+ || (SdkLevel.isAtLeastV() && context.getResources().getBoolean(
+ R.bool.config_usePreferenceForAppPermissionSettings))) {
return new AppPermissionFragment();
} else {
return new LegacyAppPermissionFragment();
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt
index 1e44f16bd..3202f5b69 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/AppOpLiveData.kt
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.permission.data
import android.app.AppOpsManager
import android.app.Application
import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.compat.AppOpsManagerCompat
/**
* A LiveData which represents the appop state
@@ -36,13 +37,13 @@ private constructor(
private val app: Application,
private val packageName: String,
private val op: String,
- private val uid: Int
+ private val uid: Int,
) : SmartUpdateMediatorLiveData<Int>() {
private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
override fun onUpdate() {
- value = appOpsManager.unsafeCheckOpNoThrow(op, uid, packageName)
+ value = AppOpsManagerCompat.checkOpRawNoThrow(appOpsManager, op, uid, packageName)
}
override fun onActive() {
@@ -62,7 +63,7 @@ private constructor(
PermissionControllerApplication.get(),
key.first,
key.second,
- key.third
+ key.third,
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt
index b17098a13..ad356d5f7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/AppPermGroupUiInfoLiveData.kt
@@ -18,6 +18,7 @@ package com.android.permissioncontroller.permission.data
import android.Manifest
import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+import android.Manifest.permission_group.READ_MEDIA_VISUAL
import android.Manifest.permission_group.STORAGE
import android.app.AppOpsManager
import android.app.Application
@@ -66,13 +67,8 @@ private constructor(
private val isHealth = Utils.isHealthPermissionGroup(permGroupName)
init {
- isSpecialLocation =
- LocationUtils.isLocationGroupAndProvider(app, permGroupName, packageName) ||
- LocationUtils.isLocationGroupAndControllerExtraPackage(
- app,
- permGroupName,
- packageName
- )
+ isSpecialLocation = LightAppPermGroupLiveData
+ .isSpecialLocationGranted(app, packageName, permGroupName, user) != null
addSource(packageInfoLiveData) { update() }
@@ -233,8 +229,8 @@ private constructor(
* user
*/
private fun isUserSet(permissionState: Map<String, PermState>): Boolean {
- val flagMask =
- PackageManager.FLAG_PERMISSION_USER_SET or PackageManager.FLAG_PERMISSION_USER_FIXED
+ val flagMask = PackageManager.FLAG_PERMISSION_USER_SET or
+ PackageManager.FLAG_PERMISSION_USER_FIXED or PackageManager.FLAG_PERMISSION_ONE_TIME
return permissionState.any { (it.value.permFlags and flagMask) != 0 }
}
@@ -258,9 +254,14 @@ private constructor(
allPermInfos: Map<String, LightPermInfo>,
pkg: LightPackageInfo
): PermGrantState {
- val specialLocationState = getIsSpecialLocationState()
+ val specialLocationState = LightAppPermGroupLiveData
+ .isSpecialLocationGranted(app, packageName, permGroupName, user)
+ val specialFixedStorage = LightAppPermGroupLiveData
+ .isSpecialFixedStorageGranted(app, packageName, permGroupName, pkg.uid)
if (isStorage && isFullFilesAccessGranted(pkg)) {
return PermGrantState.PERMS_ALLOWED
+ } else if (permGroupName == READ_MEDIA_VISUAL && specialFixedStorage) {
+ return PermGrantState.PERMS_ALLOWED
}
var hasPermWithBackground = false
@@ -332,29 +333,6 @@ private constructor(
return PermGrantState.PERMS_DENIED
}
- private fun getIsSpecialLocationState(): Boolean? {
- if (!isSpecialLocation) {
- return null
- }
-
- val userContext = Utils.getUserContext(app, user)
- if (LocationUtils.isLocationGroupAndProvider(userContext, permGroupName, packageName)) {
- return LocationUtils.isLocationEnabled(userContext)
- }
- // The permission of the extra location controller package is determined by the
- // status of the controller package itself.
- if (
- LocationUtils.isLocationGroupAndControllerExtraPackage(
- userContext,
- permGroupName,
- packageName
- )
- ) {
- return LocationUtils.isExtraLocationControllerPackageEnabled(userContext)
- }
- return null
- }
-
private fun isFullFilesAccessGranted(pkg: LightPackageInfo): Boolean {
val packageState =
if (!FullStoragePermissionAppsLiveData.isStale) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt
index 4a2d3b68a..2b6d9728e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/FullStoragePermissionAppsLiveData.kt
@@ -28,6 +28,7 @@ import android.app.Application
import android.os.Build
import android.os.UserHandle
import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.compat.AppOpsManagerCompat
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
import kotlinx.coroutines.Job
@@ -46,7 +47,7 @@ object FullStoragePermissionAppsLiveData :
val packageName: String,
val user: UserHandle,
val isLegacy: Boolean,
- val isGranted: Boolean
+ val isGranted: Boolean,
)
init {
@@ -88,7 +89,7 @@ object FullStoragePermissionAppsLiveData :
fun getFullStorageStateForPackage(
appOpsManager: AppOpsManager,
packageInfo: LightPackageInfo,
- userHandle: UserHandle? = null
+ userHandle: UserHandle? = null,
): FullStoragePackageState? {
val sdk = packageInfo.targetSdkVersion
val user = userHandle ?: UserHandle.getUserHandleForUid(packageInfo.uid)
@@ -97,29 +98,31 @@ object FullStoragePermissionAppsLiveData :
packageInfo.packageName,
user,
isLegacy = true,
- isGranted = true
+ isGranted = true,
)
} else if (
sdk <= Build.VERSION_CODES.Q &&
- appOpsManager.unsafeCheckOpNoThrow(
+ AppOpsManagerCompat.checkOpRawNoThrow(
+ appOpsManager,
OPSTR_LEGACY_STORAGE,
packageInfo.uid,
- packageInfo.packageName
+ packageInfo.packageName,
) == MODE_ALLOWED
) {
return FullStoragePackageState(
packageInfo.packageName,
user,
isLegacy = true,
- isGranted = true
+ isGranted = true,
)
}
if (MANAGE_EXTERNAL_STORAGE in packageInfo.requestedPermissions) {
val mode =
- appOpsManager.unsafeCheckOpNoThrow(
+ AppOpsManagerCompat.checkOpRawNoThrow(
+ appOpsManager,
OPSTR_MANAGE_EXTERNAL_STORAGE,
packageInfo.uid,
- packageInfo.packageName
+ packageInfo.packageName,
)
val granted =
mode == MODE_ALLOWED ||
@@ -130,7 +133,7 @@ object FullStoragePermissionAppsLiveData :
packageInfo.packageName,
user,
isLegacy = false,
- isGranted = granted
+ isGranted = granted,
)
}
return null
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/LightAppPermGroupLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/LightAppPermGroupLiveData.kt
index 67b765097..d66d6ec6c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/LightAppPermGroupLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/LightAppPermGroupLiveData.kt
@@ -16,7 +16,13 @@
package com.android.permissioncontroller.permission.data
+import android.Manifest.permission_group.READ_MEDIA_VISUAL
+import android.Manifest.permission_group.STORAGE
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES
import android.app.Application
+import android.app.role.RoleManager
import android.content.pm.PackageManager
import android.content.pm.PermissionInfo
import android.os.Build
@@ -121,19 +127,6 @@ private constructor(
LightPermission(packageInfo, permInfo, permState, foregroundPerms)
}
- // Determine if this app permission group is a special location package or provider
- var specialLocationGrant: Boolean? = null
- val userContext = Utils.getUserContext(app, user)
- if (LocationUtils.isLocationGroupAndProvider(userContext, permGroupName, packageName)) {
- specialLocationGrant = LocationUtils.isLocationEnabled(userContext)
- } else if (
- LocationUtils.isLocationGroupAndControllerExtraPackage(app, permGroupName, packageName)
- ) {
- // The permission of the extra location controller package is determined by the status
- // of the controller package itself.
- specialLocationGrant =
- LocationUtils.isExtraLocationControllerPackageEnabled(userContext)
- }
val hasInstallToRuntimeSplit = hasInstallToRuntimeSplit(packageInfo, permissionMap)
value =
@@ -142,7 +135,8 @@ private constructor(
permGroup.groupInfo,
permissionMap,
hasInstallToRuntimeSplit,
- specialLocationGrant
+ isSpecialLocationGranted(app, packageName, permGroupName, user),
+ isSpecialFixedStorageGranted(app, packageName, permGroupName, packageInfo.uid)
)
}
@@ -234,5 +228,56 @@ private constructor(
deviceId
)
}
+
+ private const val SYSTEM_GALLERY_ROLE_NAME = "android.app.role.SYSTEM_GALLERY"
+
+ // Returns true if this app is the location provider or location extra package, and location
+ // access is granted, false if it is the provider/extra, and location is not granted, and
+ // null if it is not a special package
+ fun isSpecialLocationGranted(
+ app: Application,
+ packageName: String,
+ permGroupName: String,
+ user: UserHandle
+ ): Boolean? {
+ val userContext = Utils.getUserContext(app, user)
+ return if (
+ LocationUtils.isLocationGroupAndProvider(userContext, permGroupName, packageName)
+ ) {
+ LocationUtils.isLocationEnabled(userContext)
+ } else if (
+ LocationUtils.isLocationGroupAndControllerExtraPackage(app, permGroupName, packageName)
+ ) {
+ // The permission of the extra location controller package is determined by the status
+ // of the controller package itself.
+ LocationUtils.isExtraLocationControllerPackageEnabled(userContext)
+ } else {
+ null
+ }
+ }
+
+ // Gallery role is static, so we only need to get the set gallery app once
+ private val systemGalleryApps: List<String> by lazy {
+ val roleManager = PermissionControllerApplication.get()
+ .getSystemService(RoleManager::class.java) ?: return@lazy emptyList()
+ roleManager.getRoleHolders(SYSTEM_GALLERY_ROLE_NAME)
+ }
+
+ fun isSpecialFixedStorageGranted(
+ app: Application,
+ packageName: String,
+ permGroupName: String,
+ uid: Int
+ ): Boolean {
+ if (permGroupName != READ_MEDIA_VISUAL && permGroupName != STORAGE) {
+ return false
+ }
+ if (packageName !in systemGalleryApps) {
+ return false
+ }
+ // This is the storage group, and the gallery app. Check the write media app op
+ val appOps = app.getSystemService(AppOpsManager::class.java)
+ return appOps.checkOpNoThrow(OPSTR_WRITE_MEDIA_IMAGES, uid, packageName) == MODE_ALLOWED
+ }
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt
deleted file mode 100644
index a3fc40232..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.data.v31
-
-import android.app.AppOpsManager
-import android.app.AppOpsManager.HISTORY_FLAG_DISCRETE
-import android.app.AppOpsManager.HISTORY_FLAG_GET_ATTRIBUTION_CHAINS
-import android.app.AppOpsManager.HistoricalOps
-import android.app.AppOpsManager.HistoricalOpsRequest
-import android.app.AppOpsManager.OP_FLAG_SELF
-import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED
-import android.app.Application
-import android.os.UserHandle
-import android.os.UserManager
-import com.android.modules.utils.build.SdkLevel
-import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData
-import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps
-import java.util.concurrent.TimeUnit
-import kotlin.coroutines.suspendCoroutine
-import kotlinx.coroutines.Job
-
-/**
- * LiveData class tracking [LightHistoricalPackageOps] for all packages on the device and for the
- * provided app ops.
- *
- * App ops data is retrieved from [AppOpsManager] and is updated whenever app ops data changes are
- * heard.
- */
-class AllLightHistoricalPackageOpsLiveData(app: Application, val opNames: Set<String>) :
- SmartAsyncMediatorLiveData<Map<Pair<String, UserHandle>, LightHistoricalPackageOps>>(),
- AppOpsManager.OnOpActiveChangedListener,
- AppOpsManager.OnOpNotedListener,
- AppOpsManager.OnOpChangedListener {
-
- private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
- private val userManager = app.getSystemService(UserManager::class.java)!!
-
- override fun onActive() {
- super.onActive()
-
- opNames.forEach { opName ->
- // TODO(b/262035952): We watch each active op individually as startWatchingActive only
- // registers the callback if all ops are valid. Fix this behavior so if one op is
- // invalid it doesn't affect the other ops.
- try {
- appOpsManager.startWatchingActive(arrayOf(opName), { it.run() }, this)
- } catch (ignored: IllegalArgumentException) {
- // Older builds may not support all requested app ops.
- }
-
- try {
- appOpsManager.startWatchingMode(opName, /* all packages */ null, this)
- } catch (ignored: IllegalArgumentException) {
- // Older builds may not support all requested app ops.
- }
-
- if (SdkLevel.isAtLeastU()) {
- try {
- appOpsManager.startWatchingNoted(arrayOf(opName), this)
- } catch (ignored: IllegalArgumentException) {
- // Older builds may not support all requested app ops.
- }
- }
- }
- }
-
- override fun onInactive() {
- super.onInactive()
-
- appOpsManager.stopWatchingActive(this)
- appOpsManager.stopWatchingMode(this)
- }
-
- override suspend fun loadDataAndPostValue(job: Job) {
- if (job.isCancelled) {
- return
- }
-
- val allLightHistoricalPackageOps =
- mutableMapOf<Pair<String, UserHandle>, LightHistoricalPackageOps>()
-
- val endTimeMillis = System.currentTimeMillis()
- val beginTimeMillis = endTimeMillis - TimeUnit.DAYS.toMillis(7)
-
- val allProfilesInCurrentUser = userManager.userProfiles
-
- val request =
- HistoricalOpsRequest.Builder(beginTimeMillis, endTimeMillis)
- .setFlags(OP_FLAG_SELF or OP_FLAG_TRUSTED_PROXIED)
- .setHistoryFlags(HISTORY_FLAG_DISCRETE or HISTORY_FLAG_GET_ATTRIBUTION_CHAINS)
- .build()
-
- val historicalOps = suspendCoroutine {
- appOpsManager.getHistoricalOps(request, { it.run() }) { ops: HistoricalOps ->
- it.resumeWith(Result.success(ops))
- }
- }
-
- for (i in 0 until historicalOps.uidCount) {
- val historicalUidOps = historicalOps.getUidOpsAt(i)
- val userHandle = UserHandle.getUserHandleForUid(historicalUidOps.uid)
- if (userHandle !in allProfilesInCurrentUser) {
- continue
- }
- for (j in 0 until historicalUidOps.packageCount) {
- val historicalPackageOps = historicalUidOps.getPackageOpsAt(j)
- allLightHistoricalPackageOps[Pair(historicalPackageOps.packageName, userHandle)] =
- LightHistoricalPackageOps(historicalPackageOps, userHandle, opNames)
- }
- }
-
- postValue(allLightHistoricalPackageOps)
- }
-
- override fun onOpChanged(op: String?, packageName: String?) {
- update()
- }
-
- override fun onOpActiveChanged(op: String, uid: Int, packageName: String, active: Boolean) {
- update()
- }
-
- override fun onOpNoted(
- code: String,
- uid: Int,
- packageName: String,
- attributionTag: String?,
- flags: Int,
- result: Int
- ) {
- update()
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/domain/usecase/v31/GetPermissionGroupUsageDetailsUseCase.kt b/PermissionController/src/com/android/permissioncontroller/permission/domain/usecase/v31/GetPermissionGroupUsageDetailsUseCase.kt
index 16eaaf6f2..5ba649fd3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/domain/usecase/v31/GetPermissionGroupUsageDetailsUseCase.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/domain/usecase/v31/GetPermissionGroupUsageDetailsUseCase.kt
@@ -21,6 +21,7 @@ import android.app.AppOpsManager
import android.os.UserHandle
import android.permission.flags.Flags
import com.android.modules.utils.build.SdkLevel
+import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.appops.data.model.v31.DiscretePackageOpsModel
import com.android.permissioncontroller.appops.data.model.v31.DiscretePackageOpsModel.DiscreteOpModel
import com.android.permissioncontroller.appops.data.repository.v31.AppOpRepository
@@ -29,6 +30,7 @@ import com.android.permissioncontroller.permission.domain.model.v31.PermissionTi
import com.android.permissioncontroller.permission.domain.model.v31.PermissionTimelineUsageModelWrapper
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.Companion.CLUSTER_SPACING_MINUTES
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.Companion.ONE_MINUTE_MS
+import com.android.permissioncontroller.permission.utils.LocationUtils
import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.pm.data.repository.v31.PackageRepository
import com.android.permissioncontroller.role.data.repository.v31.RoleRepository
@@ -48,6 +50,9 @@ class GetPermissionGroupUsageDetailsUseCase(
private val appOpRepository: AppOpRepository,
private val roleRepository: RoleRepository,
private val userRepository: UserRepository,
+ // Allow tests to inject as on T- READ_DEVICE_CONFIG permission check is enforced.
+ private val attributionLabelFix: Boolean =
+ com.android.permission.flags.Flags.permissionTimelineAttributionLabelFix(),
) {
operator fun invoke(coroutineScope: CoroutineScope): Flow<PermissionTimelineUsageModelWrapper> {
val opNames = requireNotNull(permissionGroupToOpNames[permissionGroup])
@@ -72,7 +77,7 @@ class GetPermissionGroupUsageDetailsUseCase(
permissionGroup,
packageOps.userId,
permissionRepository,
- packageRepository
+ packageRepository,
)
packageOps
}
@@ -86,43 +91,61 @@ class GetPermissionGroupUsageDetailsUseCase(
}
}
+ // show attribution on T+ for location provider only..
+ private fun shouldShowAttributionLabel(packageName: String): Boolean {
+ return if (attributionLabelFix) {
+ SdkLevel.isAtLeastT() &&
+ LocationUtils.isLocationProvider(PermissionControllerApplication.get(), packageName)
+ } else true
+ }
+
/** Group app op accesses by attribution label if it is available and user visible. */
private suspend fun List<DiscretePackageOpsModel>.groupByAttributionLabelIfNeeded() =
map { packageOps ->
- val attributionInfo =
- packageRepository.getPackageAttributionInfo(
- packageOps.packageName,
- UserHandle.of(packageOps.userId)
- )
- if (attributionInfo != null) {
- if (attributionInfo.areUserVisible && attributionInfo.tagResourceMap != null) {
- val attributionLabelOpsMap: Map<String?, List<DiscreteOpModel>> =
- packageOps.appOpAccesses
- .map { appOpEntry ->
- val resourceId =
- attributionInfo.tagResourceMap[appOpEntry.attributionTag]
- val label = attributionInfo.resourceLabelMap?.get(resourceId)
- label to appOpEntry
- }
- .groupBy { labelAppOpEntryPair -> labelAppOpEntryPair.first }
- .mapValues { (_, values) ->
- values.map { labelAppOpEntryPair -> labelAppOpEntryPair.second }
- }
+ if (!shouldShowAttributionLabel(packageOps.packageName)) {
+ listOf(packageOps)
+ } else {
+ val attributionInfo =
+ packageRepository.getPackageAttributionInfo(
+ packageOps.packageName,
+ UserHandle.of(packageOps.userId),
+ )
+ if (attributionInfo != null) {
+ if (
+ attributionInfo.areUserVisible && attributionInfo.tagResourceMap != null
+ ) {
+ val attributionLabelOpsMap: Map<String?, List<DiscreteOpModel>> =
+ packageOps.appOpAccesses
+ .map { appOpEntry ->
+ val resourceId =
+ attributionInfo.tagResourceMap[
+ appOpEntry.attributionTag]
+ val label =
+ attributionInfo.resourceLabelMap?.get(resourceId)
+ label to appOpEntry
+ }
+ .groupBy { labelAppOpEntryPair -> labelAppOpEntryPair.first }
+ .mapValues { (_, values) ->
+ values.map { labelAppOpEntryPair ->
+ labelAppOpEntryPair.second
+ }
+ }
- attributionLabelOpsMap.map { labelAppOpsEntry ->
- DiscretePackageOpsModel(
- packageOps.packageName,
- packageOps.userId,
- appOpAccesses = labelAppOpsEntry.value,
- attributionLabel = labelAppOpsEntry.key,
- isUserSensitive = packageOps.isUserSensitive,
- )
+ attributionLabelOpsMap.map { labelAppOpsEntry ->
+ DiscretePackageOpsModel(
+ packageOps.packageName,
+ packageOps.userId,
+ appOpAccesses = labelAppOpsEntry.value,
+ attributionLabel = labelAppOpsEntry.key,
+ isUserSensitive = packageOps.isUserSensitive,
+ )
+ }
+ } else {
+ listOf(packageOps)
}
} else {
listOf(packageOps)
}
- } else {
- listOf(packageOps)
}
}
.flatten()
@@ -147,7 +170,7 @@ class GetPermissionGroupUsageDetailsUseCase(
packageOps.userId,
currentCluster.toMutableList(),
packageOps.attributionLabel,
- packageOps.isUserSensitive
+ packageOps.isUserSensitive,
)
)
currentCluster.clear()
@@ -164,7 +187,7 @@ class GetPermissionGroupUsageDetailsUseCase(
packageOps.userId,
currentCluster.toMutableList(),
packageOps.attributionLabel,
- packageOps.isUserSensitive
+ packageOps.isUserSensitive,
)
)
}
@@ -220,7 +243,7 @@ class GetPermissionGroupUsageDetailsUseCase(
private fun canAccessBeAddedToCluster(
currentAccess: DiscreteOpModel,
- clusteredAccesses: List<DiscreteOpModel>
+ clusteredAccesses: List<DiscreteOpModel>,
): Boolean {
val clusterOp = clusteredAccesses.last().opName
if (
@@ -282,7 +305,7 @@ class GetPermissionGroupUsageDetailsUseCase(
listOf(
Manifest.permission_group.CAMERA,
Manifest.permission_group.LOCATION,
- Manifest.permission_group.MICROPHONE
+ Manifest.permission_group.MICROPHONE,
)
permissionGroups.forEach { permissionGroup ->
val opNames =
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt
index 61a604de8..18f4ce203 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightAppPermGroup.kt
@@ -35,19 +35,24 @@ import com.android.permissioncontroller.permission.utils.Utils
* @param specialLocationGrant If this package is the location provider, or the extra location
* package, then the grant state of the group is not determined by the grant state of individual
* permissions, but by other system properties
+ * @param specialFixedStorageGrant If this package holds the SYSTEM_GALLERY role, and has the
+ * WRITE_MEDIA_IMAGES app op granted, then we should show the grant state of the storage
+ * permissions as system fixed and granted.
+ *
*/
data class LightAppPermGroup(
val packageInfo: LightPackageInfo,
val permGroupInfo: LightPermGroupInfo,
val allPermissions: Map<String, LightPermission>,
val hasInstallToRuntimeSplit: Boolean,
- val specialLocationGrant: Boolean?
+ val specialLocationGrant: Boolean?,
+ val specialFixedStorageGrant: Boolean,
) {
constructor(
pI: LightPackageInfo,
pGI: LightPermGroupInfo,
perms: Map<String, LightPermission>
- ) : this(pI, pGI, perms, false, null)
+ ) : this(pI, pGI, perms, false, null, false)
/** All unrestricted permissions. Usually restricted permissions are ignored */
val permissions: Map<String, LightPermission> =
@@ -83,7 +88,8 @@ data class LightAppPermGroup(
permissions.filter { it.key in foregroundPermNames },
packageInfo,
isPlatformPermissionGroup,
- specialLocationGrant
+ specialLocationGrant,
+ specialFixedStorageGrant
)
val background =
@@ -91,7 +97,8 @@ data class LightAppPermGroup(
permissions.filter { it.key in backgroundPermNames },
packageInfo,
isPlatformPermissionGroup,
- specialLocationGrant
+ specialLocationGrant,
+ specialFixedStorageGrant
)
/** Whether or not this App Permission Group has a permission which has a background mode */
@@ -167,18 +174,20 @@ data class LightAppPermGroup(
* contained in the full group
* @param isPlatformPermissionGroup Whether this is a platform permission group
* @param specialLocationGrant Whether this is a special location package
+ * @param specialFixedStorageGrant Whether this is a special storage grant
*/
data class AppPermSubGroup
internal constructor(
private val permissions: Map<String, LightPermission>,
private val packageInfo: LightPackageInfo,
private val isPlatformPermissionGroup: Boolean,
- private val specialLocationGrant: Boolean?
+ private val specialLocationGrant: Boolean?,
+ private val specialFixedStorageGrant: Boolean
) {
/** Whether any of this App Permission SubGroup's permissions are granted */
val isGranted =
specialLocationGrant
- ?: permissions.any {
+ ?: specialFixedStorageGrant || permissions.any {
val mayGrantByPlatformOrSystem =
!isPlatformPermissionGroup || it.value.isPlatformOrSystem
it.value.isGranted && mayGrantByPlatformOrSystem
@@ -216,7 +225,7 @@ data class LightAppPermGroup(
val isPolicyFixed = permissions.any { it.value.isPolicyFixed }
/** Whether any of this App Permission Subgroup's permissions are fixed by the system */
- val isSystemFixed = permissions.any { it.value.isSystemFixed }
+ val isSystemFixed = permissions.any { it.value.isSystemFixed } || specialFixedStorageGrant
/** Whether any of this App Permission Subgroup's permissions are fixed by the user */
val isUserFixed = permissions.any { it.value.isUserFixed }
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/AppPermissionId.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/AppPermissionId.kt
deleted file mode 100644
index 061bcb8a3..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/AppPermissionId.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.android.permissioncontroller.permission.model.livedatatypes.v31
-
-import android.os.UserHandle
-
-/** Identifier for an app permission group combination. */
-data class AppPermissionId(
- val packageName: String,
- val userHandle: UserHandle,
- val permissionGroup: String,
-)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt
deleted file mode 100644
index 04cc8a796..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.model.livedatatypes.v31
-
-import android.app.AppOpsManager.AttributedHistoricalOps
-import android.app.AppOpsManager.AttributedOpEntry
-import android.app.AppOpsManager.HistoricalOp
-import android.app.AppOpsManager.HistoricalPackageOps
-import android.app.AppOpsManager.OP_FLAG_SELF
-import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED
-import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY
-import android.app.AppOpsManager.OpEventProxyInfo
-import android.os.UserHandle
-import com.android.permissioncontroller.permission.utils.PermissionMapping.getPlatformPermissionGroupForOp
-
-/**
- * Light version of [HistoricalPackageOps] class, tracking the last permission access for system
- * permission groups.
- */
-data class LightHistoricalPackageOps(
- /** Name of the package. */
- val packageName: String,
- /** [UserHandle] running the package. */
- val userHandle: UserHandle,
- /**
- * Data about permission accesses, one [AppPermissionDiscreteAccesses] for each permission
- * group.
- */
- // TODO(b/262042582): Consider removing this field and using attributed accesses aggregated over
- // attribution tags instead.
- val appPermissionDiscreteAccesses: List<AppPermissionDiscreteAccesses>,
- /**
- * Attributed data about permission accesses, one [AttributedAppPermissionDiscreteAccesses] for
- * each permission group.
- */
- val attributedAppPermissionDiscreteAccesses: List<AttributedAppPermissionDiscreteAccesses>
-) {
- constructor(
- historicalPackageOps: HistoricalPackageOps,
- userHandle: UserHandle,
- opNames: Set<String>
- ) : this(
- historicalPackageOps.packageName,
- userHandle,
- historicalPackageOps.getAppPermissionDiscreteAccesses(userHandle, opNames),
- historicalPackageOps.getAttributedAppPermissionDiscreteAccesses(userHandle, opNames),
- )
-
- /** Companion object for [LightHistoricalPackageOps]. */
- companion object {
- /** String to represent the absence of an attribution tag. */
- const val NO_ATTRIBUTION_TAG = "no_attribution_tag"
- /** String to represent the absence of a permission group. */
- private const val NO_PERM_GROUP = "no_perm_group"
- private const val DISCRETE_ACCESS_OP_FLAGS =
- OP_FLAG_SELF or OP_FLAG_TRUSTED_PROXIED or OP_FLAG_TRUSTED_PROXY
-
- /**
- * Creates a list of [AppPermissionDiscreteAccesses] for the provided package, user and ops.
- */
- private fun HistoricalPackageOps.getAppPermissionDiscreteAccesses(
- userHandle: UserHandle,
- opNames: Set<String>
- ): List<AppPermissionDiscreteAccesses> {
- val permissionsToOpNames = partitionOpsByPermission(opNames)
- val appPermissionDiscreteAccesses = mutableListOf<AppPermissionDiscreteAccesses>()
- for (permissionToOpNames in permissionsToOpNames.entries) {
- this.getDiscreteAccesses(permissionToOpNames.value)?.let {
- appPermissionDiscreteAccesses.add(
- AppPermissionDiscreteAccesses(
- AppPermissionId(packageName, userHandle, permissionToOpNames.key),
- it
- )
- )
- }
- }
-
- return appPermissionDiscreteAccesses
- }
-
- /**
- * Creates a list of [AttributedAppPermissionDiscreteAccesses] for the provided package,
- * user and ops.
- */
- private fun HistoricalPackageOps.getAttributedAppPermissionDiscreteAccesses(
- userHandle: UserHandle,
- opNames: Set<String>
- ): List<AttributedAppPermissionDiscreteAccesses> {
- val permissionsToOpNames = partitionOpsByPermission(opNames)
- val attributedAppPermissionDiscreteAccesses =
- mutableMapOf<AppPermissionId, MutableMap<String, List<DiscreteAccess>>>()
-
- val attributedHistoricalOpsList = mutableListOf<AttributedHistoricalOps>()
- for (i in 0 until attributedOpsCount) {
- attributedHistoricalOpsList.add(getAttributedOpsAt(i))
- }
-
- for (permissionToOpNames in permissionsToOpNames.entries) {
- attributedHistoricalOpsList.forEach { attributedHistoricalOps ->
- attributedHistoricalOps.getDiscreteAccesses(permissionToOpNames.value)?.let {
- discAccessData ->
- val appPermissionId =
- AppPermissionId(packageName, userHandle, permissionToOpNames.key)
- if (!attributedAppPermissionDiscreteAccesses.containsKey(appPermissionId)) {
- attributedAppPermissionDiscreteAccesses[appPermissionId] =
- mutableMapOf()
- }
- attributedAppPermissionDiscreteAccesses[appPermissionId]?.put(
- attributedHistoricalOps.tag ?: NO_ATTRIBUTION_TAG,
- discAccessData
- )
- }
- }
- }
-
- return attributedAppPermissionDiscreteAccesses.map {
- AttributedAppPermissionDiscreteAccesses(it.key, it.value)
- }
- }
-
- /**
- * Retrieves all discrete accesses for the provided op names, if any.
- *
- * Returns null if there are no accesses.
- */
- private fun HistoricalPackageOps.getDiscreteAccesses(
- opNames: List<String>
- ): List<DiscreteAccess>? {
- if (opCount == 0) {
- return null
- }
-
- val historicalOps = mutableListOf<HistoricalOp>()
- for (opName in opNames) {
- getOp(opName)?.let { historicalOps.add(it) }
- }
-
- val discreteAccessList = mutableListOf<DiscreteAccess>()
- historicalOps.forEach {
- for (i in 0 until it.discreteAccessCount) {
- val opEntry: AttributedOpEntry = it.getDiscreteAccessAt(i)
- discreteAccessList.add(
- DiscreteAccess(
- it.opName,
- opEntry.getLastAccessTime(DISCRETE_ACCESS_OP_FLAGS),
- opEntry.getLastDuration(DISCRETE_ACCESS_OP_FLAGS),
- opEntry.getLastProxyInfo(DISCRETE_ACCESS_OP_FLAGS)
- )
- )
- }
- }
-
- if (discreteAccessList.isEmpty()) {
- return null
- }
- return discreteAccessList.sortedWith(compareBy { -it.accessTimeMs })
- }
-
- /**
- * Retrieves all discrete accesses for the provided op names, if any.
- *
- * Returns null if there are no accesses.
- */
- private fun AttributedHistoricalOps.getDiscreteAccesses(
- opNames: List<String>
- ): List<DiscreteAccess>? {
- if (opCount == 0) {
- return null
- }
-
- val historicalOps = mutableListOf<HistoricalOp>()
- for (opName in opNames) {
- getOp(opName)?.let { historicalOps.add(it) }
- }
-
- val discreteAccessList = mutableListOf<DiscreteAccess>()
- historicalOps.forEach {
- for (i in 0 until it.discreteAccessCount) {
- val attributedOpEntry: AttributedOpEntry = it.getDiscreteAccessAt(i)
- discreteAccessList.add(
- DiscreteAccess(
- it.opName,
- attributedOpEntry.getLastAccessTime(DISCRETE_ACCESS_OP_FLAGS),
- attributedOpEntry.getLastDuration(DISCRETE_ACCESS_OP_FLAGS),
- attributedOpEntry.getLastProxyInfo(DISCRETE_ACCESS_OP_FLAGS)
- )
- )
- }
- }
-
- if (discreteAccessList.isEmpty()) {
- return null
- }
- return discreteAccessList.sortedWith(compareBy { -it.accessTimeMs })
- }
-
- private fun partitionOpsByPermission(ops: Set<String>): Map<String, List<String>> =
- ops.groupBy { getPlatformPermissionGroupForOp(it) ?: NO_PERM_GROUP }
- .filter { it.key != NO_PERM_GROUP }
- }
-
- /**
- * Data class representing permissions accesses for a particular permission group by a
- * particular package and user.
- */
- data class AppPermissionDiscreteAccesses(
- val appPermissionId: AppPermissionId,
- val discreteAccesses: List<DiscreteAccess>
- )
-
- /**
- * Data class representing permissions accesses for a particular permission group by a
- * particular package and user, partitioned by attribution tag.
- */
- data class AttributedAppPermissionDiscreteAccesses(
- val appPermissionId: AppPermissionId,
- val attributedDiscreteAccesses: Map<String, List<DiscreteAccess>>
- )
-
- /** Data class representing a discrete permission access. */
- data class DiscreteAccess(
- val opName: String,
- val accessTimeMs: Long,
- val accessDurationMs: Long,
- val proxy: OpEventProxyInfo?
- )
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt
deleted file mode 100644
index b65fda5ea..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.model.livedatatypes.v31
-
-import android.app.AppOpsManager.OP_FLAG_SELF
-import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED
-import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY
-import android.app.AppOpsManager.PackageOps
-import android.os.UserHandle
-import com.android.permissioncontroller.permission.utils.PermissionMapping.getPlatformPermissionGroupForOp
-
-/**
- * Light version of [PackageOps] class, tracking the last permission access for system permission
- * groups.
- */
-data class LightPackageOps(
- /** Name of the package. */
- val packageName: String,
- /** [UserHandle] running the package. */
- val userHandle: UserHandle,
- /**
- * Mapping of permission group name to the last access time of any op backing a permission in
- * the group.
- */
- val lastPermissionGroupAccessTimesMs: Map<String, Long>
-) {
- constructor(
- ops: Set<String>,
- packageOps: PackageOps
- ) : this(
- packageOps.packageName,
- UserHandle.getUserHandleForUid(packageOps.uid),
- createLastPermissionGroupAccessTimesMap(ops, packageOps)
- )
-
- /** Companion object for [LightPackageOps]. */
- companion object {
- /** Flags to use for querying an op's last access time. */
- private const val OPS_LAST_ACCESS_FLAGS =
- OP_FLAG_SELF or OP_FLAG_TRUSTED_PROXIED or OP_FLAG_TRUSTED_PROXY
-
- /** Creates a mapping from permission group to the last time it was accessed. */
- private fun createLastPermissionGroupAccessTimesMap(
- opNames: Set<String>,
- packageOps: PackageOps
- ): Map<String, Long> {
- val lastAccessTimeMs = mutableMapOf<String, Long>()
- // Add keys for all permissions groups covered by the provided ops, regardless of
- // whether they have been observed recently.
- for (permissionGroup in
- opNames.mapNotNull { getPlatformPermissionGroupForOp(it) }.toSet()) {
- lastAccessTimeMs[permissionGroup] = -1
- }
-
- for (opEntry in packageOps.ops) {
- val permissionGroupOfOp = getPlatformPermissionGroupForOp(opEntry.opStr) ?: continue
- lastAccessTimeMs[permissionGroupOfOp] =
- maxOf(
- lastAccessTimeMs[permissionGroupOfOp] ?: -1,
- opEntry.getLastAccessTime(OPS_LAST_ACCESS_FLAGS)
- )
- }
-
- return lastAccessTimeMs
- }
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/BackupHelper.java b/PermissionController/src/com/android/permissioncontroller/permission/service/BackupHelper.java
index 24aab174c..2fa809c6d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/BackupHelper.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/BackupHelper.java
@@ -96,6 +96,7 @@ public class BackupHelper {
private static final String ATTR_USER_SET = "set";
private static final String ATTR_USER_FIXED = "fixed";
private static final String ATTR_WAS_REVIEWED = "was-reviewed";
+ private static final String ATTR_ONE_TIME = "one-time";
/** Flags of permissions to <u>not</u> back up */
private static final int SYSTEM_RUNTIME_GRANT_MASK = FLAG_PERMISSION_POLICY_FIXED
@@ -452,19 +453,21 @@ public class BackupHelper {
private final boolean mIsUserSet;
private final boolean mIsUserFixed;
private final boolean mWasReviewed;
+ private final boolean mIsOneTime;
// Not persisted, used during parsing so explicitly defined state takes precedence
private final boolean mIsAddedFromSplit;
private BackupPermissionState(@NonNull String permissionName, boolean isGranted,
boolean isUserSet, boolean isUserFixed, boolean wasReviewed,
- boolean isAddedFromSplit) {
+ boolean isOneTime, boolean isAddedFromSplit) {
mPermissionName = permissionName;
mIsGranted = isGranted;
mIsUserSet = isUserSet;
mIsUserFixed = isUserFixed;
mWasReviewed = wasReviewed;
mIsAddedFromSplit = isAddedFromSplit;
+ mIsOneTime = isOneTime;
}
/**
@@ -512,6 +515,7 @@ public class BackupHelper {
"true".equals(parser.getAttributeValue(null, ATTR_USER_SET)),
"true".equals(parser.getAttributeValue(null, ATTR_USER_FIXED)),
"true".equals(parser.getAttributeValue(null, ATTR_WAS_REVIEWED)),
+ "true".equals(parser.getAttributeValue(null, ATTR_ONE_TIME)),
/* isAddedFromSplit */ i > 0));
}
@@ -519,7 +523,8 @@ public class BackupHelper {
}
/**
- * Is the permission granted, also considering the app-op.
+ * Is the permission granted, also considering the app-op. Don't consider one time grant
+ * as a permission grant for backup/restore.
*
* <p>This does not consider the review-required state of the permission.
*
@@ -528,7 +533,8 @@ public class BackupHelper {
* @return {@code true} iff the permission and app-op is granted
*/
private static boolean isPermGrantedIncludingAppOp(@NonNull Permission perm) {
- return perm.isGranted() && (!perm.affectsAppOp() || perm.isAppOpAllowed());
+ return perm.isGranted() && (!perm.affectsAppOp() || perm.isAppOpAllowed())
+ && !perm.isOneTime();
}
/**
@@ -549,7 +555,7 @@ public class BackupHelper {
return null;
}
- if (!perm.isUserSet() && perm.isGrantedByDefault()) {
+ if (!perm.isUserSet() && !perm.isOneTime() && perm.isGrantedByDefault()) {
return null;
}
@@ -564,10 +570,10 @@ public class BackupHelper {
}
if (isNotInDefaultGrantState || perm.isUserSet() || perm.isUserFixed()
- || permissionWasReviewed) {
+ || perm.isOneTime() || permissionWasReviewed) {
return new BackupPermissionState(perm.getName(), isPermGrantedIncludingAppOp(perm),
perm.isUserSet(), perm.isUserFixed(), permissionWasReviewed,
- /* isAddedFromSplit */ false);
+ perm.isOneTime(), /* isAddedFromSplit */ false);
} else {
return null;
}
@@ -628,6 +634,10 @@ public class BackupHelper {
serializer.attribute(null, ATTR_WAS_REVIEWED, "true");
}
+ if (mIsOneTime) {
+ serializer.attribute(null, ATTR_ONE_TIME, "true");
+ }
+
serializer.endTag(null, TAG_PERMISSION);
}
@@ -671,6 +681,10 @@ public class BackupHelper {
perm.setUserSet(mIsUserSet);
}
+
+ if (!perm.isOneTime()) {
+ perm.setOneTime(mIsOneTime);
+ }
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt
index 2734116dd..184c37921 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/RuntimePermissionsUpgradeController.kt
@@ -503,7 +503,8 @@ object RuntimePermissionsUpgradeController {
bgApp.permGroupInfo,
allPermissionsWithxemption,
bgApp.hasInstallToRuntimeSplit,
- bgApp.specialLocationGrant
+ bgApp.specialLocationGrant,
+ bgApp.specialFixedStorageGrant,
)
}
@@ -566,10 +567,11 @@ object RuntimePermissionsUpgradeController {
appPermGroup.permissions[permission.ACCESS_MEDIA_LOCATION] ?: continue
if (
+ !perm.isGranted &&
!perm.isUserSet &&
- !perm.isSystemFixed &&
- !perm.isPolicyFixed &&
- !perm.isGranted
+ !perm.isOneTime &&
+ !perm.isSystemFixed &&
+ !perm.isPolicyFixed
) {
grants.add(
Grant(false, appPermGroup, listOf(permission.ACCESS_MEDIA_LOCATION))
@@ -610,20 +612,21 @@ object RuntimePermissionsUpgradeController {
// Upon upgrading to platform 33, for all targetSdk>=33 apps, do the following:
// If STORAGE is granted, and the user has not set READ_MEDIA_AURAL or
// READ_MEDIA_VISUAL, grant READ_MEDIA_AURAL and READ_MEDIA_VISUAL
- val storageAppPermGroups =
+ val grantedStorageAppPermGroups =
storageAndMediaAppPermGroups.filter {
it.packageInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU &&
it.permGroupInfo.name == permission_group.STORAGE &&
it.isGranted &&
it.isUserSet
}
- for (storageAppPermGroup in storageAppPermGroups) {
+ for (storageAppPermGroup in grantedStorageAppPermGroups) {
val pkgName = storageAppPermGroup.packageInfo.packageName
val auralAppPermGroup =
storageAndMediaAppPermGroups.firstOrNull {
it.packageInfo.packageName == pkgName &&
it.permGroupInfo.name == permission_group.READ_MEDIA_AURAL &&
!it.isUserSet &&
+ !it.isOneTime &&
!it.isUserFixed
}
val visualAppPermGroup =
@@ -632,7 +635,8 @@ object RuntimePermissionsUpgradeController {
it.permGroupInfo.name == permission_group.READ_MEDIA_VISUAL &&
!it.permissions
.filter { it.key != permission.ACCESS_MEDIA_LOCATION }
- .any { it.value.isUserSet || it.value.isUserFixed }
+ .any { it.value.isUserSet || it.value.isOneTime ||
+ it.value.isUserFixed }
}
if (auralAppPermGroup != null) {
@@ -680,7 +684,8 @@ object RuntimePermissionsUpgradeController {
bgSensorsGroup.permGroupInfo,
allPermissionsWithExemption,
bgSensorsGroup.hasInstallToRuntimeSplit,
- bgSensorsGroup.specialLocationGrant
+ bgSensorsGroup.specialLocationGrant,
+ bgSensorsGroup.specialFixedStorageGrant,
)
// Grant the background permission only if foreground permission is granted.
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
index a4f629d80..a7114f30b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
@@ -46,7 +46,6 @@ import android.app.KeyguardManager;
import android.app.ecm.EnhancedConfirmationManager;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
@@ -195,7 +194,7 @@ public class GrantPermissionsActivity extends SettingsActivity
/** A list of permissions requested on an app's behalf by the system. Usually Implicitly
* requested, although this isn't necessarily always the case.
*/
- private List<String> mSystemRequestedPermissions = new ArrayList<>();
+ private final List<String> mSystemRequestedPermissions = new ArrayList<>();
/** A copy of the list of permissions originally requested in the intent to this activity */
private String[] mOriginalRequestedPermissions = new String[0];
@@ -209,7 +208,7 @@ public class GrantPermissionsActivity extends SettingsActivity
* A list of other GrantPermissionActivities for the same package which passed their list of
* permissions to this one. They need to be informed when this activity finishes.
*/
- private List<GrantPermissionsActivity> mFollowerActivities = new ArrayList<>();
+ private final List<GrantPermissionsActivity> mFollowerActivities = new ArrayList<>();
/** Whether this activity has asked another GrantPermissionsActivity to show on its behalf */
private boolean mDelegated;
@@ -235,7 +234,7 @@ public class GrantPermissionsActivity extends SettingsActivity
private PackageManager mPackageManager;
- private ActivityResultLauncher<Intent> mShowWarningDialog =
+ private final ActivityResultLauncher<Intent> mShowWarningDialog =
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
@@ -263,6 +262,9 @@ public class GrantPermissionsActivity extends SettingsActivity
if (DeviceUtils.isWear(this)) {
// Do not grab input focus and hide keyboard.
getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, 0, 0);
+ }
}
if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(getIntent().getAction())) {
@@ -284,7 +286,7 @@ public class GrantPermissionsActivity extends SettingsActivity
return;
}
try {
- PackageInfo packageInfo = mPackageManager.getPackageInfo(mTargetPackage, 0);
+ mPackageManager.getPackageInfo(mTargetPackage, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Unable to get package info for the calling package.", e);
finishAfterTransition();
@@ -314,20 +316,23 @@ public class GrantPermissionsActivity extends SettingsActivity
.getPackageManager();
}
- // When the dialog is streamed to a remote device, verify requested permissions are all
- // device aware and target device is the same as the remote device. Otherwise show a
- // warning dialog.
+ // When the permission grant dialog is streamed to a virtual device, and when requested
+ // permissions include both device-aware permissions and non-device aware permissions,
+ // device-aware permissions will use virtual device id and non-device aware permissions
+ // will use default device id for granting. If flag is not enabled, we would show a
+ // warning dialog for this use case.
if (getDeviceId() != ContextCompat.DEVICE_ID_DEFAULT) {
boolean showWarningDialog = mTargetDeviceId != getDeviceId();
for (String permission : mRequestedPermissions) {
- if (!MultiDeviceUtils.isPermissionDeviceAware(
- getApplicationContext(), mTargetDeviceId, permission)) {
+ if (!MultiDeviceUtils.isPermissionDeviceAware(getApplicationContext(),
+ mTargetDeviceId, permission)) {
showWarningDialog = true;
+ break;
}
}
- if (showWarningDialog) {
+ if (showWarningDialog && !Flags.allowHostPermissionDialogsOnVirtualDevices()) {
mShowWarningDialog.launch(
new Intent(this, PermissionDialogStreamingBlockedActivity.class));
return;
@@ -1115,9 +1120,17 @@ public class GrantPermissionsActivity extends SettingsActivity
if ((mDelegated || (mViewModel != null && mViewModel.shouldReturnPermissionState()))
&& mTargetPackage != null) {
+ PackageManager defaultDevicePackageManager = SdkLevel.isAtLeastV()
+ && mTargetDeviceId != ContextCompat.DEVICE_ID_DEFAULT
+ ? createDeviceContext(ContextCompat.DEVICE_ID_DEFAULT).getPackageManager()
+ : mPackageManager;
+ PackageManager targetDevicePackageManager = mPackageManager;
for (int i = 0; i < resultPermissions.length; i++) {
- grantResults[i] =
- mPackageManager.checkPermission(resultPermissions[i], mTargetPackage);
+ String permission = resultPermissions[i];
+ PackageManager pm = MultiDeviceUtils.isPermissionDeviceAware(
+ getApplicationContext(), mTargetDeviceId, permission)
+ ? targetDevicePackageManager : defaultDevicePackageManager;
+ grantResults[i] = pm.checkPermission(resultPermissions[i], mTargetPackage);
}
} else {
grantResults = new int[0];
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
index 36917fbf1..0af7cf2ec 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
@@ -42,6 +42,7 @@ import android.os.Bundle;
import android.os.Process;
import android.os.UserHandle;
import android.permission.PermissionManager;
+import android.permission.flags.Flags;
import android.provider.Settings;
import android.safetycenter.SafetyCenterManager;
import android.safetycenter.SafetyEvent;
@@ -450,7 +451,15 @@ public final class ManagePermissionsActivity extends SettingsActivity {
if (Utils.isHealthPermissionUiEnabled() && permissionGroupName
.equals(HEALTH_PERMISSION_GROUP)) {
- Utils.navigateToHealthConnectSettings(this);
+ // On Handheld, PrivacyDashboard and PermissionManager have the same UI.
+ // On Wear, PrivacyDashboard and PermissionManager have different UI,
+ // PermissionController needs to add an extra in the intent to instruct
+ // HealthConnect how to differentiate.
+ if (DeviceUtils.isWear(this) && Flags.replaceBodySensorPermissionEnabled()) {
+ Utils.navigateToWearHealthConnectSettingsPrivacyDashboard(this);
+ } else {
+ Utils.navigateToHealthConnectSettings(this);
+ }
finishAfterTransition();
return;
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt
index 2d14260a2..36597a3a3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt
@@ -23,30 +23,30 @@ import androidx.annotation.RequiresApi
import androidx.preference.Preference.OnPreferenceClickListener
import com.android.car.ui.preference.CarUiPreference
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelLegacy
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.AppPermissionAccessUiInfo
/** Preference that displays a permission usage for an app. */
@RequiresApi(Build.VERSION_CODES.S)
class AutoPermissionHistoryPreference(
context: Context,
- historyPreferenceData: PermissionUsageDetailsViewModelLegacy.HistoryPreferenceData
+ historyPreferenceData: AppPermissionAccessUiInfo,
) : CarUiPreference(context) {
init {
- title = historyPreferenceData.preferenceTitle
+ title = historyPreferenceData.packageLabel
summary =
if (historyPreferenceData.summaryText != null) {
context.getString(
R.string.auto_permission_usage_timeline_summary,
DateFormat.getTimeFormat(context).format(historyPreferenceData.accessEndTime),
- historyPreferenceData.summaryText
+ historyPreferenceData.summaryText,
)
} else {
DateFormat.getTimeFormat(context).format(historyPreferenceData.accessEndTime)
}
- if (historyPreferenceData.appIcon != null) {
- icon = historyPreferenceData.appIcon
+ if (historyPreferenceData.badgedPackageIcon != null) {
+ icon = historyPreferenceData.badgedPackageIcon
}
onPreferenceClickListener = OnPreferenceClickListener {
@@ -56,12 +56,12 @@ class AutoPermissionHistoryPreference(
PermissionUsageDetailsViewModel.createHistoryPreferenceClickIntent(
context = context,
userHandle = historyPreferenceData.userHandle,
- packageName = historyPreferenceData.pkgName,
+ packageName = historyPreferenceData.packageName,
permissionGroup = historyPreferenceData.permissionGroup,
accessEndTime = historyPreferenceData.accessEndTime,
accessStartTime = historyPreferenceData.accessStartTime,
showingAttribution = historyPreferenceData.showingAttribution,
- attributionTags = historyPreferenceData.attributionTags.toSet()
+ attributionTags = historyPreferenceData.attributionTags.toSet(),
)
)
true
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt
index 481543eb6..2ecf40ce2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt
@@ -13,11 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:Suppress("DEPRECATION")
-
package com.android.permissioncontroller.permission.ui.auto.dashboard
-import android.app.role.RoleManager
import android.content.Intent
import android.os.Build
import android.os.Bundle
@@ -36,16 +33,11 @@ import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED
import com.android.permissioncontroller.R
import com.android.permissioncontroller.auto.AutoSettingsFrameFragment
-import com.android.permissioncontroller.permission.model.legacy.PermissionApps.AppDataLoader
-import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
-import com.android.permissioncontroller.permission.model.v31.PermissionUsages
-import com.android.permissioncontroller.permission.model.v31.PermissionUsages.PermissionsUsagesChangeCallback
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
import com.android.permissioncontroller.permission.ui.auto.AutoDividerPreference
-import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelFactoryLegacy
-import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelLegacy
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.AppPermissionAccessUiInfo
import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
-import com.android.permissioncontroller.permission.utils.Utils
import java.time.Clock
import java.time.Instant
import java.time.ZoneId
@@ -54,9 +46,7 @@ import java.time.temporal.ChronoUnit
import java.util.concurrent.atomic.AtomicReference
@RequiresApi(Build.VERSION_CODES.S)
-class AutoPermissionUsageDetailsFragment :
- AutoSettingsFrameFragment(), PermissionsUsagesChangeCallback {
-
+class AutoPermissionUsageDetailsFragment : AutoSettingsFrameFragment() {
companion object {
private const val LOG_TAG = "AutoPermissionUsageDetailsFragment"
private const val KEY_SESSION_ID = "_session_id"
@@ -70,14 +60,11 @@ class AutoPermissionUsageDetailsFragment :
.truncatedTo(ChronoUnit.DAYS)
.toEpochSecond() * 1000L
- // Only show the last 24 hours on Auto right now
- private const val SHOW_7_DAYS = false
-
/** Creates a new instance of [AutoPermissionUsageDetailsFragment]. */
fun newInstance(
groupName: String?,
showSystem: Boolean,
- sessionId: Long
+ sessionId: Long,
): AutoPermissionUsageDetailsFragment {
return AutoPermissionUsageDetailsFragment().apply {
arguments =
@@ -92,14 +79,10 @@ class AutoPermissionUsageDetailsFragment :
private val SESSION_ID_KEY = (AutoPermissionUsageFragment::class.java.name + KEY_SESSION_ID)
- private lateinit var permissionUsages: PermissionUsages
- private lateinit var usageViewModel: PermissionUsageDetailsViewModelLegacy
+ private lateinit var usageViewModel: PermissionUsageDetailsViewModel
private lateinit var filterGroup: String
- private lateinit var roleManager: RoleManager
- private var appPermissionUsages: List<AppPermissionUsage> = listOf()
private var showSystem = false
- private var finishedInitialLoad = false
private var hasSystemApps = false
/** Unique Id of a request */
@@ -116,7 +99,7 @@ class AutoPermissionUsageDetailsFragment :
!requireArguments().containsKey(Intent.EXTRA_PERMISSION_GROUP_NAME) or
(requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME) == null)
) {
- DumpableLog.e(LOG_TAG, "Missing argument ${Intent.EXTRA_USER}")
+ DumpableLog.e(LOG_TAG, "Missing argument ${Intent.EXTRA_PERMISSION_GROUP_NAME}")
activity?.finish()
return
}
@@ -130,28 +113,20 @@ class AutoPermissionUsageDetailsFragment :
headerLabel =
resources.getString(
R.string.permission_group_usage_title,
- getPermGroupLabel(requireContext(), filterGroup)
+ getPermGroupLabel(requireContext(), filterGroup),
)
-
- val context = preferenceManager.getContext()
- permissionUsages = PermissionUsages(context)
- roleManager = Utils.getSystemServiceSafe(context, RoleManager::class.java)
- val usageViewModelFactory =
- PermissionUsageDetailsViewModelFactoryLegacy(
+ val factory =
+ PermissionUsageDetailsViewModel.PermissionUsageDetailsViewModelFactory(
PermissionControllerApplication.get(),
- roleManager,
filterGroup,
- sessionId
)
usageViewModel =
- ViewModelProvider(this, usageViewModelFactory)[
- PermissionUsageDetailsViewModelLegacy::class.java]
-
- reloadData()
+ ViewModelProvider(this, factory)[PermissionUsageDetailsViewModel::class.java]
+ usageViewModel.getPermissionUsagesDetailsInfoUiLiveData().observe(this, this::updateUI)
}
override fun onCreatePreferences(bundlle: Bundle?, s: String?) {
- preferenceScreen = preferenceManager.createPreferenceScreen(context!!)
+ preferenceScreen = preferenceManager.createPreferenceScreen(requireContext())
}
private fun setupHeaderPreferences() {
@@ -161,38 +136,16 @@ class AutoPermissionUsageDetailsFragment :
preferenceScreen.addPreference(AutoDividerPreference(context))
}
- /** Reloads the data to show. */
- private fun reloadData() {
- usageViewModel.loadPermissionUsages(
- requireActivity().getLoaderManager(),
- permissionUsages,
- this,
- FILTER_24_HOURS
- )
- if (finishedInitialLoad) {
- setLoading(true)
- }
- }
-
- override fun onPermissionUsagesChanged() {
- if (permissionUsages.usages.isEmpty()) {
- return
- }
- appPermissionUsages = ArrayList(permissionUsages.usages)
- updateUI()
- }
-
private fun updateSystemToggle() {
if (!showSystem) {
PermissionControllerStatsLog.write(
PERMISSION_USAGE_FRAGMENT_INTERACTION,
sessionId,
- PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED
+ PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED,
)
}
showSystem = !showSystem
updateAction()
- updateUI()
}
private fun updateAction() {
@@ -206,47 +159,36 @@ class AutoPermissionUsageDetailsFragment :
} else {
getString(R.string.menu_show_system)
}
- setAction(label) { updateSystemToggle() }
+ setAction(label) {
+ usageViewModel.updateShowSystemAppsToggle(!showSystem)
+ updateSystemToggle()
+ }
}
- private fun updateUI() {
- if (appPermissionUsages.isEmpty()) {
+ private fun updateUI(uiInfo: PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState) {
+ if (
+ activity == null ||
+ uiInfo is PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState.Loading
+ ) {
return
}
preferenceScreen.removeAll()
setupHeaderPreferences()
-
- val uiData =
- usageViewModel.buildPermissionUsageDetailsUiData(
- appPermissionUsages,
- showSystem,
- SHOW_7_DAYS
- )
-
- if (hasSystemApps != uiData.shouldDisplayShowSystemToggle) {
- hasSystemApps = uiData.shouldDisplayShowSystemToggle
+ val uiData = uiInfo as PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState.Success
+ if (hasSystemApps != uiData.containsSystemAppUsage) {
+ hasSystemApps = uiData.containsSystemAppUsage
updateAction()
}
-
val category = AtomicReference(PreferenceCategory(requireContext()))
preferenceScreen.addPreference(category.get())
- AppDataLoader(context) {
- renderHistoryPreferences(
- uiData.getHistoryPreferenceDataList(),
- category,
- preferenceScreen
- )
+ renderHistoryPreferences(uiData.appPermissionAccessUiInfoList, category, preferenceScreen)
- setLoading(false)
- finishedInitialLoad = true
- permissionUsages.stopLoader(requireActivity().getLoaderManager())
- }
- .execute(*uiData.permissionApps.toTypedArray())
+ setLoading(false)
}
fun createPermissionHistoryPreference(
- historyPreferenceData: PermissionUsageDetailsViewModelLegacy.HistoryPreferenceData
+ historyPreferenceData: AppPermissionAccessUiInfo
): Preference {
return AutoPermissionHistoryPreference(requireContext(), historyPreferenceData)
}
@@ -257,7 +199,7 @@ class AutoPermissionUsageDetailsFragment :
summary =
getString(
R.string.permission_group_usage_subtitle_24h,
- getPermGroupLabel(requireContext(), filterGroup)
+ getPermGroupLabel(requireContext(), filterGroup),
)
isSelectable = false
}
@@ -271,7 +213,7 @@ class AutoPermissionUsageDetailsFragment :
summary =
getString(
R.string.manage_permission_summary,
- getPermGroupLabel(requireContext(), filterGroup)
+ getPermGroupLabel(requireContext(), filterGroup),
)
onPreferenceClickListener =
Preference.OnPreferenceClickListener {
@@ -287,9 +229,8 @@ class AutoPermissionUsageDetailsFragment :
}
/** Render the provided [historyPreferenceDataList] into the [preferenceScreen] UI. */
- fun renderHistoryPreferences(
- historyPreferenceDataList:
- List<PermissionUsageDetailsViewModelLegacy.HistoryPreferenceData>,
+ private fun renderHistoryPreferences(
+ historyPreferenceDataList: List<AppPermissionAccessUiInfo>,
category: AtomicReference<PreferenceCategory>,
preferenceScreen: PreferenceScreen,
) {
@@ -299,7 +240,7 @@ class AutoPermissionUsageDetailsFragment :
val currentDateMs =
ZonedDateTime.ofInstant(
Instant.ofEpochMilli(usageTimestamp),
- Clock.system(ZoneId.systemDefault()).zone
+ Clock.system(ZoneId.systemDefault()).zone,
)
.truncatedTo(ChronoUnit.DAYS)
.toEpochSecond() * 1000L
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt
index f2e453447..f52eaadcd 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt
@@ -46,7 +46,7 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment() {
Manifest.permission_group.CAMERA,
1,
Manifest.permission_group.MICROPHONE,
- 2
+ 2,
)
private const val DEFAULT_ORDER: Int = 3
}
@@ -54,7 +54,6 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment() {
private val SESSION_ID_KEY = (AutoPermissionUsageFragment::class.java.name + KEY_SESSION_ID)
private var showSystem = false
- private var finishedInitialLoad = false
private var hasSystemApps = false
/** Unique Id of a request */
@@ -89,7 +88,7 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment() {
PermissionControllerStatsLog.write(
PERMISSION_USAGE_FRAGMENT_INTERACTION,
sessionId,
- PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED
+ PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED,
)
}
showSystem = !showSystem
@@ -133,13 +132,13 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment() {
Comparator.comparing { permissionGroupWithUsageCount: Map.Entry<String, Int> ->
PERMISSION_GROUP_ORDER.getOrDefault(
permissionGroupWithUsageCount.key,
- DEFAULT_ORDER
+ DEFAULT_ORDER,
)
}
.thenComparing { permissionGroupWithUsageCount: Map.Entry<String, Int> ->
mViewModel.getPermissionGroupLabel(
requireContext(),
- permissionGroupWithUsageCount.key
+ permissionGroupWithUsageCount.key,
)
}
)
@@ -153,11 +152,10 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment() {
permissionGroupWithUsageCountsEntries[i].value,
showSystem,
sessionId,
- false
+ false,
)
getPreferenceScreen().addPreference(permissionUsagePreference)
}
- finishedInitialLoad = true
setLoading(false)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionWrapperFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionWrapperFragment.java
index 8650d99fc..080c7cfdc 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionWrapperFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionWrapperFragment.java
@@ -29,7 +29,7 @@ public class AppPermissionWrapperFragment extends PermissionsCollapsingToolbarBa
@NonNull
@Override
public PreferenceFragmentCompat createPreferenceFragment() {
- return AppPermissionFragmentCompat.createFragment();
+ return AppPermissionFragmentCompat.createFragment(getContext());
}
@Override
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageCustomPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageCustomPermissionsFragment.java
index 35236b8de..dd460aa2f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageCustomPermissionsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageCustomPermissionsFragment.java
@@ -24,6 +24,8 @@ import android.view.MenuItem;
import androidx.lifecycle.ViewModelProvider;
+import com.android.permission.flags.Flags;
+import com.android.permissioncontroller.permission.data.PermGroupsPackagesUiInfoLiveData;
import com.android.permissioncontroller.permission.ui.model.ManageCustomPermissionsViewModel;
import com.android.permissioncontroller.permission.ui.model.ManageCustomPermissionsViewModelFactory;
@@ -48,6 +50,14 @@ public class ManageCustomPermissionsFragment extends ManagePermissionsFragment {
return arguments;
}
+ private PermGroupsPackagesUiInfoLiveData getPermGroupsLiveData() {
+ if (Flags.declutteredPermissionManagerEnabled()) {
+ return mViewModel.getAdditionaPermGroupsUiInfo();
+ } else {
+ return mViewModel.getUiDataLiveData();
+ }
+ }
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -56,9 +66,9 @@ public class ManageCustomPermissionsFragment extends ManagePermissionsFragment {
new ManageCustomPermissionsViewModelFactory(getActivity().getApplication());
mViewModel = new ViewModelProvider(this, factory)
.get(ManageCustomPermissionsViewModel.class);
- mPermissionGroups = mViewModel.getUiDataLiveData().getValue();
+ mPermissionGroups = getPermGroupsLiveData().getValue();
- mViewModel.getUiDataLiveData().observe(this, permissionGroups -> {
+ getPermGroupsLiveData().observe(this, permissionGroups -> {
if (permissionGroups == null) {
mPermissionGroups = new HashMap<>();
} else {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java
index bf99b7134..51c0906a2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java
@@ -31,7 +31,9 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.modules.utils.build.SdkLevel;
+import com.android.permission.flags.Flags;
import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.data.PermGroupsPackagesUiInfoLiveData;
import com.android.permissioncontroller.permission.ui.UnusedAppsFragment;
import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModel;
import com.android.permissioncontroller.permission.utils.StringUtils;
@@ -58,6 +60,14 @@ public final class ManageStandardPermissionsFragment extends ManagePermissionsFr
return arguments;
}
+ private PermGroupsPackagesUiInfoLiveData getPermGroupsLiveData() {
+ if (Flags.declutteredPermissionManagerEnabled()) {
+ return mViewModel.getUsedStandardPermGroupsUiInfo();
+ } else {
+ return mViewModel.getUiDataLiveData();
+ }
+ }
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -65,12 +75,12 @@ public final class ManageStandardPermissionsFragment extends ManagePermissionsFr
final Application application = getActivity().getApplication();
mViewModel = new ViewModelProvider(this, AndroidViewModelFactory.getInstance(application))
.get(ManageStandardPermissionsViewModel.class);
- mPermissionGroups = mViewModel.getUiDataLiveData().getValue();
+ mPermissionGroups = getPermGroupsLiveData().getValue();
- mViewModel.getUiDataLiveData().observe(this, permissionGroups -> {
+ getPermGroupsLiveData().observe(this, permissionGroups -> {
// Once we have loaded data for the first time, further loads should be staggered,
// for performance reasons.
- mViewModel.getUiDataLiveData().setLoadStaggered(true);
+ getPermGroupsLiveData().setLoadStaggered(true);
if (permissionGroups != null) {
mPermissionGroups = permissionGroups;
updatePermissionsUi();
@@ -80,13 +90,18 @@ public final class ManageStandardPermissionsFragment extends ManagePermissionsFr
}
// If we've loaded all LiveDatas, no need to prioritize loading any particular one
- if (!mViewModel.getUiDataLiveData().isStale()) {
- mViewModel.getUiDataLiveData().setFirstLoadGroup(null);
+ if (!getPermGroupsLiveData().isStale()) {
+ getPermGroupsLiveData().setFirstLoadGroup(null);
}
});
mViewModel.getNumCustomPermGroups().observe(this, permNames -> updatePermissionsUi());
mViewModel.getNumAutoRevoked().observe(this, show -> updatePermissionsUi());
+ if (Flags.declutteredPermissionManagerEnabled()) {
+ mViewModel.getNumUnusedStandardPermGroups().observe(
+ this, show -> updatePermissionsUi()
+ );
+ }
}
@Override
@@ -118,6 +133,14 @@ public final class ManageStandardPermissionsFragment extends ManagePermissionsFr
if (mViewModel.getNumCustomPermGroups().getValue() != null) {
numExtraPermissions = mViewModel.getNumCustomPermGroups().getValue();
}
+ if (Flags.declutteredPermissionManagerEnabled()) {
+ if (mViewModel.getNumUnusedStandardPermGroups().getValue() != null) {
+ // When decluttered permission manager is enabled, unused
+ // permission groups will also be displayed in the additional
+ // permissions screen.
+ numExtraPermissions += mViewModel.getNumUnusedStandardPermGroups().getValue();
+ }
+ }
Preference additionalPermissionsPreference = screen.findPreference(EXTRA_PREFS_KEY);
if (numExtraPermissions == 0) {
@@ -198,7 +221,7 @@ public final class ManageStandardPermissionsFragment extends ManagePermissionsFr
public void showPermissionApps(String permissionGroupName) {
// If we return to this page within a reasonable time, prioritize loading data from the
// permission group whose page we are going to, as that is group most likely to have changed
- mViewModel.getUiDataLiveData().setFirstLoadGroup(permissionGroupName);
+ getPermGroupsLiveData().setFirstLoadGroup(permissionGroupName);
mViewModel.showPermissionApps(this, PermissionAppsFragment.createArgs(
permissionGroupName, getArguments().getLong(EXTRA_SESSION_ID)));
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionFooterPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionFooterPreference.kt
index 1cd4ed23a..e7749d827 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionFooterPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionFooterPreference.kt
@@ -17,16 +17,21 @@
package com.android.permissioncontroller.permission.ui.handheld
import android.content.Context
+import android.util.AttributeSet
import android.view.View
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.R
import com.android.settingslib.widget.FooterPreference
-class PermissionFooterPreference(c: Context) : FooterPreference(c) {
+class PermissionFooterPreference : FooterPreference {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
init {
if (SdkLevel.isAtLeastV()) {
layoutResource = R.layout.permission_footer_preference
- if (c.resources.getBoolean(R.bool.config_permissionFooterPreferenceIconVisible)) {
+ if (context.resources.getBoolean(R.bool.config_permissionFooterPreferenceIconVisible)) {
setIconVisibility(View.VISIBLE)
} else {
setIconVisibility(View.GONE)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.kt
index 5e30183ec..010ca28a7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.kt
@@ -18,15 +18,30 @@ package com.android.permissioncontroller.permission.ui.handheld
import android.content.Context
import android.util.AttributeSet
+import androidx.annotation.AttrRes
+import androidx.annotation.StyleRes
import androidx.preference.Preference
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.DeviceUtils
import com.android.permissioncontroller.R
open class PermissionPreference : Preference {
- constructor(c: Context) : super(c)
+ constructor(context: Context) : super(context)
- constructor(c: Context, a: AttributeSet) : super(c, a)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ @AttrRes defStyleAttr: Int,
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ @AttrRes defStyleAttr: Int,
+ @StyleRes defStyleRes: Int,
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
init {
if (SdkLevel.isAtLeastV() && DeviceUtils.isHandheld(context)) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreferenceCategory.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreferenceCategory.kt
index ef1752530..ef95c6c5c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreferenceCategory.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreferenceCategory.kt
@@ -18,15 +18,30 @@ package com.android.permissioncontroller.permission.ui.handheld
import android.content.Context
import android.util.AttributeSet
+import androidx.annotation.AttrRes
+import androidx.annotation.StyleRes
import androidx.preference.PreferenceCategory
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.DeviceUtils
import com.android.permissioncontroller.R
open class PermissionPreferenceCategory : PreferenceCategory {
- constructor(c: Context) : super(c)
+ constructor(context: Context) : super(context)
- constructor(c: Context, a: AttributeSet) : super(c, a)
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ @AttrRes defStyleAttr: Int,
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ @AttrRes defStyleAttr: Int,
+ @StyleRes defStyleRes: Int,
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
init {
if (SdkLevel.isAtLeastV() && DeviceUtils.isHandheld(context)) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/max35/LegacyAppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/max35/LegacyAppPermissionFragment.java
index 35650defb..f852baa13 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/max35/LegacyAppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/max35/LegacyAppPermissionFragment.java
@@ -584,7 +584,8 @@ public class LegacyAppPermissionFragment extends SettingsWithLargeHeader
// If the permissions are individually controlled, also show a link to the page that
// lets you control them.
mDivider.setVisibility(View.VISIBLE);
- showRightIcon(R.drawable.ic_settings);
+ showRightIcon(R.drawable.ic_settings, getContext().getString(
+ R.string.app_permission_settings_button));
Bundle args = AllAppPermissionsFragment.createArgs(mPackageName, mPermGroupName, mUser);
mWidgetFrame.setOnClickListener(v -> mViewModel.showAllPermissions(this, args));
mPermissionDetails.setText(getPreferenceManager().getContext().getString(
@@ -599,7 +600,8 @@ public class LegacyAppPermissionFragment extends SettingsWithLargeHeader
private void setAdminSupportDetail(EnforcedAdmin admin) {
if (admin != null) {
- showRightIcon(R.drawable.ic_info);
+ showRightIcon(R.drawable.ic_info, getContext().getString(
+ R.string.app_permission_info_button));
mWidgetFrame.setOnClickListener(v ->
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin)
);
@@ -613,10 +615,11 @@ public class LegacyAppPermissionFragment extends SettingsWithLargeHeader
*
* @param iconId the resourceId of the drawable to use.
*/
- private void showRightIcon(int iconId) {
+ private void showRightIcon(int iconId, @NonNull String contentDescription) {
mWidgetFrame.removeAllViews();
ImageView imageView = new ImageView(getPreferenceManager().getContext());
imageView.setImageResource(iconId);
+ imageView.setContentDescription(contentDescription);
mWidgetFrame.addView(imageView);
mWidgetFrame.setVisibility(View.VISIBLE);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionHistoryPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionHistoryPreference.java
index 933911bff..3be9434b2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionHistoryPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionHistoryPreference.java
@@ -195,6 +195,7 @@ public class PermissionHistoryPreference extends Preference {
Intent intent = getViewPermissionUsageForPeriodIntent();
if (intent != null) {
dividerVerticalBar.setVisibility(View.VISIBLE);
+ widgetView.setVisibility(View.VISIBLE);
widgetView.setImageDrawable(mContext.getDrawable(R.drawable.ic_info_outline));
widgetView.setOnClickListener(v -> {
write(PERMISSION_DETAILS_INTERACTION,
@@ -214,6 +215,7 @@ public class PermissionHistoryPreference extends Preference {
} else {
dividerVerticalBar.setVisibility(View.GONE);
widgetView.setImageDrawable(null);
+ widgetView.setVisibility(View.GONE);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionUsageDetailsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionUsageDetailsFragment.java
index 3a904c466..9078f1a0a 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionUsageDetailsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionUsageDetailsFragment.java
@@ -54,7 +54,7 @@ import com.android.permissioncontroller.PermissionControllerApplication;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity;
import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader;
-import com.android.permissioncontroller.permission.ui.model.v31.BasePermissionUsageDetailsViewModel;
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel;
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.AppPermissionAccessUiInfo;
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState;
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.PermissionUsageDetailsViewModelFactory;
@@ -89,7 +89,7 @@ public class PermissionUsageDetailsFragment extends SettingsWithLargeHeader {
private MenuItem mShow7DaysDataMenu;
private MenuItem mShow24HoursDataMenu;
- private BasePermissionUsageDetailsViewModel mViewModel;
+ private PermissionUsageDetailsViewModel mViewModel;
private long mSessionId;
@@ -103,9 +103,9 @@ public class PermissionUsageDetailsFragment extends SettingsWithLargeHeader {
}
PermissionUsageDetailsViewModelFactory factory =
new PermissionUsageDetailsViewModelFactory(
- PermissionControllerApplication.get(), this, mPermissionGroup);
+ PermissionControllerApplication.get(), mPermissionGroup);
mViewModel =
- new ViewModelProvider(this, factory).get(BasePermissionUsageDetailsViewModel.class);
+ new ViewModelProvider(this, factory).get(PermissionUsageDetailsViewModel.class);
if (savedInstanceState != null) {
mSessionId = savedInstanceState.getLong(SESSION_ID_KEY);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v35/SectionPreferenceGroupAdapter.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v35/SectionPreferenceGroupAdapter.kt
index 72e066777..e5dce40b0 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v35/SectionPreferenceGroupAdapter.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v35/SectionPreferenceGroupAdapter.kt
@@ -25,6 +25,7 @@ import androidx.preference.PreferenceGroup
import androidx.preference.PreferenceGroupAdapter
import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.handheld.v36.AppPermissionFooterLinkPreference
import com.android.settingslib.widget.FooterPreference
/**
@@ -106,7 +107,10 @@ class SectionPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) :
}
private val Preference.isSectionDivider: Boolean
- get() = this is PreferenceCategory || this is FooterPreference
+ get() =
+ this is PreferenceCategory ||
+ this is FooterPreference ||
+ this is AppPermissionFooterLinkPreference
override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/AppPermissionFooterLinkPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/AppPermissionFooterLinkPreference.kt
new file mode 100644
index 000000000..01554880a
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/AppPermissionFooterLinkPreference.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.handheld.v36
+
+import android.content.Context
+import android.os.Build
+import android.util.AttributeSet
+import android.widget.TextView
+import androidx.annotation.AttrRes
+import androidx.annotation.RequiresApi
+import androidx.annotation.StyleRes
+import androidx.preference.PreferenceViewHolder
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.handheld.PermissionPreference
+
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+class AppPermissionFooterLinkPreference : PermissionPreference {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ @AttrRes defStyleAttr: Int,
+ ) : super(context, attrs, defStyleAttr)
+
+ constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ @AttrRes defStyleAttr: Int,
+ @StyleRes defStyleRes: Int,
+ ) : super(context, attrs, defStyleAttr, defStyleRes)
+
+ init {
+ layoutResource = R.layout.app_permission_footer_link_preference
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ if (
+ context.resources.getBoolean(
+ R.bool.config_appPermissionFooterLinkPreferenceSummaryUnderlined
+ )
+ ) {
+ val summary = holder.findViewById(android.R.id.summary) as TextView
+ summary.paint.isUnderlineText = true
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/AppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/AppPermissionFragment.java
index 481dc0dac..768b00e39 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/AppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/AppPermissionFragment.java
@@ -79,11 +79,10 @@ import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandle
import com.android.permissioncontroller.permission.ui.handheld.AllAppPermissionsFragment;
import com.android.permissioncontroller.permission.ui.handheld.AppPermissionGroupsFragment;
import com.android.permissioncontroller.permission.ui.handheld.PermissionAppsFragment;
+import com.android.permissioncontroller.permission.ui.handheld.PermissionFooterPreference;
import com.android.permissioncontroller.permission.ui.handheld.PermissionPreference;
import com.android.permissioncontroller.permission.ui.handheld.PermissionPreferenceCategory;
-import com.android.permissioncontroller.permission.ui.handheld.PermissionSelectorWithWidgetPreference;
import com.android.permissioncontroller.permission.ui.handheld.PermissionSwitchPreference;
-import com.android.permissioncontroller.permission.ui.handheld.PermissionTwoTargetPreference;
import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader;
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel;
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonState;
@@ -133,10 +132,10 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
private @NonNull SelectorWithWidgetPreference mDenyForegroundButton;
private @NonNull PermissionSwitchPreference mLocationAccuracySwitch;
private @NonNull PermissionTwoTargetPreference mDetails;
- private @NonNull PermissionPreference mFooterLink1;
- private @NonNull PermissionPreference mFooterLink2;
- private @NonNull PermissionPreference mFooterStorageSpecialAppAccess;
- private @NonNull PermissionPreference mAdditionalInfo;
+ private @NonNull AppPermissionFooterLinkPreference mFooterLink1;
+ private @NonNull AppPermissionFooterLinkPreference mFooterLink2;
+ private @NonNull PermissionFooterPreference mFooterStorageSpecialAppAccess;
+ private @NonNull PermissionFooterPreference mAdditionalInfo;
private @NonNull String mPackageName;
private @NonNull String mPermGroupName;
@@ -268,7 +267,7 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
if (exemptedPackages.contains(mPackageName)) {
int additional_info_label = Utils.isStatusBarIndicatorPermission(mPermGroupName)
? R.string.exempt_mic_camera_info_label : R.string.exempt_info_label;
- mAdditionalInfo.setSummary(context.getString(additional_info_label, mPackageLabel));
+ mAdditionalInfo.setTitle(context.getString(additional_info_label, mPackageLabel));
mAdditionalInfo.setVisible(true);
} else {
mAdditionalInfo.setVisible(false);
@@ -554,14 +553,14 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
if (detailResIds.getSecond() != null) {
// If the permissions are individually controlled, also show a link to the page that
// lets you control them.
- mDetails.setExtraWidgetIconRes(R.drawable.ic_settings);
+ mDetails.setExtraWidgetIcon(R.drawable.ic_settings, getContext().getString(
+ R.string.app_permission_settings_button));
Bundle args = AllAppPermissionsFragment.createArgs(mPackageName, mPermGroupName, mUser);
mDetails.setOnSecondTargetClickListener((v) ->
mViewModel.showAllPermissions(this, args));
mDetails.setSummary(getPreferenceManager().getContext().getString(
detailResIds.getFirst(), detailResIds.getSecond()));
} else {
- mDetails.setOnSecondTargetClickListener(null);
mDetails.setSummary(getPreferenceManager().getContext().getString(
detailResIds.getFirst()));
}
@@ -570,7 +569,8 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
private void setAdminSupportDetail(EnforcedAdmin admin) {
if (admin != null) {
- mDetails.setExtraWidgetIconRes(R.drawable.ic_info_outline);
+ mDetails.setExtraWidgetIcon(R.drawable.ic_info_outline, getContext().getString(
+ R.string.app_permission_info_button));
mDetails.setOnSecondTargetClickListener((v) ->
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), admin));
} else {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionSelectorWithWidgetPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/PermissionSelectorWithWidgetPreference.kt
index 9d095c7dc..1574eaba3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionSelectorWithWidgetPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/PermissionSelectorWithWidgetPreference.kt
@@ -14,14 +14,16 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.handheld
+package com.android.permissioncontroller.permission.ui.handheld.v36
import android.content.Context
+import android.os.Build
import android.util.AttributeSet
import android.widget.ImageView
import androidx.annotation.AttrRes
import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
+import androidx.annotation.RequiresApi
import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.utils.ResourceUtils
@@ -34,6 +36,7 @@ import com.android.settingslib.widget.SelectorWithWidgetPreference
* - Propagates the supplied `app:checkboxId` id to the checkbox (or radio button, on the left)
* - Allows defining a "disabled click listener" handler that handles clicks when disabled
*/
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
class PermissionSelectorWithWidgetPreference : SelectorWithWidgetPreference {
constructor(context: Context) : super(context) {
init(context, null)
@@ -46,7 +49,7 @@ class PermissionSelectorWithWidgetPreference : SelectorWithWidgetPreference {
constructor(
context: Context,
attrs: AttributeSet?,
- @AttrRes defStyleAttr: Int
+ @AttrRes defStyleAttr: Int,
) : super(context, attrs, defStyleAttr) {
init(context, attrs)
}
@@ -56,6 +59,8 @@ class PermissionSelectorWithWidgetPreference : SelectorWithWidgetPreference {
}
private fun init(context: Context, attrs: AttributeSet?) {
+ layoutResource = R.layout.permission_preference_selector_with_widget
+ widgetLayoutResource = R.layout.permission_preference_widget_radiobutton
extraWidgetIconRes =
ResourceUtils.getResourceIdByAttr(context, attrs, R.attr.extraWidgetIcon)
extraWidgetIdRes = ResourceUtils.getResourceIdByAttr(context, attrs, R.attr.extraWidgetId)
@@ -69,9 +74,10 @@ class PermissionSelectorWithWidgetPreference : SelectorWithWidgetPreference {
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
- val extraWidget = holder.findViewById(
- com.android.settingslib.widget.preference.selector.R.id.selector_extra_widget
- ) as? ImageView
+ val extraWidget =
+ holder.findViewById(
+ com.android.settingslib.widget.preference.selector.R.id.selector_extra_widget
+ ) as? ImageView
val checkbox = holder.findViewById(android.R.id.checkbox)
if (extraWidgetIconRes != 0) {
extraWidget?.setImageResource(extraWidgetIconRes)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionTwoTargetPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/PermissionTwoTargetPreference.kt
index 13c9ee7c4..a3a3172e8 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionTwoTargetPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v36/PermissionTwoTargetPreference.kt
@@ -14,13 +14,15 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.handheld
+package com.android.permissioncontroller.permission.ui.handheld.v36
import android.content.Context
+import android.os.Build
import android.util.AttributeSet
import android.widget.ImageView
import androidx.annotation.AttrRes
import androidx.annotation.DrawableRes
+import androidx.annotation.RequiresApi
import androidx.annotation.StyleRes
import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.R
@@ -32,6 +34,7 @@ import com.android.settingslib.widget.TwoTargetPreference
* - Propagates the supplied `app:extraWidgetIcon` drawable to the second target
* - Allows defining a click listener on the second target (the icon on the right)
*/
+@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
class PermissionTwoTargetPreference : TwoTargetPreference {
constructor(context: Context) : super(context) {
init(context, null)
@@ -44,7 +47,7 @@ class PermissionTwoTargetPreference : TwoTargetPreference {
constructor(
context: Context,
attrs: AttributeSet?,
- @AttrRes defStyleAttr: Int
+ @AttrRes defStyleAttr: Int,
) : super(context, attrs, defStyleAttr) {
init(context, attrs)
}
@@ -53,17 +56,19 @@ class PermissionTwoTargetPreference : TwoTargetPreference {
context: Context,
attrs: AttributeSet?,
@AttrRes defStyleAttr: Int,
- @StyleRes defStyleRes: Int
+ @StyleRes defStyleRes: Int,
) : super(context, attrs, defStyleAttr, defStyleRes) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
+ layoutResource = R.layout.permission_preference_two_target
extraWidgetIconRes =
ResourceUtils.getResourceIdByAttr(context, attrs, R.attr.extraWidgetIcon)
}
@DrawableRes private var extraWidgetIconRes = 0
+ private var extraWidgetContentDescription: String? = null
private var secondTargetClickListener: OnSecondTargetClickListener? = null
override fun onBindViewHolder(holder: PreferenceViewHolder) {
@@ -72,6 +77,9 @@ class PermissionTwoTargetPreference : TwoTargetPreference {
if (extraWidgetIconRes != 0) {
settingsButton.setImageResource(extraWidgetIconRes)
}
+ if (extraWidgetContentDescription != null) {
+ settingsButton.contentDescription = extraWidgetContentDescription
+ }
if (secondTargetClickListener != null) {
settingsButton.setOnClickListener {
secondTargetClickListener!!.onSecondTargetClick(this)
@@ -90,8 +98,9 @@ class PermissionTwoTargetPreference : TwoTargetPreference {
notifyChanged()
}
- fun setExtraWidgetIconRes(@DrawableRes extraWidgetIconRes: Int) {
- this.extraWidgetIconRes = extraWidgetIconRes
+ fun setExtraWidgetIcon(@DrawableRes iconRes: Int, contentDescription: String) {
+ this.extraWidgetIconRes = iconRes
+ this.extraWidgetContentDescription = contentDescription
notifyChanged()
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageDetailsViewModelLegacy.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageDetailsViewModelLegacy.kt
deleted file mode 100644
index 1369bfdaa..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageDetailsViewModelLegacy.kt
+++ /dev/null
@@ -1,596 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-@file:Suppress("DEPRECATION")
-
-package com.android.permissioncontroller.permission.ui.legacy
-
-import android.Manifest
-import android.app.AppOpsManager
-import android.app.Application
-import android.app.LoaderManager
-import android.app.role.RoleManager
-import android.content.Context
-import android.content.pm.ApplicationInfo
-import android.content.res.Resources
-import android.graphics.drawable.Drawable
-import android.os.Build
-import android.os.UserHandle
-import androidx.annotation.RequiresApi
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.android.permissioncontroller.PermissionControllerApplication
-import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.model.AppPermissionGroup
-import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp
-import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
-import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage.TimelineUsage
-import com.android.permissioncontroller.permission.model.v31.PermissionUsages
-import com.android.permissioncontroller.permission.ui.handheld.v31.getDurationUsedStr
-import com.android.permissioncontroller.permission.ui.handheld.v31.shouldShowSubattributionInPermissionsDashboard
-import com.android.permissioncontroller.permission.utils.KotlinUtils.getPackageLabel
-import com.android.permissioncontroller.permission.utils.PermissionMapping
-import com.android.permissioncontroller.permission.utils.StringUtils
-import com.android.permissioncontroller.permission.utils.Utils
-import com.android.permissioncontroller.permission.utils.v31.SubattributionUtils
-import java.time.Instant
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.TimeUnit.DAYS
-import kotlin.math.max
-
-/** View model for the permission details fragment. */
-@RequiresApi(Build.VERSION_CODES.S)
-class PermissionUsageDetailsViewModelLegacy(
- val application: Application,
- val roleManager: RoleManager,
- private val permissionGroup: String,
- val sessionId: Long
-) : ViewModel() {
-
- companion object {
- private const val ONE_HOUR_MS = 3_600_000
- private const val ONE_MINUTE_MS = 60_000
- private const val CLUSTER_SPACING_MINUTES: Long = 1L
- private val TIME_7_DAYS_DURATION: Long = DAYS.toMillis(7)
- private val TIME_24_HOURS_DURATION: Long = DAYS.toMillis(1)
- }
-
- private val mTimeFilterItemMs = mutableListOf<TimeFilterItemMs>()
-
- init {
- initializeTimeFilterItems(application)
- }
-
- /** Loads permission usages using [PermissionUsages]. Response is returned to the [callback]. */
- fun loadPermissionUsages(
- loaderManager: LoaderManager,
- permissionUsages: PermissionUsages,
- callback: PermissionUsages.PermissionsUsagesChangeCallback,
- filterTimesIndex: Int
- ) {
- val timeFilterItemMs: TimeFilterItemMs = mTimeFilterItemMs[filterTimesIndex]
- val filterTimeBeginMillis = max(System.currentTimeMillis() - timeFilterItemMs.timeMs, 0)
- permissionUsages.load(
- /* filterPackageName= */ null,
- /* filterPermissionGroups= */ null,
- filterTimeBeginMillis,
- Long.MAX_VALUE,
- PermissionUsages.USAGE_FLAG_LAST or PermissionUsages.USAGE_FLAG_HISTORICAL,
- loaderManager,
- /* getUiInfo= */ false,
- /* getNonPlatformPermissions= */ false,
- /* callback= */ callback,
- /* sync= */ false
- )
- }
-
- /**
- * Create a [PermissionUsageDetailsUiData] based on the provided data.
- *
- * @param appPermissionUsages data about app permission usages
- * @param showSystem whether system apps should be shown
- * @param show7Days whether the last 7 days of history should be shown
- */
- fun buildPermissionUsageDetailsUiData(
- appPermissionUsages: List<AppPermissionUsage>,
- showSystem: Boolean,
- show7Days: Boolean
- ): PermissionUsageDetailsUiData {
- val showPermissionUsagesDuration =
- if (show7Days) {
- TIME_7_DAYS_DURATION
- } else {
- TIME_24_HOURS_DURATION
- }
- val startTime =
- (System.currentTimeMillis() - showPermissionUsagesDuration).coerceAtLeast(
- Instant.EPOCH.toEpochMilli()
- )
- val appPermissionTimelineUsages: List<AppPermissionTimelineUsage> =
- extractAppPermissionTimelineUsagesForGroup(appPermissionUsages, permissionGroup)
- val shouldDisplayShowSystemToggle =
- shouldDisplayShowSystemToggle(appPermissionTimelineUsages)
- val permissionApps: List<PermissionApp> =
- getPermissionAppsWithRecentDiscreteUsage(
- appPermissionTimelineUsages,
- showSystem,
- startTime
- )
- val appPermissionUsageEntries =
- buildDiscreteAccessClusterData(appPermissionTimelineUsages, showSystem, startTime)
-
- return PermissionUsageDetailsUiData(
- permissionApps,
- shouldDisplayShowSystemToggle,
- appPermissionUsageEntries
- )
- }
-
- private fun getHistoryPreferenceData(
- discreteAccessClusterData: DiscreteAccessClusterData,
- ): HistoryPreferenceData {
- val context = application
- val accessTimeList =
- discreteAccessClusterData.discreteAccessDataList.map { p -> p.accessTimeMs }
- val durationSummaryLabel =
- getDurationSummary(discreteAccessClusterData, accessTimeList, context)
- val proxyLabel = getProxyPackageLabel(discreteAccessClusterData)
- val subattributionLabel = getSubattributionLabel(discreteAccessClusterData)
- val showingSubattribution = subattributionLabel != null && subattributionLabel.isNotEmpty()
- val summary =
- buildUsageSummary(durationSummaryLabel, proxyLabel, subattributionLabel, context)
-
- return HistoryPreferenceData(
- UserHandle.getUserHandleForUid(
- discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.uid
- ),
- discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.packageName,
- discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.icon,
- discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.label,
- permissionGroup,
- discreteAccessClusterData.discreteAccessDataList.last().accessTimeMs,
- discreteAccessClusterData.discreteAccessDataList.first().accessTimeMs,
- summary,
- showingSubattribution,
- discreteAccessClusterData.appPermissionTimelineUsage.attributionTags,
- sessionId
- )
- }
-
- /**
- * Returns whether the provided [AppPermissionUsage] instances contains the provided platform
- * permission group.
- */
- fun containsPlatformAppPermissionGroup(
- appPermissionUsages: List<AppPermissionUsage>,
- groupName: String,
- ) = appPermissionUsages.extractAllPlatformAppPermissionGroups().any { it.name == groupName }
-
- /** Extracts a list of [AppPermissionTimelineUsage] for a particular permission group. */
- private fun extractAppPermissionTimelineUsagesForGroup(
- appPermissionUsages: List<AppPermissionUsage>,
- group: String
- ): List<AppPermissionTimelineUsage> {
- val exemptedPackages = Utils.getExemptedPackages(roleManager)
- return appPermissionUsages
- .filter { !exemptedPackages.contains(it.packageName) }
- .map { appPermissionUsage ->
- getAppPermissionTimelineUsages(
- appPermissionUsage.app,
- appPermissionUsage.groupUsages.firstOrNull { it.group.name == group }
- )
- }
- .flatten()
- }
-
- /** Returns whether the show/hide system toggle should be displayed in the UI. */
- private fun shouldDisplayShowSystemToggle(
- appPermissionTimelineUsages: List<AppPermissionTimelineUsage>,
- ): Boolean =
- appPermissionTimelineUsages
- .map { it.timelineUsage }
- .filter { it.hasDiscreteData() }
- .any { it.group.isSystem() }
-
- /**
- * Returns a list of [PermissionApp] instances which had recent discrete permission usage
- * (recent here refers to usages occurring after the provided start time).
- */
- private fun getPermissionAppsWithRecentDiscreteUsage(
- appPermissionTimelineUsageList: List<AppPermissionTimelineUsage>,
- showSystem: Boolean,
- startTime: Long,
- ): List<PermissionApp> =
- appPermissionTimelineUsageList
- .filter { it.timelineUsage.hasDiscreteData() }
- .filter { showSystem || !it.timelineUsage.group.isSystem() }
- .filter { it.timelineUsage.allDiscreteAccessTime.any { it.first >= startTime } }
- .map { it.permissionApp }
-
- /**
- * Builds a list of [DiscreteAccessClusterData] from the provided list of
- * [AppPermissionTimelineUsage].
- */
- private fun buildDiscreteAccessClusterData(
- appPermissionTimelineUsageList: List<AppPermissionTimelineUsage>,
- showSystem: Boolean,
- startTime: Long,
- ): List<DiscreteAccessClusterData> =
- appPermissionTimelineUsageList
- .map { appPermissionTimelineUsages ->
- val accessDataList =
- extractRecentDiscreteAccessData(
- appPermissionTimelineUsages.timelineUsage,
- showSystem,
- startTime
- )
-
- if (accessDataList.size <= 1) {
- return@map accessDataList.map {
- DiscreteAccessClusterData(appPermissionTimelineUsages, listOf(it))
- }
- }
-
- clusterDiscreteAccessData(appPermissionTimelineUsages, accessDataList)
- }
- .flatten()
- .sortedWith(
- compareBy(
- { -it.discreteAccessDataList.first().accessTimeMs },
- { it.appPermissionTimelineUsage.permissionApp.label }
- )
- )
- .toList()
-
- /**
- * Clusters a list of [DiscreteAccessData] into a list of [DiscreteAccessClusterData] instances.
- *
- * [DiscreteAccessData] which have accesses sufficiently close together in time will be places
- * in the same cluster.
- */
- private fun clusterDiscreteAccessData(
- appPermissionTimelineUsage: AppPermissionTimelineUsage,
- discreteAccessDataList: List<DiscreteAccessData>
- ): List<DiscreteAccessClusterData> {
- val clusterDataList = mutableListOf<DiscreteAccessClusterData>()
- val currentDiscreteAccessDataList: MutableList<DiscreteAccessData> = mutableListOf()
- for (discreteAccessData in discreteAccessDataList) {
- if (currentDiscreteAccessDataList.isEmpty()) {
- currentDiscreteAccessDataList.add(discreteAccessData)
- } else if (
- !canAccessBeAddedToCluster(discreteAccessData, currentDiscreteAccessDataList)
- ) {
- clusterDataList.add(
- DiscreteAccessClusterData(
- appPermissionTimelineUsage,
- currentDiscreteAccessDataList.toMutableList()
- )
- )
- currentDiscreteAccessDataList.clear()
- currentDiscreteAccessDataList.add(discreteAccessData)
- } else {
- currentDiscreteAccessDataList.add(discreteAccessData)
- }
- }
- if (currentDiscreteAccessDataList.isNotEmpty()) {
- clusterDataList.add(
- DiscreteAccessClusterData(appPermissionTimelineUsage, currentDiscreteAccessDataList)
- )
- }
- return clusterDataList
- }
-
- /**
- * Extract recent [DiscreteAccessData] from a list of [TimelineUsage] instances, and return them
- * ordered descending by access time (recent here refers to accesses occurring after the
- * provided start time).
- */
- private fun extractRecentDiscreteAccessData(
- timelineUsages: TimelineUsage,
- showSystem: Boolean,
- startTime: Long
- ): List<DiscreteAccessData> {
- return if (
- timelineUsages.hasDiscreteData() && (showSystem || !timelineUsages.group.isSystem())
- ) {
- getRecentDiscreteAccessData(timelineUsages, startTime)
- .sortedWith(compareBy { -it.accessTimeMs })
- .toList()
- } else {
- listOf()
- }
- }
-
- /**
- * Extract recent [DiscreteAccessData] from a [TimelineUsage]. (recent here refers to accesses
- * occurring after the provided start time).
- */
- private fun getRecentDiscreteAccessData(
- timelineUsage: TimelineUsage,
- startTime: Long
- ): List<DiscreteAccessData> {
- return timelineUsage.allDiscreteAccessTime
- .filter { it.first >= startTime }
- .map {
- DiscreteAccessData(
- it.first,
- it.second,
- it.third,
- )
- }
- }
-
- /**
- * Returns whether the provided [DiscreteAccessData] occurred close enough to those in the
- * clustered list that it can be added to the cluster
- */
- private fun canAccessBeAddedToCluster(
- accessData: DiscreteAccessData,
- clusteredAccessDataList: List<DiscreteAccessData>
- ): Boolean =
- accessData.accessTimeMs / ONE_HOUR_MS ==
- clusteredAccessDataList.first().accessTimeMs / ONE_HOUR_MS &&
- clusteredAccessDataList.last().accessTimeMs / ONE_MINUTE_MS -
- accessData.accessTimeMs / ONE_MINUTE_MS > CLUSTER_SPACING_MINUTES
-
- /**
- * Returns whether the provided [AppPermissionGroup] is considered a system group.
- *
- * For the purpose of Permissions Hub UI, non user-sensitive [AppPermissionGroup]s are
- * considered "system" and should be hidden from the main page unless requested by the user
- * through the "show/hide system" toggle.
- */
- private fun AppPermissionGroup.isSystem() = !Utils.isGroupOrBgGroupUserSensitive(this)
-
- /** Returns whether app subattribution should be shown. */
- private fun shouldShowSubattributionForApp(appInfo: ApplicationInfo): Boolean {
- return shouldShowSubattributionInPermissionsDashboard() &&
- SubattributionUtils.isSubattributionSupported(application, appInfo)
- }
-
- /** Returns a summary of the duration the permission was accessed for. */
- private fun getDurationSummary(
- usage: DiscreteAccessClusterData,
- accessTimeList: List<Long>,
- context: Context
- ): String? {
- if (accessTimeList.isEmpty()) {
- return null
- }
-
- var durationMs: Long
-
- // Since Location accesses are atomic, we manually calculate the access duration
- // by comparing the first and last access within the cluster.
- if (permissionGroup == Manifest.permission_group.LOCATION) {
- durationMs = accessTimeList[0] - accessTimeList[accessTimeList.size - 1]
- } else {
- durationMs =
- usage.discreteAccessDataList.map { it.accessDurationMs }.filter { it > 0 }.sum()
- }
- // Only show the duration summary if it is at least (CLUSTER_SPACING_MINUTES + 1) minutes.
- // Displaying a time that is shorter than the cluster granularity
- // (CLUSTER_SPACING_MINUTES) will not convey useful information.
- if (durationMs >= TimeUnit.MINUTES.toMillis(CLUSTER_SPACING_MINUTES + 1)) {
- return getDurationUsedStr(context, durationMs)
- }
-
- return null
- }
-
- /** Returns the proxied package label if the permission access was proxied. */
- private fun getProxyPackageLabel(usage: DiscreteAccessClusterData): String? =
- usage.discreteAccessDataList
- .firstOrNull { it.proxy?.packageName != null }
- ?.let {
- getPackageLabel(
- PermissionControllerApplication.get(),
- it.proxy!!.packageName!!,
- UserHandle.getUserHandleForUid(it.proxy.uid)
- )
- }
-
- /** Returns the attribution label for the permission access, if any. */
- private fun getSubattributionLabel(usage: DiscreteAccessClusterData): String? =
- if (usage.appPermissionTimelineUsage.label == Resources.ID_NULL) null
- else
- usage.appPermissionTimelineUsage.permissionApp.attributionLabels?.let {
- it[usage.appPermissionTimelineUsage.label]
- }
-
- /** Builds a summary of the permission access. */
- private fun buildUsageSummary(
- subattributionLabel: String?,
- proxyPackageLabel: String?,
- durationSummary: String?,
- context: Context
- ): String? {
- val subTextStrings: MutableList<String?> = mutableListOf()
-
- subattributionLabel?.let { subTextStrings.add(subattributionLabel) }
- proxyPackageLabel?.let { subTextStrings.add(it) }
- durationSummary?.let { subTextStrings.add(it) }
- return when (subTextStrings.size) {
- 3 ->
- context.getString(
- R.string.history_preference_subtext_3,
- subTextStrings[0],
- subTextStrings[1],
- subTextStrings[2]
- )
- 2 ->
- context.getString(
- R.string.history_preference_subtext_2,
- subTextStrings[0],
- subTextStrings[1]
- )
- 1 -> subTextStrings[0]
- else -> null
- }
- }
-
- /**
- * Builds a list of [AppPermissionTimelineUsage] from the provided
- * [AppPermissionUsage.GroupUsage].
- */
- private fun getAppPermissionTimelineUsages(
- app: PermissionApp,
- groupUsage: AppPermissionUsage.GroupUsage?
- ): List<AppPermissionTimelineUsage> {
- if (groupUsage == null) {
- return listOf()
- }
-
- if (shouldShowSubattributionForApp(app.appInfo)) {
- return groupUsage.attributionLabelledGroupUsages.map {
- AppPermissionTimelineUsage(permissionGroup, app, it, it.label)
- }
- }
-
- return listOf(
- AppPermissionTimelineUsage(permissionGroup, app, groupUsage, Resources.ID_NULL)
- )
- }
-
- /** Extracts to a set all the permission groups declared by the platform. */
- private fun List<AppPermissionUsage>.extractAllPlatformAppPermissionGroups():
- Set<AppPermissionGroup> =
- this.flatMap { it.groupUsages }
- .map { it.group }
- .filter { PermissionMapping.isPlatformPermissionGroup(it.name) }
- .toSet()
-
- /** Initialize all relevant [TimeFilterItemMs] values. */
- private fun initializeTimeFilterItems(context: Context) {
- mTimeFilterItemMs.add(
- TimeFilterItemMs(Long.MAX_VALUE, context.getString(R.string.permission_usage_any_time))
- )
- mTimeFilterItemMs.add(
- TimeFilterItemMs(
- DAYS.toMillis(7),
- StringUtils.getIcuPluralsString(context, R.string.permission_usage_last_n_days, 7)
- )
- )
- mTimeFilterItemMs.add(
- TimeFilterItemMs(
- DAYS.toMillis(1),
- StringUtils.getIcuPluralsString(context, R.string.permission_usage_last_n_days, 1)
- )
- )
-
- // TODO: theianchen add code for filtering by time here.
- }
-
- /** Data used to create a preference for an app's permission usage. */
- data class HistoryPreferenceData(
- val userHandle: UserHandle,
- val pkgName: String,
- val appIcon: Drawable?,
- val preferenceTitle: String,
- val permissionGroup: String,
- val accessStartTime: Long,
- val accessEndTime: Long,
- val summaryText: CharSequence?,
- val showingAttribution: Boolean,
- val attributionTags: ArrayList<String>,
- val sessionId: Long
- )
-
- /**
- * A class representing a given time, e.g., "in the last hour".
- *
- * @param timeMs the time represented by this object in milliseconds.
- * @param label the label to describe the timeframe
- */
- data class TimeFilterItemMs(val timeMs: Long, val label: String)
-
- /**
- * Class containing all the information needed by the permission usage details fragments to
- * render UI.
- */
- inner class PermissionUsageDetailsUiData(
- /** List of [PermissionApp] instances */
- // Note that these are used only to cache app data for the permission usage details
- // fragment, and have no bearing on the UI on the main permission usage page.
- val permissionApps: List<PermissionApp>,
- /** Whether to show the "show/hide system" toggle. */
- val shouldDisplayShowSystemToggle: Boolean,
- /** [DiscreteAccessClusterData] instances ordered for display in UI */
- private val discreteAccessClusterDataList: List<DiscreteAccessClusterData>,
- ) {
- // Note that the HistoryPreferenceData are not initialized within the
- // PermissionUsageDetailsUiData instance as the need to be constructed only after the
- // calling fragment loads the necessary PermissionApp instances. We will attempt to remove
- // this dependency in b/240978905.
- /** Builds a list of [HistoryPreferenceData] to be displayed in the UI. */
- fun getHistoryPreferenceDataList(): List<HistoryPreferenceData> {
- return discreteAccessClusterDataList.map {
- this@PermissionUsageDetailsViewModelLegacy.getHistoryPreferenceData(it)
- }
- }
- }
-
- /**
- * Data class representing a cluster of accesses, to be represented as a single entry in the UI.
- */
- data class DiscreteAccessClusterData(
- val appPermissionTimelineUsage: AppPermissionTimelineUsage,
- val discreteAccessDataList: List<DiscreteAccessData>
- )
-
- /** Data class representing a discrete permission access. */
- data class DiscreteAccessData(
- val accessTimeMs: Long,
- val accessDurationMs: Long,
- val proxy: AppOpsManager.OpEventProxyInfo?
- )
-
- /** Data class representing an app's permissions usages for a particular permission group. */
- data class AppPermissionTimelineUsage(
- /** Permission group whose usage is being tracked. */
- val permissionGroup: String,
- // we need a PermissionApp because the loader takes the PermissionApp
- // object and loads the icon and label information asynchronously
- /** App whose permissions are being tracked. */
- val permissionApp: PermissionApp,
- /** Timeline usage for the given app and permission. */
- val timelineUsage: TimelineUsage,
- val label: Int
- ) {
- val attributionTags: java.util.ArrayList<String>
- get() = ArrayList(timelineUsage.attributionTags)
- }
-}
-
-/** Factory for an [PermissionUsageDetailsViewModelLegacy] */
-@RequiresApi(Build.VERSION_CODES.S)
-class PermissionUsageDetailsViewModelFactoryLegacy(
- private val application: Application,
- private val roleManager: RoleManager,
- private val filterGroup: String,
- private val sessionId: Long
-) : ViewModelProvider.Factory {
-
- override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST")
- return PermissionUsageDetailsViewModelLegacy(
- application,
- roleManager,
- filterGroup,
- sessionId
- )
- as T
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
index 0a01929e6..1e5b96c2e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
@@ -29,6 +29,7 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
import android.app.admin.DevicePolicyManager
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED
@@ -41,6 +42,8 @@ import android.os.Build
import android.os.Bundle
import android.os.Process
import android.permission.PermissionManager
+import android.permission.flags.Flags
+import android.util.ArrayMap
import android.util.Log
import androidx.core.util.Consumer
import androidx.lifecycle.ViewModel
@@ -116,6 +119,7 @@ import com.android.permissioncontroller.permission.utils.SafetyNetLogger
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils
import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils
+import com.android.permissioncontroller.permission.utils.v35.MultiDeviceUtils.isPermissionDeviceAware
/**
* ViewModel for the GrantPermissionsActivity. Tracks all permission groups that are affected by the
@@ -153,6 +157,21 @@ class GrantPermissionsViewModel(
} else {
null
}
+ private val permissionGroupToDeviceIdMap: Map<String, Int> =
+ if (SdkLevel.isAtLeastV() && Flags.allowHostPermissionDialogsOnVirtualDevices()) {
+ requestedPermissions
+ .filter({ PermissionMapping.getGroupOfPlatformPermission(it) != null })
+ .associateBy({ PermissionMapping.getGroupOfPlatformPermission(it)!! }, {
+ if (isPermissionDeviceAware(
+ app.applicationContext,
+ deviceId,
+ it
+ )
+ ) deviceId else Context.DEVICE_ID_DEFAULT
+ })
+ } else {
+ ArrayMap()
+ }
private val dpm = app.getSystemService(DevicePolicyManager::class.java)!!
private val permissionPolicy = dpm.getPermissionPolicy(null)
private val groupStates = mutableMapOf<String, GroupState>()
@@ -314,7 +333,8 @@ class GrantPermissionsViewModel(
}
val getLiveDataFun = { groupName: String ->
- LightAppPermGroupLiveData[packageName, groupName, user, deviceId]
+ LightAppPermGroupLiveData[packageName, groupName, user,
+ permissionGroupToDeviceIdMap.get(groupName) ?: deviceId]
}
setSourcesToDifference(requestedGroups.keys, appPermGroupLiveDatas, getLiveDataFun)
}
@@ -398,7 +418,8 @@ class GrantPermissionsViewModel(
safetyLabel,
groupState.group.permGroupName
),
- deviceId
+ permissionGroupToDeviceIdMap.get(groupState.group.permGroupName)
+ ?: deviceId
)
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageCustomPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageCustomPermissionsViewModel.kt
index bd80a88cd..429799157 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageCustomPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageCustomPermissionsViewModel.kt
@@ -16,17 +16,23 @@
package com.android.permissioncontroller.permission.ui.model
+import android.Manifest
import android.app.Application
+import android.content.Intent
+import android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
+import com.android.permission.flags.Flags
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.PermGroupsPackagesLiveData
import com.android.permissioncontroller.permission.data.PermGroupsPackagesUiInfoLiveData
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
+import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.permission.utils.navigateSafe
/**
@@ -38,6 +44,12 @@ import com.android.permissioncontroller.permission.utils.navigateSafe
class ManageCustomPermissionsViewModel(private val app: Application) : AndroidViewModel(app) {
val uiDataLiveData = PermGroupsPackagesUiInfoLiveData(app, UsedCustomPermGroupNamesLiveData())
+ val additionaPermGroupsUiInfo =
+ PermGroupsPackagesUiInfoLiveData(
+ app,
+ if (Flags.declutteredPermissionManagerEnabled()) AdditionalPermGroupNamesLiveData(app)
+ else MutableLiveData<List<String>>(),
+ )
/**
* Navigate to a Permission Apps fragment
@@ -46,6 +58,15 @@ class ManageCustomPermissionsViewModel(private val app: Application) : AndroidVi
* @param args The args to pass to the new fragment
*/
fun showPermissionApps(fragment: Fragment, args: Bundle) {
+ val groupName = args.getString(Intent.EXTRA_PERMISSION_GROUP_NAME)
+ if (groupName == Manifest.permission_group.NOTIFICATIONS) {
+ Utils.navigateToNotificationSettings(fragment.context!!)
+ return
+ }
+ if (Utils.isHealthPermissionUiEnabled() && groupName == HEALTH_PERMISSION_GROUP) {
+ Utils.navigateToHealthConnectSettings(fragment.context!!)
+ return
+ }
fragment.findNavController().navigateSafe(R.id.manage_to_perm_apps, args)
}
}
@@ -58,7 +79,8 @@ class ManageCustomPermissionsViewModel(private val app: Application) : AndroidVi
class ManageCustomPermissionsViewModelFactory(private val app: Application) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST") return ManageCustomPermissionsViewModel(app) as T
+ @Suppress("UNCHECKED_CAST")
+ return ManageCustomPermissionsViewModel(app) as T
}
}
@@ -77,3 +99,32 @@ class UsedCustomPermGroupNamesLiveData : SmartUpdateMediatorLiveData<List<String
/* No op override */
}
}
+
+/**
+ * A LiveData that is the union of LiveData UsedCustomPermGroupNamesLiveData and
+ * UnusedStandardPermGroupNamesLiveData.
+ *
+ * @param app The current application of the fragment
+ */
+class AdditionalPermGroupNamesLiveData(private val app: Application) :
+ SmartUpdateMediatorLiveData<List<String>>() {
+
+ val usedCustomGroupNames = UsedCustomPermGroupNamesLiveData()
+ val unusedStandardGroupNames = UnusedStandardPermGroupNamesLiveData(app)
+
+ init {
+ addSource(usedCustomGroupNames) { update() }
+ addSource(unusedStandardGroupNames) { update() }
+ }
+
+ private fun combineGroupNames(
+ groupNames1: List<String>?,
+ groupNames2: List<String>?,
+ ): List<String> {
+ return (groupNames1 ?: emptyList()) + (groupNames2 ?: emptyList())
+ }
+
+ override fun onUpdate() {
+ value = combineGroupNames(usedCustomGroupNames.value, unusedStandardGroupNames.value)
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
index aeab0aa89..6dabe8ab7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
@@ -23,8 +23,11 @@ import android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.map
import androidx.navigation.fragment.findNavController
+import com.android.permission.flags.Flags
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.PermGroupsPackagesLiveData
import com.android.permissioncontroller.permission.data.PermGroupsPackagesUiInfoLiveData
@@ -45,7 +48,21 @@ import com.android.permissioncontroller.permission.utils.navigateSafe
class ManageStandardPermissionsViewModel(private val app: Application) : AndroidViewModel(app) {
val uiDataLiveData = PermGroupsPackagesUiInfoLiveData(app, StandardPermGroupNamesLiveData)
+ val usedStandardPermGroupsUiInfo =
+ PermGroupsPackagesUiInfoLiveData(
+ app,
+ if (Flags.declutteredPermissionManagerEnabled()) UsedStandardPermGroupNamesLiveData(app)
+ else MutableLiveData<List<String>>(),
+ )
val numCustomPermGroups = NumCustomPermGroupsWithPackagesLiveData()
+ val numUnusedStandardPermGroups =
+ MediatorLiveData<Int>().apply {
+ if (Flags.declutteredPermissionManagerEnabled()) {
+ addSource(UnusedStandardPermGroupNamesLiveData(app)) { groupNames ->
+ value = groupNames.size
+ }
+ }
+ }
val numAutoRevoked = unusedAutoRevokePackagesLiveData.map { it?.size ?: 0 }
/**
@@ -98,3 +115,47 @@ class NumCustomPermGroupsWithPackagesLiveData() : SmartUpdateMediatorLiveData<In
value = customPermGroupPackages.value?.size ?: 0
}
}
+
+/**
+ * A LiveData that tracks the names of the platform-defined permission groups, such that at least
+ * one of the permissions in the group has been requested at runtime by at least one non-system
+ * application.
+ *
+ * @param app The current application of the fragment
+ */
+class UsedStandardPermGroupNamesLiveData(private val app: Application) :
+ SmartUpdateMediatorLiveData<List<String>>() {
+ init {
+ addSource(PermGroupsPackagesUiInfoLiveData(app, StandardPermGroupNamesLiveData)) {
+ permGroups ->
+ if (permGroups.values.any { it != null }) {
+ value =
+ permGroups.filterValues { it != null && it.nonSystemTotal > 0 }.keys.toList()
+ }
+ }
+ }
+
+ override fun onUpdate() {
+ /* No op override */
+ }
+}
+
+/**
+ * A LiveData that tracks the names of the platform-defined permission groups, such that none of the
+ * the permissions in the group has been requested at runtime by any non-system application.
+ *
+ * @param app The current application of the fragment
+ */
+class UnusedStandardPermGroupNamesLiveData(private val app: Application) :
+ SmartUpdateMediatorLiveData<List<String>>() {
+ init {
+ addSource(PermGroupsPackagesUiInfoLiveData(app, StandardPermGroupNamesLiveData)) {
+ permGroups ->
+ value = permGroups.filterValues { it != null && it.nonSystemTotal == 0 }.keys.toList()
+ }
+ }
+
+ override fun onUpdate() {
+ /* No op override */
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/BasePermissionUsageDetailsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/BasePermissionUsageDetailsViewModel.kt
deleted file mode 100644
index 64c5c6927..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/BasePermissionUsageDetailsViewModel.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.ui.model.v31
-
-import android.app.Application
-import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.LiveData
-import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.ui.handheld.v31.getDurationUsedStr
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.Companion.CLUSTER_SPACING_MINUTES
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState
-import com.android.permissioncontroller.permission.utils.KotlinUtils
-import java.util.concurrent.TimeUnit
-
-abstract class BasePermissionUsageDetailsViewModel(val app: Application) : AndroidViewModel(app) {
- abstract fun getPermissionUsagesDetailsInfoUiLiveData(): LiveData<PermissionUsageDetailsUiState>
-
- abstract fun getShowSystem(): Boolean
-
- abstract val showSystemLiveData: LiveData<Boolean>
-
- abstract fun getShow7Days(): Boolean
-
- abstract fun updateShowSystemAppsToggle(showSystem: Boolean)
-
- abstract fun updateShow7DaysToggle(show7Days: Boolean)
-
- private val packageIconCache: MutableMap<Pair<String, UserHandle>, Drawable> = mutableMapOf()
- private val packageLabelCache: MutableMap<String, String> = mutableMapOf()
-
- /**
- * Returns the label for the provided package name, by first searching the cache otherwise
- * retrieving it from the app's [android.content.pm.ApplicationInfo].
- */
- fun getPackageLabel(packageName: String, user: UserHandle): String {
- if (packageLabelCache.containsKey(packageName)) {
- return requireNotNull(packageLabelCache[packageName])
- }
- val packageLabel = getPackageLabel(app, packageName, user)
- packageLabelCache[packageName] = packageLabel
- return packageLabel
- }
-
- open fun getPackageLabel(app: Application, packageName: String, user: UserHandle): String {
- return KotlinUtils.getPackageLabel(app, packageName, user)
- }
-
- /**
- * Returns the icon for the provided package name and user, by first searching the cache
- * otherwise retrieving it from the app's [android.content.pm.ApplicationInfo].
- */
- fun getBadgedPackageIcon(packageName: String, userHandle: UserHandle): Drawable? {
- val packageNameWithUser: Pair<String, UserHandle> = Pair(packageName, userHandle)
- if (packageIconCache.containsKey(packageNameWithUser)) {
- return requireNotNull(packageIconCache[packageNameWithUser])
- }
- val packageIcon = getBadgedPackageIcon(app, packageName, userHandle)
- if (packageIcon != null) packageIconCache[packageNameWithUser] = packageIcon
-
- return packageIcon
- }
-
- open fun getBadgedPackageIcon(
- app: Application,
- packageName: String,
- user: UserHandle
- ): Drawable? {
- return KotlinUtils.getBadgedPackageIcon(app, packageName, user)
- }
-
- fun getDurationSummary(durationMs: Long): String? {
- // Only show the duration summary if it is at least (CLUSTER_SPACING_MINUTES + 1) minutes.
- // Displaying a time that is shorter than the cluster granularity
- // (CLUSTER_SPACING_MINUTES) will not convey useful information.
- if (durationMs >= TimeUnit.MINUTES.toMillis(CLUSTER_SPACING_MINUTES + 1)) {
- return getDurationUsedStr(app, durationMs)
- }
- return null
- }
-
- fun buildUsageSummary(
- subAttributionLabel: String?,
- proxyPackageLabel: String?,
- durationSummary: String?
- ): String? {
- val subTextStrings: MutableList<String> = mutableListOf()
- subAttributionLabel?.let { subTextStrings.add(subAttributionLabel) }
- proxyPackageLabel?.let { subTextStrings.add(it) }
- durationSummary?.let { subTextStrings.add(it) }
- return when (subTextStrings.size) {
- 3 ->
- app.getString(
- R.string.history_preference_subtext_3,
- subTextStrings[0],
- subTextStrings[1],
- subTextStrings[2]
- )
- 2 ->
- app.getString(
- R.string.history_preference_subtext_2,
- subTextStrings[0],
- subTextStrings[1]
- )
- 1 -> subTextStrings[0]
- else -> null
- }
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt
index 497e5cc35..ad21ab220 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,677 +13,288 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-@file:Suppress("DEPRECATION")
package com.android.permissioncontroller.permission.ui.model.v31
import android.Manifest
import android.app.AppOpsManager
+import android.app.AppOpsManager.OPSTR_EMERGENCY_LOCATION
import android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA
import android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE
import android.app.Application
-import android.app.role.RoleManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
-import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.location.LocationManager
import android.os.Build
-import android.os.Bundle
import android.os.UserHandle
-import android.os.UserManager
import android.permission.flags.Flags
-import android.util.Log
import androidx.annotation.RequiresApi
-import androidx.lifecycle.AbstractSavedStateViewModelFactory
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
-import androidx.savedstate.SavedStateRegistryOwner
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.application
+import androidx.lifecycle.asLiveData
+import androidx.lifecycle.createSavedStateHandle
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.CreationExtras
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.DeviceUtils
import com.android.permissioncontroller.R
+import com.android.permissioncontroller.appops.data.repository.v31.AppOpRepository
import com.android.permissioncontroller.permission.compat.IntentCompat
-import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData
-import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
-import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
-import com.android.permissioncontroller.permission.data.get
-import com.android.permissioncontroller.permission.data.v31.AllLightHistoricalPackageOpsLiveData
-import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
-import com.android.permissioncontroller.permission.model.livedatatypes.v31.AppPermissionId
-import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps
-import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.AppPermissionDiscreteAccesses
-import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.AttributedAppPermissionDiscreteAccesses
-import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.Companion.NO_ATTRIBUTION_TAG
-import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.DiscreteAccess
+import com.android.permissioncontroller.permission.data.repository.v31.PermissionRepository
+import com.android.permissioncontroller.permission.domain.model.v31.PermissionTimelineUsageModel
+import com.android.permissioncontroller.permission.domain.model.v31.PermissionTimelineUsageModelWrapper
+import com.android.permissioncontroller.permission.domain.usecase.v31.GetPermissionGroupUsageDetailsUseCase
import com.android.permissioncontroller.permission.ui.handheld.v31.getDurationUsedStr
-import com.android.permissioncontroller.permission.ui.handheld.v31.shouldShowSubattributionInPermissionsDashboard
import com.android.permissioncontroller.permission.utils.PermissionMapping
-import com.android.permissioncontroller.permission.utils.Utils
-import com.android.permissioncontroller.permission.utils.v31.SubattributionUtils
+import com.android.permissioncontroller.pm.data.repository.v31.PackageRepository
+import com.android.permissioncontroller.role.data.repository.v31.RoleRepository
+import com.android.permissioncontroller.user.data.repository.v31.UserRepository
import java.time.Instant
import java.util.Objects
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.DAYS
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.stateIn
-/** [ViewModel] for the Permission Usage Details page. */
-@RequiresApi(Build.VERSION_CODES.S)
class PermissionUsageDetailsViewModel(
- private val application: Application,
+ app: Application,
+ private val getPermissionUsageDetailsUseCase: GetPermissionGroupUsageDetailsUseCase,
private val state: SavedStateHandle,
private val permissionGroup: String,
-) : BasePermissionUsageDetailsViewModel(application) {
-
- val allLightHistoricalPackageOpsLiveData =
- AllLightHistoricalPackageOpsLiveData(application, opNames)
- private val appPermGroupUiInfoLiveDataList =
- mutableMapOf<AppPermissionId, AppPermGroupUiInfoLiveData>()
- private val lightPackageInfoLiveDataMap =
- mutableMapOf<Pair<String, UserHandle>, LightPackageInfoLiveData>()
-
- override val showSystemLiveData = state.getLiveData(SHOULD_SHOW_SYSTEM_KEY, false)
- val show7DaysLiveData = state.getLiveData(SHOULD_SHOW_7_DAYS_KEY, false)
-
- private val roleManager =
- Utils.getSystemServiceSafe(application.applicationContext, RoleManager::class.java)
- private val userManager =
- Utils.getSystemServiceSafe(application.applicationContext, UserManager::class.java)
+ scope: CoroutineScope? = null,
+ private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
+ private val packageRepository: PackageRepository = PackageRepository.getInstance(app),
+) : AndroidViewModel(app) {
+ private val coroutineScope = scope ?: viewModelScope
+ private val context = app
+
+ private val packageIconCache: MutableMap<Pair<String, UserHandle>, Drawable> = mutableMapOf()
+ private val packageLabelCache: MutableMap<String, String> = mutableMapOf()
+
+ private val showSystemFlow = MutableStateFlow(state[SHOULD_SHOW_SYSTEM_KEY] ?: false)
+ private val show7DaysFlow = MutableStateFlow(state[SHOULD_SHOW_7_DAYS_KEY] ?: false)
+
+ private val permissionTimelineUsagesFlow:
+ StateFlow<PermissionTimelineUsageModelWrapper> by lazy {
+ getPermissionUsageDetailsUseCase(coroutineScope)
+ .flowOn(defaultDispatcher)
+ .stateIn(
+ coroutineScope,
+ SharingStarted.WhileSubscribed(5000),
+ PermissionTimelineUsageModelWrapper.Loading,
+ )
+ }
- /** Updates whether system app permissions usage should be displayed in the UI. */
- override fun updateShowSystemAppsToggle(showSystem: Boolean) {
- if (showSystem != state[SHOULD_SHOW_SYSTEM_KEY]) {
- state[SHOULD_SHOW_SYSTEM_KEY] = showSystem
- }
+ @VisibleForTesting
+ val permissionUsageDetailsUiStateFlow: Flow<PermissionUsageDetailsUiState> by lazy {
+ combine(permissionTimelineUsagesFlow, showSystemFlow, show7DaysFlow) {
+ permissionTimelineUsages,
+ showSystem,
+ show7Days ->
+ permissionTimelineUsages.buildPermissionUsageDetailsUiInfo(showSystem, show7Days)
+ }
+ .flowOn(defaultDispatcher)
}
- /** Updates whether 7 days usage or 1 day usage should be displayed in the UI. */
- override fun updateShow7DaysToggle(show7Days: Boolean) {
- if (show7Days != state[SHOULD_SHOW_7_DAYS_KEY]) {
- state[SHOULD_SHOW_7_DAYS_KEY] = show7Days
- }
+ fun getPermissionUsagesDetailsInfoUiLiveData(): LiveData<PermissionUsageDetailsUiState> {
+ return permissionUsageDetailsUiStateFlow.asLiveData(
+ context = coroutineScope.coroutineContext
+ )
}
- /** Creates a [PermissionUsageDetailsUiState] containing all information to render the UI. */
- fun buildPermissionUsageDetailsUiInfo(): PermissionUsageDetailsUiState {
- val showSystem: Boolean = state[SHOULD_SHOW_SYSTEM_KEY] ?: false
- val show7Days: Boolean = state[SHOULD_SHOW_7_DAYS_KEY] ?: false
- val showPermissionUsagesDuration =
- if (show7Days && DeviceUtils.isHandheld()) {
- TIME_7_DAYS_DURATION
- } else {
- TIME_24_HOURS_DURATION
- }
+ private fun PermissionTimelineUsageModelWrapper.buildPermissionUsageDetailsUiInfo(
+ showSystem: Boolean,
+ show7Days: Boolean,
+ ): PermissionUsageDetailsUiState {
+ if (this is PermissionTimelineUsageModelWrapper.Loading) {
+ return PermissionUsageDetailsUiState.Loading
+ }
+ val timelineUsageModels =
+ (this as PermissionTimelineUsageModelWrapper.Success).timelineUsageModels
val startTime =
- (System.currentTimeMillis() - showPermissionUsagesDuration).coerceAtLeast(
+ (System.currentTimeMillis() - getUsageDuration(show7Days)).coerceAtLeast(
Instant.EPOCH.toEpochMilli()
)
+ val permissionTimelineUsageModels =
+ timelineUsageModels.filter { it.accessEndMillis > startTime }
+ val containsSystemUsages = permissionTimelineUsageModels.any { !it.isUserSensitive }
+ val result =
+ permissionTimelineUsageModels
+ .filter { showSystem || it.isUserSensitive }
+ .map { clusterOps ->
+ val durationSummaryLabel =
+ if (clusterOps.durationMillis > 0) {
+ getDurationSummary(clusterOps.durationMillis)
+ } else {
+ null
+ }
+ val proxyLabel = getProxyPackageLabel(clusterOps)
+ val isEmergencyLocationAccess =
+ isLocationByPassEnabled() &&
+ clusterOps.opNames.any { it == OPSTR_EMERGENCY_LOCATION }
+ val subAttributionLabel =
+ if (isEmergencyLocationAccess) {
+ emergencyLocationAttributionLabel
+ } else {
+ clusterOps.attributionLabel
+ }
+ val showingSubAttribution = !subAttributionLabel.isNullOrEmpty()
+ val summary =
+ buildUsageSummary(subAttributionLabel, proxyLabel, durationSummaryLabel)
+ PermissionUsageDetailsViewModel.AppPermissionAccessUiInfo(
+ UserHandle.of(clusterOps.userId),
+ clusterOps.packageName,
+ getPackageLabel(clusterOps.packageName, UserHandle.of(clusterOps.userId)),
+ permissionGroup,
+ clusterOps.accessStartMillis,
+ clusterOps.accessEndMillis,
+ summary,
+ showingSubAttribution,
+ clusterOps.attributionTags ?: emptySet(),
+ getBadgedPackageIcon(
+ clusterOps.packageName,
+ UserHandle.of(clusterOps.userId),
+ ),
+ isEmergencyLocationAccess,
+ )
+ }
+ .sortedBy { -1 * it.accessStartTime }
return PermissionUsageDetailsUiState.Success(
- buildAppPermissionAccessUiInfoList(
- allLightHistoricalPackageOpsLiveData,
- startTime,
- showSystem
- ),
- containsSystemAppUsages(allLightHistoricalPackageOpsLiveData, startTime),
+ result,
+ containsSystemUsages,
showSystem,
- show7Days
+ show7Days,
)
}
- /**
- * Returns whether the "show/hide system" toggle should be displayed in the UI for the provided
- * [AllLightHistoricalPackageOpsLiveData].
- */
- private fun containsSystemAppUsages(
- allLightHistoricalPackageOpsLiveData: AllLightHistoricalPackageOpsLiveData,
- startTime: Long
- ): Boolean {
- return allLightHistoricalPackageOpsLiveData
- .getLightHistoricalPackageOps()
- ?.flatMap {
- it.appPermissionDiscreteAccesses
- .map { it.withLabel() }
- .filterOutExemptAppPermissions(true)
- .filterAccessesLaterThan(startTime)
- }
- ?.any { isAppPermissionSystem(it.appPermissionId) } ?: false
+ private val emergencyLocationAttributionLabel: String by lazy {
+ context.getString(R.string.privacy_dashboard_emergency_location_enforced_attribution_label)
}
- private fun isPermissionRequestedByApp(appPermissionId: AppPermissionId): Boolean {
- val appRequestedPermissions =
- lightPackageInfoLiveDataMap[
- Pair(appPermissionId.packageName, appPermissionId.userHandle)]
- ?.value
- ?.requestedPermissions ?: listOf()
-
- if (
- appPermissionId.permissionGroup == Manifest.permission_group.LOCATION &&
- appRequestedPermissions.contains(Manifest.permission.LOCATION_BYPASS)
- ) {
- return true
- }
+ fun getShowSystem(): Boolean = showSystemFlow.value
- return appRequestedPermissions.any {
- PermissionMapping.getGroupOfPlatformPermission(it) == appPermissionId.permissionGroup
- }
- }
+ val showSystemLiveData = showSystemFlow.asLiveData(context = coroutineScope.coroutineContext)
- private fun isAppPermissionSystem(appPermissionId: AppPermissionId): Boolean {
- val appPermGroupUiInfo = appPermGroupUiInfoLiveDataList[appPermissionId]?.value
-
- if (appPermGroupUiInfo != null) {
- return appPermGroupUiInfo.isSystem
- } else
- // The AppPermGroupUiInfo may be null if it has either not loaded yet or if the app has not
- // requested any permissions from the permission group in question.
- // The Telecom doesn't request microphone or camera permissions. However, telecom app may
- // use these permissions and they are considered system app permissions, so we return true
- // even if the AppPermGroupUiInfo is unavailable.
- if (
- appPermissionId.packageName == TELECOM_PACKAGE &&
- (appPermissionId.permissionGroup == Manifest.permission_group.CAMERA ||
- appPermissionId.permissionGroup == Manifest.permission_group.MICROPHONE)
- ) {
- return true
+ fun getShow7Days(): Boolean = show7DaysFlow.value
+
+ private fun getUsageDuration(show7Days: Boolean): Long {
+ return if (show7Days && DeviceUtils.isHandheld()) {
+ TIME_7_DAYS_DURATION
+ } else {
+ TIME_24_HOURS_DURATION
}
- return false
}
- /**
- * Extracts access data from [AllLightHistoricalPackageOpsLiveData] and composes
- * [AppPermissionAccessUiInfo]s to be displayed in the UI.
- */
- private fun buildAppPermissionAccessUiInfoList(
- allLightHistoricalPackageOpsLiveData: AllLightHistoricalPackageOpsLiveData,
- startTime: Long,
- showSystem: Boolean
- ): List<AppPermissionAccessUiInfo> {
- return allLightHistoricalPackageOpsLiveData
- .getLightHistoricalPackageOps()
- ?.filter { Utils.shouldShowInSettings(it.userHandle, userManager) }
- ?.flatMap { it.clusterAccesses(startTime, showSystem) }
- ?.map { it.buildAppPermissionAccessUiInfo() }
- ?.sortedBy { -it.accessStartTime } ?: listOf()
- }
+ private fun getProxyPackageLabel(accessCluster: PermissionTimelineUsageModel): String? =
+ accessCluster.proxyPackageName?.let { proxyPackageName ->
+ if (accessCluster.proxyUserId != null) {
+ getPackageLabel(proxyPackageName, UserHandle.of(accessCluster.proxyUserId))
+ } else null
+ }
- private fun LightHistoricalPackageOps.clusterAccesses(
- startTime: Long,
- showSystem: Boolean
- ): List<AppPermissionDiscreteAccessCluster> {
- return if (!shouldShowSubAttributionForApp(getLightPackageInfo(packageName, userHandle)))
- this.clusterAccessesWithoutAttribution(startTime, showSystem)
- else {
- this.clusterAccessesWithAttribution(startTime, showSystem)
+ fun updateShowSystemAppsToggle(showSystem: Boolean) {
+ if (showSystem != state[SHOULD_SHOW_SYSTEM_KEY]) {
+ state[SHOULD_SHOW_SYSTEM_KEY] = showSystem
}
+ showSystemFlow.compareAndSet(!showSystem, showSystem)
}
- /**
- * Clusters accesses that are close enough together in time such that they can be displayed as a
- * single access to the user.
- *
- * Accesses are clustered taking into account any app subattribution, so each cluster will
- * pertain a particular attribution label.
- */
- private fun LightHistoricalPackageOps.clusterAccessesWithAttribution(
- startTime: Long,
- showSystem: Boolean
- ): List<AppPermissionDiscreteAccessCluster> =
- this.attributedAppPermissionDiscreteAccesses
- .flatMap { it.groupAccessesByLabel(getLightPackageInfo(packageName, userHandle)) }
- .filterOutExemptAppPermissions(showSystem)
- .filterAccessesLaterThan(startTime)
- .flatMap { createAccessClusters(it) }
-
- /**
- * Clusters accesses that are close enough together in time such that they can be displayed as a
- * single access to the user.
- *
- * Accesses are clustered disregarding any app subattribution.
- */
- private fun LightHistoricalPackageOps.clusterAccessesWithoutAttribution(
- startTime: Long,
- showSystem: Boolean
- ): List<AppPermissionDiscreteAccessCluster> =
- this.appPermissionDiscreteAccesses
- .map { it.withLabel() }
- .filterOutExemptAppPermissions(showSystem)
- .filterAccessesLaterThan(startTime)
- .flatMap { createAccessClusters(it) }
-
- /** Filters out accesses earlier than the provided start time. */
- private fun List<AppPermissionDiscreteAccessesWithLabel>.filterAccessesLaterThan(
- startTime: Long,
- ): List<AppPermissionDiscreteAccessesWithLabel> =
- this.mapNotNull {
- val updatedDiscreteAccesses =
- it.discreteAccesses.filter { access -> access.accessTimeMs > startTime }
- if (updatedDiscreteAccesses.isEmpty()) null
- else
- AppPermissionDiscreteAccessesWithLabel(
- it.appPermissionId,
- it.attributionLabel,
- it.attributionTags,
- updatedDiscreteAccesses
- )
+ fun updateShow7DaysToggle(show7Days: Boolean) {
+ if (show7Days != state[SHOULD_SHOW_7_DAYS_KEY]) {
+ state[SHOULD_SHOW_7_DAYS_KEY] = show7Days
}
-
- /** Filters out data for apps and permissions that don't need to be displayed in the UI. */
- private fun List<AppPermissionDiscreteAccessesWithLabel>.filterOutExemptAppPermissions(
- showSystem: Boolean
- ): List<AppPermissionDiscreteAccessesWithLabel> {
- val exemptedPackages = Utils.getExemptedPackages(roleManager)
- return filter { !exemptedPackages.contains(it.appPermissionId.packageName) }
- .filter { it.appPermissionId.permissionGroup == permissionGroup }
- .filter { isPermissionRequestedByApp(it.appPermissionId) }
- .filter { showSystem || !isAppPermissionSystem(it.appPermissionId) }
+ show7DaysFlow.compareAndSet(!show7Days, show7Days)
}
/**
- * Converts the provided [AppPermissionDiscreteAccesses] to a
- * [AppPermissionDiscreteAccessesWithLabel] by adding a label.
+ * Returns the label for the provided package name, by first searching the cache otherwise
+ * retrieving it from the app's [android.content.pm.ApplicationInfo].
*/
- private fun AppPermissionDiscreteAccesses.withLabel(): AppPermissionDiscreteAccessesWithLabel =
- AppPermissionDiscreteAccessesWithLabel(
- this.appPermissionId,
- Resources.ID_NULL,
- attributionTags = emptyList(),
- this.discreteAccesses
- )
-
- /** Groups tag-attributed accesses for the provided app and permission by attribution label. */
- private fun AttributedAppPermissionDiscreteAccesses.groupAccessesByLabel(
- lightPackageInfo: LightPackageInfo?
- ): List<AppPermissionDiscreteAccessesWithLabel> {
- if (lightPackageInfo == null) return emptyList()
-
- val appPermissionId = this.appPermissionId
- val labelsToDiscreteAccesses = mutableMapOf<Int, MutableList<DiscreteAccess>>()
- val labelsToTags = mutableMapOf<Int, MutableList<String>>()
-
- val appPermissionDiscreteAccessWithLabels =
- mutableListOf<AppPermissionDiscreteAccessesWithLabel>()
-
- for ((tag, discreteAccesses) in this.attributedDiscreteAccesses) {
- val label: Int =
- if (tag == NO_ATTRIBUTION_TAG) Resources.ID_NULL
- else lightPackageInfo.attributionTagsToLabels[tag] ?: Resources.ID_NULL
-
- if (!labelsToDiscreteAccesses.containsKey(label)) {
- labelsToDiscreteAccesses[label] = mutableListOf()
- }
- labelsToDiscreteAccesses[label]?.addAll(discreteAccesses)
-
- if (!labelsToTags.containsKey(label)) {
- labelsToTags[label] = mutableListOf()
- }
- labelsToTags[label]?.add(tag)
- }
-
- for ((label, discreteAccesses) in labelsToDiscreteAccesses.entries) {
- val tags = labelsToTags[label]?.toList() ?: listOf()
-
- appPermissionDiscreteAccessWithLabels.add(
- AppPermissionDiscreteAccessesWithLabel(
- appPermissionId,
- label,
- tags,
- discreteAccesses.sortedBy { -1 * it.accessTimeMs }
- )
- )
+ fun getPackageLabel(packageName: String, user: UserHandle): String {
+ if (packageLabelCache.containsKey(packageName)) {
+ return requireNotNull(packageLabelCache[packageName])
}
-
- return appPermissionDiscreteAccessWithLabels
+ val packageLabel = packageRepository.getPackageLabel(packageName, user)
+ packageLabelCache[packageName] = packageLabel
+ return packageLabel
}
/**
- * Clusters [DiscreteAccess]es represented by a [AppPermissionDiscreteAccessesWithLabel] into
- * smaller groups to form a list of [AppPermissionDiscreteAccessCluster] instances.
- *
- * [DiscreteAccess]es which have accesses sufficiently close together in time will be places in
- * the same cluster.
+ * Returns the icon for the provided package name and user, by first searching the cache
+ * otherwise retrieving it from the app's [android.content.pm.ApplicationInfo].
*/
- private fun createAccessClusters(
- appPermAccesses: AppPermissionDiscreteAccessesWithLabel,
- ): List<AppPermissionDiscreteAccessCluster> {
- val clusters = mutableListOf<AppPermissionDiscreteAccessCluster>()
- val currentDiscreteAccesses = mutableListOf<DiscreteAccess>()
- // Iterate entries in asc order based on access timestamp.
- for (index in appPermAccesses.discreteAccesses.size - 1 downTo 0) {
- val discreteAccess = appPermAccesses.discreteAccesses[index]
- if (currentDiscreteAccesses.isEmpty()) {
- currentDiscreteAccesses.add(discreteAccess)
- } else if (!canAccessBeAddedToCluster(discreteAccess, currentDiscreteAccesses)) {
- clusters.add(
- AppPermissionDiscreteAccessCluster(
- appPermAccesses.appPermissionId,
- appPermAccesses.attributionLabel,
- appPermAccesses.attributionTags,
- currentDiscreteAccesses.toMutableList(),
- if (isOpClusteredByItself(discreteAccess.opName)) discreteAccess.opName
- else null
- )
- )
- currentDiscreteAccesses.clear()
- currentDiscreteAccesses.add(discreteAccess)
- } else {
- currentDiscreteAccesses.add(discreteAccess)
- }
+ fun getBadgedPackageIcon(packageName: String, userHandle: UserHandle): Drawable? {
+ val packageNameWithUser: Pair<String, UserHandle> = Pair(packageName, userHandle)
+ if (packageIconCache.containsKey(packageNameWithUser)) {
+ return requireNotNull(packageIconCache[packageNameWithUser])
}
-
- if (currentDiscreteAccesses.isNotEmpty()) {
- val opName = currentDiscreteAccesses.last().opName
- clusters.add(
- AppPermissionDiscreteAccessCluster(
- appPermAccesses.appPermissionId,
- appPermAccesses.attributionLabel,
- appPermAccesses.attributionTags,
- currentDiscreteAccesses.toMutableList(),
- if (isOpClusteredByItself(opName)) opName else null
- )
- )
+ val packageIcon = packageRepository.getBadgedPackageIcon(packageName, userHandle)
+ if (packageIcon != null) {
+ packageIconCache[packageNameWithUser] = packageIcon
}
- return clusters
- }
- /**
- * Returns whether the provided [DiscreteAccess] occurred close enough to those in the clustered
- * list that it can be added to the cluster.
- */
- private fun canAccessBeAddedToCluster(
- currentAccess: DiscreteAccess,
- clusteredAccesses: List<DiscreteAccess>
- ): Boolean {
- val clusterOp = clusteredAccesses.last().opName
- if (
- (isOpClusteredByItself(currentAccess.opName) || isOpClusteredByItself(clusterOp)) &&
- currentAccess.opName != clusteredAccesses.last().opName
- ) {
- return false
- }
- val currentAccessMinute = currentAccess.accessTimeMs / ONE_MINUTE_MS
- val prevMostRecentAccessMillis =
- clusteredAccesses.maxOf { discreteAccess ->
- if (discreteAccess.accessDurationMs > 0)
- discreteAccess.accessTimeMs + discreteAccess.accessDurationMs - ONE_MINUTE_MS
- else discreteAccess.accessTimeMs
- }
- val prevMostRecentAccessMinute = prevMostRecentAccessMillis / ONE_MINUTE_MS
- return (currentAccessMinute - prevMostRecentAccessMinute) <= CLUSTER_SPACING_MINUTES
+ return packageIcon
}
- /**
- * Determine if an op should be in its own cluster and hence display as an individual entry in
- * the privacy timeline
- */
- private fun isOpClusteredByItself(opName: String): Boolean {
- if (isLocationByPassEnabled()) {
- return opName == AppOpsManager.OPSTR_EMERGENCY_LOCATION
+ private fun getDurationSummary(durationMs: Long): String? {
+ // Only show the duration summary if it is at least (CLUSTER_SPACING_MINUTES + 1) minutes.
+ // Displaying a time that is shorter than the cluster granularity
+ // (CLUSTER_SPACING_MINUTES) will not convey useful information.
+ if (durationMs >= TimeUnit.MINUTES.toMillis(CLUSTER_SPACING_MINUTES + 1)) {
+ return getDurationUsedStr(application, durationMs)
}
- return false
+ return null
}
- /**
- * Composes all UI information from a [AppPermissionDiscreteAccessCluster] into a
- * [AppPermissionAccessUiInfo].
- */
- private fun AppPermissionDiscreteAccessCluster.buildAppPermissionAccessUiInfo():
- AppPermissionAccessUiInfo {
- val context = application
- // The end minute is exclusive here in terms of access, i.e. [1..5) as the private data
- // was not accessed at minute 5, it helps calculate the duration correctly.
- val accessEndTimeMillis =
- discreteAccesses.maxOf { appOpEvent ->
- if (appOpEvent.accessDurationMs > 0)
- appOpEvent.accessTimeMs + appOpEvent.accessDurationMs
- else appOpEvent.accessTimeMs + ONE_MINUTE_MS
- }
- val accessStartTimeMillis = discreteAccesses.minOf { it.accessTimeMs }
- val durationMs = accessEndTimeMillis - accessStartTimeMillis
- val durationSummaryLabel =
- if (durationMs >= TimeUnit.MINUTES.toMillis(CLUSTER_SPACING_MINUTES + 1)) {
- getDurationUsedStr(context, durationMs)
- } else null
-
- val proxyLabel = getProxyPackageLabel(this)
- val subAttributionLabel = getSubAttributionLabel(this)
- val showingSubAttribution = !subAttributionLabel.isNullOrEmpty()
- val summary =
- buildUsageSummary(context, subAttributionLabel, proxyLabel, durationSummaryLabel)
- val isEmergencyLocationAccess =
- isLocationByPassEnabled() && clusteredOp == AppOpsManager.OPSTR_EMERGENCY_LOCATION
-
- return AppPermissionAccessUiInfo(
- this.appPermissionId.userHandle,
- this.appPermissionId.packageName,
- getPackageLabel(this.appPermissionId.packageName, this.appPermissionId.userHandle),
- permissionGroup,
- accessStartTimeMillis,
- // Make the end time inclusive i.e. [1..4]
- accessEndTimeMillis - ONE_MINUTE_MS,
- summary,
- showingSubAttribution,
- this.attributionTags.toSet(),
- getBadgedPackageIcon(this.appPermissionId.packageName, this.appPermissionId.userHandle),
- isEmergencyLocationAccess
- )
- }
-
- /** Builds a summary of the permission access. */
private fun buildUsageSummary(
- context: Context,
subAttributionLabel: String?,
proxyPackageLabel: String?,
- durationSummary: String?
+ durationSummary: String?,
): String? {
val subTextStrings: MutableList<String> = mutableListOf()
-
subAttributionLabel?.let { subTextStrings.add(subAttributionLabel) }
proxyPackageLabel?.let { subTextStrings.add(it) }
durationSummary?.let { subTextStrings.add(it) }
return when (subTextStrings.size) {
3 ->
- context.getString(
+ application.getString(
R.string.history_preference_subtext_3,
subTextStrings[0],
subTextStrings[1],
- subTextStrings[2]
+ subTextStrings[2],
)
2 ->
- context.getString(
+ application.getString(
R.string.history_preference_subtext_2,
subTextStrings[0],
- subTextStrings[1]
+ subTextStrings[1],
)
1 -> subTextStrings[0]
else -> null
}
}
- /** Returns whether app subattribution should be shown. */
- private fun shouldShowSubAttributionForApp(lightPackageInfo: LightPackageInfo?): Boolean {
- return lightPackageInfo != null &&
- shouldShowSubattributionInPermissionsDashboard() &&
- SubattributionUtils.isSubattributionSupported(lightPackageInfo)
- }
-
- /** Returns the proxied package label if the permission access was proxied. */
- private fun getProxyPackageLabel(accessCluster: AppPermissionDiscreteAccessCluster): String? =
- accessCluster.discreteAccesses
- .firstOrNull { it.proxy?.packageName != null }
- ?.let {
- getPackageLabel(
- it.proxy!!.packageName!!,
- UserHandle.getUserHandleForUid(it.proxy.uid)
- )
- }
-
- /** Returns the attribution label for the permission access, if any. */
- private fun getSubAttributionLabel(accessCluster: AppPermissionDiscreteAccessCluster): String? {
- // Special case for EMERGENCY_LOCATION app op. Show enforced attribution label in the
- // Privacy Dashboard
- if (
- isLocationByPassEnabled() &&
- accessCluster.clusteredOp == AppOpsManager.OPSTR_EMERGENCY_LOCATION
- ) {
- return application.getString(
- R.string.privacy_dashboard_emergency_location_enforced_attribution_label
- )
- }
-
- return if (accessCluster.attributionLabel == Resources.ID_NULL) null
- else {
- val lightPackageInfo = getLightPackageInfo(accessCluster.appPermissionId)
- getSubAttributionLabels(lightPackageInfo)?.get(accessCluster.attributionLabel)
- }
- }
-
- private fun getSubAttributionLabels(lightPackageInfo: LightPackageInfo?): Map<Int, String>? =
- if (lightPackageInfo == null) null
- else SubattributionUtils.getAttributionLabels(application, lightPackageInfo)
-
- private fun getLightPackageInfo(appPermissionId: AppPermissionId) =
- lightPackageInfoLiveDataMap[Pair(appPermissionId.packageName, appPermissionId.userHandle)]
- ?.value
-
- private fun getLightPackageInfo(packageName: String, userHandle: UserHandle) =
- lightPackageInfoLiveDataMap[Pair(packageName, userHandle)]?.value
-
- private fun AllLightHistoricalPackageOpsLiveData.getLightHistoricalPackageOps() =
- this.value?.values
-
- /** Data used to create a preference for an app's permission usage. */
- data class AppPermissionAccessUiInfo(
- val userHandle: UserHandle,
- val packageName: String,
- val packageLabel: String,
- val permissionGroup: String,
- val accessStartTime: Long,
- val accessEndTime: Long,
- val summaryText: CharSequence?,
- val showingAttribution: Boolean,
- val attributionTags: Set<String>,
- val badgedPackageIcon: Drawable?,
- val isEmergencyLocationAccess: Boolean
- )
-
- sealed class PermissionUsageDetailsUiState {
- data object Loading : PermissionUsageDetailsUiState()
-
- data class Success(
- val appPermissionAccessUiInfoList: List<AppPermissionAccessUiInfo>,
- val containsSystemAppUsage: Boolean,
- val showSystem: Boolean,
- val show7Days: Boolean,
- ) : PermissionUsageDetailsUiState()
- }
-
- /**
- * Data class representing a cluster of permission accesses close enough together to be
- * displayed as a single access in the UI.
- */
- private data class AppPermissionDiscreteAccessCluster(
- val appPermissionId: AppPermissionId,
- val attributionLabel: Int,
- val attributionTags: List<String>,
- val discreteAccesses: List<DiscreteAccess>,
- val clusteredOp: String?
- )
-
- /**
- * Data class representing all permission accesses for a particular package, user, permission
- * and attribution label.
- */
- private data class AppPermissionDiscreteAccessesWithLabel(
- val appPermissionId: AppPermissionId,
- val attributionLabel: Int,
- val attributionTags: List<String>,
- val discreteAccesses: List<DiscreteAccess>
- )
-
- /** [LiveData] object for [PermissionUsageDetailsUiState]. */
- private val _permissionUsagesDetailsInfoUiLiveData =
- object :
- SmartUpdateMediatorLiveData<@JvmSuppressWildcards PermissionUsageDetailsUiState>() {
- private val getAppPermGroupUiInfoLiveData = { appPermissionId: AppPermissionId ->
- AppPermGroupUiInfoLiveData[
- Triple(
- appPermissionId.packageName,
- appPermissionId.permissionGroup,
- appPermissionId.userHandle,
- )]
- }
- private val getLightPackageInfoLiveData =
- { packageWithUserHandle: Pair<String, UserHandle> ->
- LightPackageInfoLiveData[packageWithUserHandle]
- }
-
- init {
- addSource(allLightHistoricalPackageOpsLiveData) { update() }
- addSource(showSystemLiveData) { update() }
- addSource(show7DaysLiveData) { update() }
- }
-
- override fun onUpdate() {
- if (!allLightHistoricalPackageOpsLiveData.isInitialized) {
- return
- }
-
- val appPermissionIds = mutableSetOf<AppPermissionId>()
- val allPackages: Set<Pair<String, UserHandle>> =
- allLightHistoricalPackageOpsLiveData.value?.keys ?: setOf()
- for (packageWithUserHandle: Pair<String, UserHandle> in allPackages) {
- val appPermGroupIds =
- allLightHistoricalPackageOpsLiveData.value
- ?.get(packageWithUserHandle)
- ?.appPermissionDiscreteAccesses
- ?.map { it.appPermissionId }
- ?.toSet() ?: setOf()
-
- appPermissionIds.addAll(appPermGroupIds)
- }
-
- setSourcesToDifference(
- appPermissionIds,
- appPermGroupUiInfoLiveDataList,
- getAppPermGroupUiInfoLiveData
- ) {
- update()
- }
- setSourcesToDifference(
- allPackages,
- lightPackageInfoLiveDataMap,
- getLightPackageInfoLiveData
- ) {
- update()
- }
-
- if (appPermGroupUiInfoLiveDataList.any { it.value.isStale }) {
- return
- }
-
- if (lightPackageInfoLiveDataMap.any { it.value.isStale }) {
- return
- }
-
- value = buildPermissionUsageDetailsUiInfo()
- }
- }
-
- override fun getPermissionUsagesDetailsInfoUiLiveData():
- LiveData<PermissionUsageDetailsUiState> = _permissionUsagesDetailsInfoUiLiveData
-
- override fun getShowSystem(): Boolean = showSystemLiveData.value ?: false
-
- override fun getShow7Days(): Boolean = show7DaysLiveData.value ?: false
-
/** Companion object for [PermissionUsageDetailsViewModel]. */
companion object {
- const val ONE_HOUR_MS = 3_600_000
const val ONE_MINUTE_MS = 60_000
const val CLUSTER_SPACING_MINUTES: Long = 1L
- private const val TELECOM_PACKAGE = "com.android.server.telecom"
val TIME_7_DAYS_DURATION: Long = DAYS.toMillis(7)
val TIME_24_HOURS_DURATION: Long = DAYS.toMillis(1)
internal const val SHOULD_SHOW_SYSTEM_KEY = "showSystem"
@@ -694,7 +305,7 @@ class PermissionUsageDetailsViewModel(
listOf(
Manifest.permission_group.CAMERA,
Manifest.permission_group.LOCATION,
- Manifest.permission_group.MICROPHONE
+ Manifest.permission_group.MICROPHONE,
)
.flatMap { group -> PermissionMapping.getPlatformPermissionNamesOfGroup(group) }
.mapNotNull { permName -> AppOpsManager.permissionToOp(permName) }
@@ -719,7 +330,7 @@ class PermissionUsageDetailsViewModel(
accessStartTime: Long,
accessEndTime: Long,
showingAttribution: Boolean,
- attributionTags: Set<String>
+ attributionTags: Set<String>,
): Intent {
return getManagePermissionUsageIntent(
context,
@@ -728,7 +339,7 @@ class PermissionUsageDetailsViewModel(
accessStartTime,
accessEndTime,
showingAttribution,
- attributionTags
+ attributionTags,
) ?: getDefaultManageAppPermissionsIntent(packageName, userHandle)
}
@@ -736,6 +347,7 @@ class PermissionUsageDetailsViewModel(
* Gets an [Intent.ACTION_MANAGE_PERMISSION_USAGE] intent, or null if attribution shouldn't
* be shown or the intent can't be handled.
*/
+ @Suppress("DEPRECATION")
private fun getManagePermissionUsageIntent(
context: Context,
packageName: String,
@@ -743,7 +355,7 @@ class PermissionUsageDetailsViewModel(
accessStartTime: Long,
accessEndTime: Long,
showingAttribution: Boolean,
- attributionTags: Set<String>
+ attributionTags: Set<String>,
): Intent? {
if (
!showingAttribution ||
@@ -768,13 +380,13 @@ class PermissionUsageDetailsViewModel(
val resolveInfo =
context.packageManager.resolveActivity(
intent,
- PackageManager.ResolveInfoFlags.of(0)
+ PackageManager.ResolveInfoFlags.of(0),
)
if (
resolveInfo?.activityInfo == null ||
!Objects.equals(
resolveInfo.activityInfo.permission,
- Manifest.permission.START_VIEW_PERMISSION_USAGE
+ Manifest.permission.START_VIEW_PERMISSION_USAGE,
)
) {
return null
@@ -785,8 +397,9 @@ class PermissionUsageDetailsViewModel(
private fun getDefaultManageAppPermissionsIntent(
packageName: String,
- userHandle: UserHandle
+ userHandle: UserHandle,
): Intent {
+ @Suppress("DEPRECATION")
return Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS).apply {
putExtra(Intent.EXTRA_USER, userHandle)
putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
@@ -795,29 +408,65 @@ class PermissionUsageDetailsViewModel(
private fun isLocationByPassEnabled(): Boolean =
SdkLevel.isAtLeastV() && Flags.locationBypassPrivacyDashboardEnabled()
+
+ fun create(
+ app: Application,
+ handle: SavedStateHandle,
+ permissionGroup: String,
+ ): PermissionUsageDetailsViewModel {
+ val permissionRepository = PermissionRepository.getInstance(app)
+ val packageRepository = PackageRepository.getInstance(app)
+ val appOpRepository = AppOpRepository.getInstance(app, permissionRepository)
+ val roleRepository = RoleRepository.getInstance(app)
+ val userRepository = UserRepository.getInstance(app)
+ val useCase =
+ GetPermissionGroupUsageDetailsUseCase(
+ permissionGroup,
+ packageRepository,
+ permissionRepository,
+ appOpRepository,
+ roleRepository,
+ userRepository,
+ )
+ return PermissionUsageDetailsViewModel(app, useCase, handle, permissionGroup)
+ }
+ }
+
+ /** Data used to create a preference for an app's permission usage. */
+ data class AppPermissionAccessUiInfo(
+ val userHandle: UserHandle,
+ val packageName: String,
+ val packageLabel: String,
+ val permissionGroup: String,
+ val accessStartTime: Long,
+ val accessEndTime: Long,
+ val summaryText: CharSequence?,
+ val showingAttribution: Boolean,
+ val attributionTags: Set<String>,
+ val badgedPackageIcon: Drawable?,
+ val isEmergencyLocationAccess: Boolean,
+ )
+
+ sealed class PermissionUsageDetailsUiState {
+ data object Loading : PermissionUsageDetailsUiState()
+
+ data class Success(
+ val appPermissionAccessUiInfoList: List<AppPermissionAccessUiInfo>,
+ val containsSystemAppUsage: Boolean,
+ val showSystem: Boolean,
+ val show7Days: Boolean,
+ ) : PermissionUsageDetailsUiState()
}
/** Factory for [PermissionUsageDetailsViewModel]. */
@RequiresApi(Build.VERSION_CODES.S)
class PermissionUsageDetailsViewModelFactory(
val app: Application,
- owner: SavedStateRegistryOwner,
private val permissionGroup: String,
- ) : AbstractSavedStateViewModelFactory(owner, Bundle()) {
- override fun <T : ViewModel> create(
- key: String,
- modelClass: Class<T>,
- handle: SavedStateHandle,
- ): T {
+ ) : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
@Suppress("UNCHECKED_CAST")
- return if (
- com.android.permission.flags.Flags.livedataRefactorPermissionTimelineEnabled()
- ) {
- Log.d("PermissionTimeline", "timeline refactor flag enabled..")
- PermissionUsageDetailsViewModelV2.create(app, handle, permissionGroup) as T
- } else {
- PermissionUsageDetailsViewModel(app, handle, permissionGroup) as T
- }
+ return create(app, extras.createSavedStateHandle(), permissionGroup) as T
}
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModelV2.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModelV2.kt
deleted file mode 100644
index 312a7ee20..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModelV2.kt
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.ui.model.v31
-
-import android.app.AppOpsManager.OPSTR_EMERGENCY_LOCATION
-import android.app.Application
-import android.graphics.drawable.Drawable
-import android.os.UserHandle
-import androidx.annotation.VisibleForTesting
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.SavedStateHandle
-import androidx.lifecycle.asLiveData
-import androidx.lifecycle.viewModelScope
-import com.android.permissioncontroller.DeviceUtils
-import com.android.permissioncontroller.R
-import com.android.permissioncontroller.appops.data.repository.v31.AppOpRepository
-import com.android.permissioncontroller.permission.data.repository.v31.PermissionRepository
-import com.android.permissioncontroller.permission.domain.model.v31.PermissionTimelineUsageModel
-import com.android.permissioncontroller.permission.domain.model.v31.PermissionTimelineUsageModelWrapper
-import com.android.permissioncontroller.permission.domain.usecase.v31.GetPermissionGroupUsageDetailsUseCase
-import com.android.permissioncontroller.permission.domain.usecase.v31.isLocationByPassEnabled
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.Companion.SHOULD_SHOW_7_DAYS_KEY
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.Companion.SHOULD_SHOW_SYSTEM_KEY
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.Companion.TIME_24_HOURS_DURATION
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.Companion.TIME_7_DAYS_DURATION
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState
-import com.android.permissioncontroller.pm.data.repository.v31.PackageRepository
-import com.android.permissioncontroller.role.data.repository.v31.RoleRepository
-import com.android.permissioncontroller.user.data.repository.v31.UserRepository
-import java.time.Instant
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.stateIn
-
-class PermissionUsageDetailsViewModelV2(
- app: Application,
- private val getPermissionUsageDetailsUseCase: GetPermissionGroupUsageDetailsUseCase,
- private val state: SavedStateHandle,
- private val permissionGroup: String,
- scope: CoroutineScope? = null,
- private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default,
- private val packageRepository: PackageRepository = PackageRepository.getInstance(app)
-) : BasePermissionUsageDetailsViewModel(app) {
- private val coroutineScope = scope ?: viewModelScope
- private val context = app
-
- private val showSystemFlow = MutableStateFlow(state[SHOULD_SHOW_SYSTEM_KEY] ?: false)
- private val show7DaysFlow = MutableStateFlow(state[SHOULD_SHOW_7_DAYS_KEY] ?: false)
-
- private val permissionTimelineUsagesFlow:
- StateFlow<PermissionTimelineUsageModelWrapper> by lazy {
- getPermissionUsageDetailsUseCase(coroutineScope)
- .flowOn(defaultDispatcher)
- .stateIn(
- coroutineScope,
- SharingStarted.WhileSubscribed(5000),
- PermissionTimelineUsageModelWrapper.Loading
- )
- }
-
- @VisibleForTesting
- val permissionUsageDetailsUiStateFlow: Flow<PermissionUsageDetailsUiState> by lazy {
- combine(permissionTimelineUsagesFlow, showSystemFlow, show7DaysFlow) {
- permissionTimelineUsages,
- showSystem,
- show7Days ->
- permissionTimelineUsages.buildPermissionUsageDetailsUiInfo(showSystem, show7Days)
- }
- .flowOn(defaultDispatcher)
- }
-
- override fun getPermissionUsagesDetailsInfoUiLiveData():
- LiveData<PermissionUsageDetailsUiState> {
- return permissionUsageDetailsUiStateFlow.asLiveData(
- context = coroutineScope.coroutineContext
- )
- }
-
- private fun PermissionTimelineUsageModelWrapper.buildPermissionUsageDetailsUiInfo(
- showSystem: Boolean,
- show7Days: Boolean
- ): PermissionUsageDetailsUiState {
- if (this is PermissionTimelineUsageModelWrapper.Loading) {
- return PermissionUsageDetailsUiState.Loading
- }
- val timelineUsageModels =
- (this as PermissionTimelineUsageModelWrapper.Success).timelineUsageModels
- val startTime =
- (System.currentTimeMillis() - getUsageDuration(show7Days)).coerceAtLeast(
- Instant.EPOCH.toEpochMilli()
- )
-
- val permissionTimelineUsageModels =
- timelineUsageModels.filter { it.accessEndMillis > startTime }
- val containsSystemUsages = permissionTimelineUsageModels.any { !it.isUserSensitive }
- val result =
- permissionTimelineUsageModels
- .filter { showSystem || it.isUserSensitive }
- .map { clusterOps ->
- val durationSummaryLabel =
- if (clusterOps.durationMillis > 0) {
- getDurationSummary(clusterOps.durationMillis)
- } else {
- null
- }
- val proxyLabel = getProxyPackageLabel(clusterOps)
- val isEmergencyLocationAccess =
- isLocationByPassEnabled() &&
- clusterOps.opNames.any { it == OPSTR_EMERGENCY_LOCATION }
- val subAttributionLabel =
- if (isEmergencyLocationAccess) {
- emergencyLocationAttributionLabel
- } else {
- clusterOps.attributionLabel
- }
- val showingSubAttribution = !subAttributionLabel.isNullOrEmpty()
- val summary =
- buildUsageSummary(subAttributionLabel, proxyLabel, durationSummaryLabel)
- PermissionUsageDetailsViewModel.AppPermissionAccessUiInfo(
- UserHandle.of(clusterOps.userId),
- clusterOps.packageName,
- getPackageLabel(clusterOps.packageName, UserHandle.of(clusterOps.userId)),
- permissionGroup,
- clusterOps.accessStartMillis,
- clusterOps.accessEndMillis,
- summary,
- showingSubAttribution,
- clusterOps.attributionTags ?: emptySet(),
- getBadgedPackageIcon(
- clusterOps.packageName,
- UserHandle.of(clusterOps.userId)
- ),
- isEmergencyLocationAccess
- )
- }
- .sortedBy { -1 * it.accessStartTime }
- return PermissionUsageDetailsUiState.Success(
- result,
- containsSystemUsages,
- showSystem,
- show7Days
- )
- }
-
- private val emergencyLocationAttributionLabel: String by lazy {
- context.getString(R.string.privacy_dashboard_emergency_location_enforced_attribution_label)
- }
-
- override fun getShowSystem(): Boolean = showSystemFlow.value
-
- override val showSystemLiveData =
- showSystemFlow.asLiveData(context = coroutineScope.coroutineContext)
-
- override fun getShow7Days(): Boolean = show7DaysFlow.value
-
- private fun getUsageDuration(show7Days: Boolean): Long {
- return if (show7Days && DeviceUtils.isHandheld()) {
- TIME_7_DAYS_DURATION
- } else {
- TIME_24_HOURS_DURATION
- }
- }
-
- private fun getProxyPackageLabel(accessCluster: PermissionTimelineUsageModel): String? =
- accessCluster.proxyPackageName?.let { proxyPackageName ->
- if (accessCluster.proxyUserId != null) {
- getPackageLabel(proxyPackageName, UserHandle.of(accessCluster.proxyUserId))
- } else null
- }
-
- override fun updateShowSystemAppsToggle(showSystem: Boolean) {
- if (showSystem != state[SHOULD_SHOW_SYSTEM_KEY]) {
- state[SHOULD_SHOW_SYSTEM_KEY] = showSystem
- }
- showSystemFlow.compareAndSet(!showSystem, showSystem)
- }
-
- override fun updateShow7DaysToggle(show7Days: Boolean) {
- if (show7Days != state[SHOULD_SHOW_7_DAYS_KEY]) {
- state[SHOULD_SHOW_7_DAYS_KEY] = show7Days
- }
- show7DaysFlow.compareAndSet(!show7Days, show7Days)
- }
-
- // TODO review these methods when old impl is removed, suspend function??
- override fun getPackageLabel(app: Application, packageName: String, user: UserHandle): String {
- return packageRepository.getPackageLabel(packageName, user)
- }
-
- override fun getBadgedPackageIcon(
- app: Application,
- packageName: String,
- user: UserHandle
- ): Drawable? {
- return packageRepository.getBadgedPackageIcon(packageName, user)
- }
-
- companion object {
- fun create(
- app: Application,
- handle: SavedStateHandle,
- permissionGroup: String
- ): PermissionUsageDetailsViewModelV2 {
- val permissionRepository = PermissionRepository.getInstance(app)
- val packageRepository = PackageRepository.getInstance(app)
- val appOpRepository = AppOpRepository.getInstance(app, permissionRepository)
- val roleRepository = RoleRepository.getInstance(app)
- val userRepository = UserRepository.getInstance(app)
- val useCase =
- GetPermissionGroupUsageDetailsUseCase(
- permissionGroup,
- packageRepository,
- permissionRepository,
- appOpRepository,
- roleRepository,
- userRepository
- )
- return PermissionUsageDetailsViewModelV2(app, useCase, handle, permissionGroup)
- }
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java
index c9e9a2eb1..69b57e362 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java
@@ -190,6 +190,7 @@ public class GrantPermissionsWearViewHandler implements GrantPermissionsViewHand
|| mLocationVisibilities[DIALOG_WITH_COARSE_LOCATION_ONLY]) {
mViewModel.getPreciseLocationCheckedLiveData().setValue(false);
}
+ mViewModel.getShowDialog().postValue(true);
}
private void onLocationSwitchChanged(boolean checked) {
@@ -271,6 +272,12 @@ public class GrantPermissionsWearViewHandler implements GrantPermissionsViewHand
WearGrantPermissionsScreenKt.setContent(root,
mViewModel,
+ () -> {
+ if (mResultListener != null) {
+ mResultListener.onPermissionGrantResult(null, null, CANCELED);
+ }
+ return Unit.INSTANCE;
+ },
id -> {
onButtonClicked(id);
return Unit.INSTANCE;
@@ -278,7 +285,8 @@ public class GrantPermissionsWearViewHandler implements GrantPermissionsViewHand
checked -> {
onLocationSwitchChanged(checked);
return Unit.INSTANCE;
- });
+ }
+ );
if (mGroupName != null) {
updateScreen();
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt
index 6af62e01f..54ed66f0c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt
@@ -18,53 +18,45 @@ package com.android.permissioncontroller.permission.ui.wear
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.wear.compose.foundation.SwipeToDismissValue
-import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
-import androidx.wear.compose.material.ChipDefaults
-import androidx.wear.compose.material.MaterialTheme
-import androidx.wear.compose.material.SwipeToDismissBox
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.Scaffold
+import androidx.wear.compose.material3.Dialog
import com.android.permissioncontroller.permission.ui.wear.model.LocationProviderInterceptDialogArgs
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButtonStyle
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionScaffold
@Composable
-fun LocationProviderDialogScreen(args: LocationProviderInterceptDialogArgs?) {
- args?.apply {
- val state = rememberSwipeToDismissBoxState()
- LaunchedEffect(state.currentValue) {
- // If the swipe is complete
- if (state.currentValue == SwipeToDismissValue.Dismissed) {
- onOkButtonClick()
- }
- }
- SwipeToDismissBox(state = state) { isBackground ->
- Scaffold(
+fun LocationProviderDialogScreen(
+ showDialog: Boolean,
+ onDismissRequest: () -> Unit,
+ args: LocationProviderInterceptDialogArgs?,
+) {
+ Dialog(visible = showDialog, onDismissRequest = onDismissRequest) {
+ args?.run {
+ WearPermissionScaffold(
showTimeText = false,
image = iconId,
title = stringResource(titleId),
subtitle = message,
- isLoading = isBackground,
+ isLoading = false,
content = {
item {
- Chip(
+ WearPermissionButton(
label = stringResource(locationSettingsId),
- onClick = onLocationSettingsClick,
modifier = Modifier.fillMaxWidth(),
- textColor = MaterialTheme.colors.surface,
- colors = ChipDefaults.primaryChipColors()
+ onClick = onLocationSettingsClick,
+ style = WearPermissionButtonStyle.Primary,
)
}
item {
- Chip(
+ WearPermissionButton(
label = stringResource(okButtonTitleId),
onClick = onOkButtonClick,
modifier = Modifier.fillMaxWidth(),
)
}
- }
+ },
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionFragment.kt
index 959dc9137..15d0fbe5f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionFragment.kt
@@ -58,8 +58,8 @@ import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogA
import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionConfirmDialogViewModel
import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionConfirmDialogViewModelFactory
import com.android.permissioncontroller.permission.ui.wear.model.ConfirmDialogArgs
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionTheme
import com.android.settingslib.RestrictedLockUtils
/**
@@ -98,7 +98,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
userHandle: UserHandle?,
caller: String?,
sessionId: Long,
- grantCategory: String?
+ grantCategory: String?,
): Bundle {
val arguments = Bundle()
arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName)
@@ -118,7 +118,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
val activity = requireActivity()
val packageName =
@@ -145,7 +145,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
packageName,
permGroupName,
user,
- sessionId
+ sessionId,
)
val viewModel = ViewModelProvider(this, factory).get(AppPermissionViewModel::class.java)
confirmDialogViewModel =
@@ -182,14 +182,14 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
this,
this,
param.request,
- param.buttonClickAction
+ param.buttonClickAction,
)
} else {
showConfirmDialog(
ChangeRequest.GRANT_ALL_FILE_ACCESS,
R.string.special_file_access_dialog,
-1,
- false
+ false,
)
}
setResult(param.result, permGroupName)
@@ -207,7 +207,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
this,
this,
ChangeRequest.GRANT_BOTH,
- APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW,
)
} else {
viewModel.onDenyAnyWay(args.changeRequest, args.buttonPressed, args.oneTime)
@@ -225,7 +225,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
this,
this,
args.changeRequest!!,
- args.buttonClicked!!
+ args.buttonClicked!!,
)
confirmDialogViewModel.showAdvancedConfirmDialogLiveData.value = false
}
@@ -250,7 +250,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
onConfirmDialogCancelButtonClick,
onAdvancedConfirmDialogOkButtonClick,
onAdvancedConfirmDialogCancelButtonClick,
- onDisabledAllowButtonTap
+ onDisabledAllowButtonTap,
)
}
}
@@ -261,14 +261,14 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
changeRequest: ChangeRequest,
@StringRes messageId: Int,
buttonPressed: Int,
- oneTime: Boolean
+ oneTime: Boolean,
) {
confirmDialogViewModel.confirmDialogArgs =
ConfirmDialogArgs(
messageId = messageId,
changeRequest = changeRequest,
buttonPressed = buttonPressed,
- oneTime = oneTime
+ oneTime = oneTime,
)
confirmDialogViewModel.showConfirmDialogLiveData.value = true
}
@@ -284,7 +284,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
Intent()
.putExtra(
ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED,
- permGroupName
+ permGroupName,
)
.putExtra(ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT, result)
requireActivity().setResult(Activity.RESULT_OK, intent)
@@ -298,7 +298,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
ChangeRequest.GRANT_FOREGROUND,
APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW,
GRANTED_ALWAYS,
- false
+ false,
)
ButtonType.ALLOW_ALWAYS ->
GrantedStateChangeParam(
@@ -306,7 +306,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
ChangeRequest.GRANT_BOTH,
APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_ALWAYS,
GRANTED_ALWAYS,
- true
+ true,
)
ButtonType.ALLOW_FOREGROUND ->
GrantedStateChangeParam(
@@ -314,7 +314,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
ChangeRequest.GRANT_FOREGROUND_ONLY,
APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ALLOW_FOREGROUND,
GRANTED_FOREGROUND_ONLY,
- true
+ true,
)
ButtonType.ASK ->
GrantedStateChangeParam(
@@ -322,7 +322,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
ChangeRequest.REVOKE_BOTH,
APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME,
DENIED,
- false
+ false,
)
ButtonType.DENY ->
GrantedStateChangeParam(
@@ -330,7 +330,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
ChangeRequest.REVOKE_BOTH,
APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY,
DENIED_DO_NOT_ASK_AGAIN,
- false
+ false,
)
ButtonType.DENY_FOREGROUND ->
GrantedStateChangeParam(
@@ -338,7 +338,7 @@ class WearAppPermissionFragment : Fragment(), ConfirmDialogShowingFragment {
ChangeRequest.REVOKE_FOREGROUND,
APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND,
DENIED_DO_NOT_ASK_AGAIN,
- false
+ false,
)
else -> throw RuntimeException("Wrong button type: $buttonType")
}
@@ -349,5 +349,5 @@ data class GrantedStateChangeParam(
val request: ChangeRequest,
val buttonClickAction: Int,
val result: Int,
- val requiresCustomStorageBehavior: Boolean
+ val requiresCustomStorageBehavior: Boolean,
)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsFragment.kt
index 552876b7d..06d14cc14 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsFragment.kt
@@ -48,7 +48,7 @@ import com.android.permissioncontroller.permission.ui.wear.model.WearAppPermissi
import com.android.permissioncontroller.permission.ui.wear.model.WearAppPermissionUsagesViewModelFactory
import com.android.permissioncontroller.permission.ui.wear.model.WearLocationProviderInterceptDialogViewModel
import com.android.permissioncontroller.permission.ui.wear.model.WearLocationProviderInterceptDialogViewModelFactory
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionTheme
import java.time.Instant
import java.util.concurrent.TimeUnit
@@ -63,7 +63,7 @@ class WearAppPermissionGroupsFragment : Fragment(), PermissionsUsagesChangeCallb
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
val packageName = arguments?.getString(Intent.EXTRA_PACKAGE_NAME) ?: ""
val user =
@@ -93,7 +93,7 @@ class WearAppPermissionGroupsFragment : Fragment(), PermissionsUsagesChangeCallb
val viewModel =
ViewModelProvider(
owner = this,
- factory = AppPermissionGroupsViewModelFactory(packageName, user, sessionId)
+ factory = AppPermissionGroupsViewModelFactory(packageName, user, sessionId),
)[AppPermissionGroupsViewModel::class.java]
wearViewModel =
@@ -103,13 +103,13 @@ class WearAppPermissionGroupsFragment : Fragment(), PermissionsUsagesChangeCallb
val revokeDialogViewModel =
ViewModelProvider(
owner = this,
- factory = AppPermissionGroupsRevokeDialogViewModelFactory()
+ factory = AppPermissionGroupsRevokeDialogViewModelFactory(),
)[AppPermissionGroupsRevokeDialogViewModel::class.java]
val locationProviderInterceptDialogViewModel =
ViewModelProvider(
owner = this,
- factory = WearLocationProviderInterceptDialogViewModelFactory()
+ factory = WearLocationProviderInterceptDialogViewModelFactory(),
)[WearLocationProviderInterceptDialogViewModel::class.java]
val context = requireContext()
@@ -125,7 +125,7 @@ class WearAppPermissionGroupsFragment : Fragment(), PermissionsUsagesChangeCallb
maxOf(
System.currentTimeMillis() -
TimeUnit.DAYS.toMillis(aggregateDataFilterBeginDays),
- Instant.EPOCH.toEpochMilli()
+ Instant.EPOCH.toEpochMilli(),
)
permissionUsages.load(
null,
@@ -137,7 +137,7 @@ class WearAppPermissionGroupsFragment : Fragment(), PermissionsUsagesChangeCallb
false,
false,
this,
- false
+ false,
)
}
helper =
@@ -151,7 +151,7 @@ class WearAppPermissionGroupsFragment : Fragment(), PermissionsUsagesChangeCallb
viewModel = viewModel,
wearViewModel = wearViewModel,
revokeDialogViewModel = revokeDialogViewModel,
- locationProviderInterceptDialogViewModel = locationProviderInterceptDialogViewModel
+ locationProviderInterceptDialogViewModel = locationProviderInterceptDialogViewModel,
)
return ComposeView(activity).apply {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsHelper.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsHelper.kt
index 078eefe3b..2933d6fda 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsHelper.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsHelper.kt
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.permission.ui.wear
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PermissionInfo
+import android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP
import android.os.Build
import android.os.UserHandle
import android.util.ArraySet
@@ -319,6 +320,10 @@ class WearAppPermissionGroupsHelper(
) {
// Redirect to location controller extra package settings.
LocationUtils.startLocationControllerExtraPackageSettings(context, user)
+ } else if (permGroupName.equals(HEALTH_PERMISSION_GROUP)
+ && android.permission.flags.Flags.replaceBodySensorPermissionEnabled()) {
+ // Redirect to Health&Fitness UI
+ Utils.navigateToAppHealthConnectSettings(fragment.requireContext(), packageName, user)
} else {
val args =
WearAppPermissionFragment.createArgs(
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt
index ba37205a6..e375910ae 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt
@@ -24,17 +24,20 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
-import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
import com.android.permissioncontroller.permission.ui.wear.model.RevokeDialogArgs
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.DialogButtonContent
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionConfirmationDialog
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControl
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControlType
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
@Composable
fun WearAppPermissionGroupsScreen(helper: WearAppPermissionGroupsHelper) {
+ val materialUIVersion = ResourceHelper.materialUIVersionInSettings
val packagePermGroups = helper.viewModel.packagePermGroupsLiveData.observeAsState(null)
val autoRevoke = helper.viewModel.autoRevokeLiveData.observeAsState(null)
val appPermissionUsages = helper.wearViewModel.appPermissionUsages.observeAsState(emptyList())
@@ -43,6 +46,9 @@ fun WearAppPermissionGroupsScreen(helper: WearAppPermissionGroupsHelper) {
helper.locationProviderInterceptDialogViewModel.dialogVisibilityLiveData.observeAsState(
false
)
+ val locationProviderDialogArgs =
+ helper.locationProviderInterceptDialogViewModel.locationProviderInterceptDialogArgs
+ .observeAsState(null)
var isLoading by remember { mutableStateOf(true) }
@@ -50,17 +56,18 @@ fun WearAppPermissionGroupsScreen(helper: WearAppPermissionGroupsHelper) {
WearAppPermissionGroupsContent(
isLoading,
helper.getPermissionGroupChipParams(appPermissionUsages.value),
- helper.getAutoRevokeChipParam(autoRevoke.value)
+ helper.getAutoRevokeChipParam(autoRevoke.value),
)
RevokeDialog(
+ materialUIVersion = materialUIVersion,
showDialog = showRevokeDialog.value,
- args = helper.revokeDialogViewModel.revokeDialogArgs
+ args = helper.revokeDialogViewModel.revokeDialogArgs,
+ )
+ LocationProviderDialogScreen(
+ showDialog = showLocationProviderDialog.value,
+ onDismissRequest = { helper.locationProviderInterceptDialogViewModel.dismissDialog() },
+ args = locationProviderDialogArgs.value,
)
- if (showLocationProviderDialog.value) {
- LocationProviderDialogScreen(
- helper.locationProviderInterceptDialogViewModel.locationProviderInterceptDialogArgs
- )
- }
}
if (isLoading && !packagePermGroups.value.isNullOrEmpty()) {
@@ -72,30 +79,32 @@ fun WearAppPermissionGroupsScreen(helper: WearAppPermissionGroupsHelper) {
internal fun WearAppPermissionGroupsContent(
isLoading: Boolean,
permissionGroupChipParams: List<PermissionGroupChipParam>,
- autoRevokeChipParam: AutoRevokeChipParam?
+ autoRevokeChipParam: AutoRevokeChipParam?,
) {
ScrollableScreen(title = stringResource(R.string.app_permissions), isLoading = isLoading) {
if (permissionGroupChipParams.isEmpty()) {
- item { Chip(label = stringResource(R.string.no_permissions), onClick = {}) }
+ item {
+ WearPermissionButton(label = stringResource(R.string.no_permissions), onClick = {})
+ }
} else {
for (info in permissionGroupChipParams) {
item {
if (info.checked != null) {
- ToggleChip(
- checked = info.checked,
+ WearPermissionToggleControl(
+ toggleControl = WearPermissionToggleControlType.Switch,
label = info.label,
+ checked = info.checked,
enabled = info.enabled,
- toggleControl = ToggleChipToggleControl.Switch,
- onCheckedChanged = info.onCheckedChanged
+ onCheckedChanged = info.onCheckedChanged,
)
} else {
- Chip(
+ WearPermissionButton(
label = info.label,
labelMaxLines = Integer.MAX_VALUE,
secondaryLabel = info.summary?.let { info.summary },
secondaryLabelMaxLines = Integer.MAX_VALUE,
enabled = info.enabled,
- onClick = info.onClick
+ onClick = info.onClick,
)
}
}
@@ -103,12 +112,12 @@ internal fun WearAppPermissionGroupsContent(
autoRevokeChipParam?.let {
if (it.visible) {
item {
- ToggleChip(
+ WearPermissionToggleControl(
checked = it.checked,
label = stringResource(it.labelRes),
- labelMaxLine = 3,
- toggleControl = ToggleChipToggleControl.Switch,
- onCheckedChanged = it.onCheckedChanged
+ labelMaxLines = 3,
+ toggleControl = WearPermissionToggleControlType.Switch,
+ onCheckedChanged = it.onCheckedChanged,
)
}
}
@@ -118,14 +127,19 @@ internal fun WearAppPermissionGroupsContent(
}
@Composable
-internal fun RevokeDialog(showDialog: Boolean, args: RevokeDialogArgs?) {
- args?.let {
- AlertDialog(
- showDialog = showDialog,
- message = stringResource(it.messageId),
- onOKButtonClick = it.onOkButtonClick,
- onCancelButtonClick = it.onCancelButtonClick,
- scalingLazyListState = rememberScalingLazyListState()
+internal fun RevokeDialog(
+ materialUIVersion: WearPermissionMaterialUIVersion,
+ showDialog: Boolean,
+ args: RevokeDialogArgs?,
+) {
+
+ args?.run {
+ WearPermissionConfirmationDialog(
+ materialUIVersion = materialUIVersion,
+ show = showDialog,
+ message = stringResource(messageId),
+ positiveButtonContent = DialogButtonContent(onClick = onOkButtonClick),
+ negativeButtonContent = DialogButtonContent(onClick = onCancelButtonClick),
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt
index 202ad49bb..588f5a4da 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt
@@ -24,21 +24,26 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
-import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
import androidx.wear.compose.material.ToggleChipDefaults
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonState
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType
import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs
-import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog
-import com.android.permissioncontroller.permission.ui.wear.elements.ListFooter
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
-import com.android.permissioncontroller.permission.ui.wear.elements.toggleChipDisabledColors
import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionConfirmDialogViewModel
import com.android.permissioncontroller.permission.ui.wear.model.ConfirmDialogArgs
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material2.ListFooter
+import com.android.permissioncontroller.wear.permission.components.material2.ToggleChip
+import com.android.permissioncontroller.wear.permission.components.material2.toggleChipDisabledColors
+import com.android.permissioncontroller.wear.permission.components.material3.DialogButtonContent
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionConfirmationDialog
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControlType
+import com.android.permissioncontroller.wear.permission.components.material3.defaultAlertConfirmIcon
+import com.android.permissioncontroller.wear.permission.components.material3.defaultAlertDismissIcon
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
import com.android.settingslib.RestrictedLockUtils
@Composable
@@ -53,8 +58,9 @@ fun WearAppPermissionScreen(
onConfirmDialogCancelButtonClick: () -> Unit,
onAdvancedConfirmDialogOkButtonClick: (AdvancedConfirmDialogArgs) -> Unit,
onAdvancedConfirmDialogCancelButtonClick: () -> Unit,
- onDisabledAllowButtonClick: () -> Unit
+ onDisabledAllowButtonClick: () -> Unit,
) {
+ val materialUIVersion = ResourceHelper.materialUIVersionInSettings
val buttonState = viewModel.buttonStateLiveData.observeAsState(null)
val detailResIds = viewModel.detailResIdLiveData.observeAsState(null)
val admin = viewModel.showAdminSupportLiveData.observeAsState(null)
@@ -73,19 +79,21 @@ fun WearAppPermissionScreen(
onLocationSwitchChanged,
onGrantedStateChanged,
onFooterClicked,
- onDisabledAllowButtonClick
+ onDisabledAllowButtonClick,
)
ConfirmDialog(
+ materialUIVersion = materialUIVersion,
showDialog = showConfirmDialog.value,
args = confirmDialogViewModel.confirmDialogArgs,
onOkButtonClick = onConfirmDialogOkButtonClick,
- onCancelButtonClick = onConfirmDialogCancelButtonClick
+ onCancelButtonClick = onConfirmDialogCancelButtonClick,
)
AdvancedConfirmDialog(
+ materialUIVersion = materialUIVersion,
showDialog = showAdvancedConfirmDialog.value,
args = confirmDialogViewModel.advancedConfirmDialogArgs,
onOkButtonClick = onAdvancedConfirmDialogOkButtonClick,
- onCancelButtonClick = onAdvancedConfirmDialogCancelButtonClick
+ onCancelButtonClick = onAdvancedConfirmDialogCancelButtonClick,
)
}
if (isLoading && !buttonState.value.isNullOrEmpty()) {
@@ -103,7 +111,7 @@ internal fun WearAppPermissionContent(
onLocationSwitchChanged: (Boolean) -> Unit,
onGrantedStateChanged: (ButtonType, Boolean) -> Unit,
onFooterClicked: (RestrictedLockUtils.EnforcedAdmin) -> Unit,
- onDisabledAllowButtonClick: () -> Unit
+ onDisabledAllowButtonClick: () -> Unit,
) {
ScrollableScreen(title = title, isLoading = isLoading) {
buttonState?.get(ButtonType.LOCATION_ACCURACY)?.let {
@@ -113,9 +121,9 @@ internal fun WearAppPermissionContent(
checked = it.isChecked,
enabled = it.isEnabled,
label = stringResource(R.string.app_permission_location_accuracy),
- toggleControl = ToggleChipToggleControl.Switch,
+ toggleControl = WearPermissionToggleControlType.Switch,
onCheckedChanged = onLocationSwitchChanged,
- labelMaxLine = Integer.MAX_VALUE
+ labelMaxLine = Integer.MAX_VALUE,
)
}
}
@@ -133,7 +141,7 @@ internal fun WearAppPermissionContent(
toggleChipDisabledColors()
},
label = labelsByButton(buttonType),
- toggleControl = ToggleChipToggleControl.Radio,
+ toggleControl = WearPermissionToggleControlType.Radio,
onCheckedChanged = { checked ->
if (it.isEnabled) {
onGrantedStateChanged(buttonType, checked)
@@ -141,7 +149,7 @@ internal fun WearAppPermissionContent(
onDisabledAllowButtonClick()
}
},
- labelMaxLine = Integer.MAX_VALUE
+ labelMaxLine = Integer.MAX_VALUE,
)
}
}
@@ -157,7 +165,7 @@ internal fun WearAppPermissionContent(
{ onFooterClicked(admin) }
} else {
null
- }
+ },
)
}
}
@@ -172,7 +180,7 @@ internal val buttonTypeOrder =
ButtonType.ASK_ONCE,
ButtonType.ASK,
ButtonType.DENY,
- ButtonType.DENY_FOREGROUND
+ ButtonType.DENY_FOREGROUND,
)
@Composable
@@ -191,45 +199,60 @@ internal fun labelsByButton(buttonType: ButtonType) =
@Composable
internal fun ConfirmDialog(
+ materialUIVersion: WearPermissionMaterialUIVersion,
showDialog: Boolean,
args: ConfirmDialogArgs?,
onOkButtonClick: (ConfirmDialogArgs) -> Unit,
- onCancelButtonClick: () -> Unit
+ onCancelButtonClick: () -> Unit,
) {
- args?.let {
- AlertDialog(
- showDialog = showDialog,
- message = stringResource(it.messageId),
- onOKButtonClick = { onOkButtonClick(it) },
- onCancelButtonClick = onCancelButtonClick,
- scalingLazyListState = rememberScalingLazyListState()
+ args?.run {
+ WearPermissionConfirmationDialog(
+ materialUIVersion = materialUIVersion,
+ show = showDialog,
+ message = stringResource(messageId),
+ positiveButtonContent = DialogButtonContent(onClick = { onOkButtonClick(this) }),
+ negativeButtonContent = DialogButtonContent(onClick = { onCancelButtonClick() }),
)
}
}
@Composable
internal fun AdvancedConfirmDialog(
+ materialUIVersion: WearPermissionMaterialUIVersion,
showDialog: Boolean,
args: AdvancedConfirmDialogArgs?,
onOkButtonClick: (AdvancedConfirmDialogArgs) -> Unit,
- onCancelButtonClick: () -> Unit
+ onCancelButtonClick: () -> Unit,
) {
- args?.let {
- AlertDialog(
- showDialog = showDialog,
- title =
- if (it.titleId != 0) {
- stringResource(it.titleId)
- } else {
- ""
- },
- iconRes = it.iconId,
- message = stringResource(it.messageId),
- okButtonContentDescription = stringResource(it.positiveButtonTextId),
- cancelButtonContentDescription = stringResource(it.negativeButtonTextId),
- onOKButtonClick = { onOkButtonClick(it) },
- onCancelButtonClick = onCancelButtonClick,
- scalingLazyListState = rememberScalingLazyListState()
+ args?.run {
+ val title =
+ if (titleId != 0) {
+ stringResource(titleId)
+ } else {
+ ""
+ }
+ val okButtonIconBuilder =
+ WearPermissionIconBuilder.defaultAlertConfirmIcon()
+ .contentDescription(stringResource(positiveButtonTextId))
+ val cancelButtonIconBuilder =
+ WearPermissionIconBuilder.defaultAlertDismissIcon()
+ .contentDescription(stringResource(negativeButtonTextId))
+ WearPermissionConfirmationDialog(
+ materialUIVersion = materialUIVersion,
+ show = showDialog,
+ title = title,
+ iconRes = WearPermissionIconBuilder.builder(iconId),
+ message = stringResource(messageId),
+ positiveButtonContent =
+ DialogButtonContent(
+ icon = okButtonIconBuilder,
+ onClick = { onOkButtonClick(this) },
+ ),
+ negativeButtonContent =
+ DialogButtonContent(
+ icon = cancelButtonIconBuilder,
+ onClick = { onCancelButtonClick() },
+ ),
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationDialogFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationDialogFragment.kt
index e81fd948d..088531a52 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationDialogFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationDialogFragment.kt
@@ -27,7 +27,7 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import com.android.permissioncontroller.ecm.EnhancedConfirmationDialogActivity
import com.android.permissioncontroller.permission.ui.wear.model.WearEnhancedConfirmationViewModel
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionTheme
class WearEnhancedConfirmationDialogFragment : DialogFragment() {
private lateinit var enhancedConfirmationDialogActivity: EnhancedConfirmationDialogActivity
@@ -42,7 +42,7 @@ class WearEnhancedConfirmationDialogFragment : DialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View {
val title =
arguments?.getString(KEY_TITLE) ?: throw RuntimeException("ECM Title can't be null")
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt
index 1c31ec96f..1e1f8ba5a 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt
@@ -33,24 +33,26 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.fragment.app.FragmentActivity
import androidx.wear.compose.foundation.SwipeToDismissValue
-import androidx.wear.compose.foundation.lazy.ScalingLazyListState
import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.CircularProgressIndicator
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.SwipeToDismissBox
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog
-import com.android.permissioncontroller.permission.ui.wear.elements.CheckYourPhoneScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.CheckYourPhoneState
-import com.android.permissioncontroller.permission.ui.wear.elements.CheckYourPhoneState.InProgress
-import com.android.permissioncontroller.permission.ui.wear.elements.CheckYourPhoneState.Success
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.dismiss
-import com.android.permissioncontroller.permission.ui.wear.elements.findActivity
import com.android.permissioncontroller.permission.ui.wear.model.WearEnhancedConfirmationViewModel
import com.android.permissioncontroller.permission.ui.wear.model.WearEnhancedConfirmationViewModel.ScreenState
+import com.android.permissioncontroller.wear.permission.components.CheckYourPhoneScreen
+import com.android.permissioncontroller.wear.permission.components.CheckYourPhoneState
+import com.android.permissioncontroller.wear.permission.components.CheckYourPhoneState.InProgress
+import com.android.permissioncontroller.wear.permission.components.CheckYourPhoneState.Success
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.dismiss
+import com.android.permissioncontroller.wear.permission.components.findActivity
+import com.android.permissioncontroller.wear.permission.components.material2.Chip
+import com.android.permissioncontroller.wear.permission.components.material3.DialogButtonContent
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionConfirmationDialog
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
@Composable
fun WearEnhancedConfirmationScreen(
@@ -58,6 +60,7 @@ fun WearEnhancedConfirmationScreen(
title: String?,
message: CharSequence?,
) {
+ val materialUIVersion = ResourceHelper.materialUIVersionInSettings
var dismissed by remember { mutableStateOf(false) }
val context = LocalContext.current
val ecmScreenState = remember { viewModel.screenState }
@@ -97,7 +100,7 @@ fun WearEnhancedConfirmationScreen(
onClick = { dismiss(activity) },
modifier = Modifier.fillMaxWidth(),
textColor = MaterialTheme.colors.surface,
- colors = ChipDefaults.primaryChipColors()
+ colors = ChipDefaults.primaryChipColors(),
)
}
item {
@@ -107,27 +110,30 @@ fun WearEnhancedConfirmationScreen(
modifier = Modifier.fillMaxWidth(),
)
}
- }
+ },
)
@Composable
fun ShowCheckYourPhoneDialog(state: CheckYourPhoneState) =
CheckYourPhoneScreen(
title = stringResource(id = R.string.wear_check_your_phone_title),
- state = state
+ state = state,
)
@Composable
fun ShowRemoteConnectionErrorDialog() =
- AlertDialog(
+ WearPermissionConfirmationDialog(
+ materialUIVersion = materialUIVersion,
+ show = true,
title = stringResource(R.string.wear_phone_connection_error),
message = stringResource(R.string.wear_phone_connection_should_retry),
- iconRes = R.drawable.ic_error,
- showDialog = true,
- okButtonIcon = R.drawable.ic_refresh,
- onOKButtonClick = { viewModel.openUriOnPhone(context) },
- onCancelButtonClick = { dismiss(activity) },
- scalingLazyListState = ScalingLazyListState(1)
+ iconRes = WearPermissionIconBuilder.builder(R.drawable.ic_error),
+ positiveButtonContent =
+ DialogButtonContent(
+ icon = WearPermissionIconBuilder.builder(R.drawable.ic_refresh),
+ onClick = { viewModel.openUriOnPhone(context) },
+ ),
+ negativeButtonContent = DialogButtonContent(onClick = { dismiss(activity) }),
)
SwipeToDismissBox(state = state) { isBackground ->
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
index 950353f52..3ed58cf4a 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
@@ -22,6 +22,8 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
+import androidx.core.util.size
+import androidx.wear.compose.material3.Dialog
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
@@ -37,17 +39,19 @@ import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.N
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON
import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler.BUTTON_RES_ID_TO_NUM
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
import com.android.permissioncontroller.permission.ui.wear.model.WearGrantPermissionsViewModel
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControl
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControlType
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion.MATERIAL3
@Composable
fun WearGrantPermissionsScreen(
viewModel: WearGrantPermissionsViewModel,
onButtonClicked: (Int) -> Unit,
- onLocationSwitchChanged: (Boolean) -> Unit
+ onLocationSwitchChanged: (Boolean) -> Unit,
) {
val groupMessage = viewModel.groupMessageLiveData.observeAsState("")
val icon = viewModel.iconLiveData.observeAsState(null)
@@ -55,8 +59,9 @@ fun WearGrantPermissionsScreen(
val locationVisibilities = viewModel.locationVisibilitiesLiveData.observeAsState(emptyList())
val preciseLocationChecked = viewModel.preciseLocationCheckedLiveData.observeAsState(false)
val buttonVisibilities = viewModel.buttonVisibilitiesLiveData.observeAsState(emptyList())
-
+ val materialUIVersion = ResourceHelper.materialUIVersionInApp
ScrollableScreen(
+ materialUIVersion = materialUIVersion,
showTimeText = false,
image = icon.value,
title = groupMessage.value,
@@ -69,17 +74,18 @@ fun WearGrantPermissionsScreen(
locationVisibilities.value.getOrElse(DIALOG_WITH_BOTH_LOCATIONS) { false }
) {
item {
- ToggleChip(
+ WearPermissionToggleControl(
checked = preciseLocationChecked.value,
- onCheckedChanged = { onLocationSwitchChanged(it) },
+ onCheckedChanged = onLocationSwitchChanged,
label = stringResource(R.string.app_permission_location_accuracy),
- toggleControl = ToggleChipToggleControl.Switch,
+ toggleControl = WearPermissionToggleControlType.Switch,
modifier = Modifier.fillMaxWidth(),
- labelMaxLine = Integer.MAX_VALUE
+ labelMaxLines = Integer.MAX_VALUE,
+ materialUIVersion = materialUIVersion,
)
}
}
- for (i in 0 until BUTTON_RES_ID_TO_NUM.size()) {
+ for (i in 0 until BUTTON_RES_ID_TO_NUM.size) {
val pos: Int = BUTTON_RES_ID_TO_NUM.valueAt(i)
if (buttonVisibilities.value.size <= pos) {
// initial value of buttonVisibilities is empty
@@ -87,16 +93,17 @@ fun WearGrantPermissionsScreen(
}
if (buttonVisibilities.value[pos]) {
item {
- Chip(
+ WearPermissionButton(
label =
getPrimaryText(
- pos,
- locationVisibilities.value,
- labelsByButton(BUTTON_RES_ID_TO_NUM.valueAt(i))
+ pos = pos,
+ locationVisibilities = locationVisibilities.value,
+ default = labelsByButton(BUTTON_RES_ID_TO_NUM.valueAt(i)),
),
onClick = { onButtonClicked(BUTTON_RES_ID_TO_NUM.keyAt(i)) },
modifier = Modifier.fillMaxWidth(),
- labelMaxLines = Integer.MAX_VALUE
+ labelMaxLines = Integer.MAX_VALUE,
+ materialUIVersion = materialUIVersion,
)
}
}
@@ -107,15 +114,32 @@ fun WearGrantPermissionsScreen(
fun setContent(
composeView: ComposeView,
viewModel: WearGrantPermissionsViewModel,
+ onCancelled: () -> Unit,
onButtonClicked: (Int) -> Unit,
- onLocationSwitchChanged: (Boolean) -> Unit
+ onLocationSwitchChanged: (Boolean) -> Unit,
) {
composeView.setContent {
- WearGrantPermissionsScreen(viewModel, onButtonClicked, onLocationSwitchChanged)
+ if (ResourceHelper.materialUIVersionInApp == MATERIAL3) {
+ AsDialog(viewModel, onCancelled) {
+ WearGrantPermissionsScreen(viewModel, onButtonClicked, onLocationSwitchChanged)
+ }
+ } else {
+ WearGrantPermissionsScreen(viewModel, onButtonClicked, onLocationSwitchChanged)
+ }
}
}
@Composable
+private fun AsDialog(
+ viewModel: WearGrantPermissionsViewModel,
+ onDismissRequest: () -> Unit,
+ content: @Composable () -> Unit,
+) {
+ val showDialog = viewModel.showDialog.observeAsState(false)
+ Dialog(visible = showDialog.value, onDismissRequest = onDismissRequest, content = content)
+}
+
+@Composable
internal fun labelsByButton(grantPermissionsButtonType: Int) =
when (grantPermissionsButtonType) {
ALLOW_BUTTON -> stringResource(R.string.grant_dialog_button_allow)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionScreen.kt
index 1563f6a57..72c3d2054 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionScreen.kt
@@ -25,13 +25,14 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.model.ManageCustomPermissionsViewModel
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
@Composable
fun WearManageCustomPermissionScreen(
viewModel: ManageCustomPermissionsViewModel,
- onPermGroupClick: (String) -> Unit
+ onPermGroupClick: (String) -> Unit,
) {
val permissionGroups = viewModel.uiDataLiveData.observeAsState(emptyMap())
var isLoading by remember { mutableStateOf(true) }
@@ -39,7 +40,7 @@ fun WearManageCustomPermissionScreen(
WearManageCustomPermissionContent(
isLoading,
getPermGroupChipParams(permissionGroups.value),
- onPermGroupClick
+ onPermGroupClick,
)
if (isLoading && permissionGroups.value.isNotEmpty()) {
@@ -51,21 +52,21 @@ fun WearManageCustomPermissionScreen(
internal fun WearManageCustomPermissionContent(
isLoading: Boolean,
permGroupChipParams: List<PermGroupChipParam>,
- onPermGroupClick: (String) -> Unit
+ onPermGroupClick: (String) -> Unit,
) {
ScrollableScreen(
title = stringResource(R.string.additional_permissions),
- isLoading = isLoading
+ isLoading = isLoading,
) {
for (params in permGroupChipParams) {
item {
- Chip(
+ WearPermissionButton(
label = params.label,
labelMaxLines = 3,
- icon = params.icon,
+ iconBuilder = params.icon?.let { WearPermissionIconBuilder.builder(it) },
secondaryLabel = params.secondaryLabel,
secondaryLabelMaxLines = 3,
- onClick = { onPermGroupClick(params.permGroupName) }
+ onClick = { onPermGroupClick(params.permGroupName) },
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionsFragment.kt
index a9e83919f..96da37b9b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionsFragment.kt
@@ -26,13 +26,13 @@ import androidx.lifecycle.ViewModelProvider
import com.android.permissioncontroller.Constants
import com.android.permissioncontroller.permission.ui.handheld.PermissionAppsFragment
import com.android.permissioncontroller.permission.ui.model.ManageCustomPermissionsViewModel
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionTheme
class WearManageCustomPermissionsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
val activity = requireActivity()
val application = activity.getApplication()
@@ -41,14 +41,14 @@ class WearManageCustomPermissionsFragment : Fragment() {
val viewModel =
ViewModelProvider(
this,
- ViewModelProvider.AndroidViewModelFactory.getInstance(application)
+ ViewModelProvider.AndroidViewModelFactory.getInstance(application),
)
.get(ManageCustomPermissionsViewModel::class.java)
val onPermGroupClick: (String) -> Unit = { permGroupName ->
viewModel.showPermissionApps(
this,
- PermissionAppsFragment.createArgs(permGroupName, sessionId)
+ PermissionAppsFragment.createArgs(permGroupName, sessionId),
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt
index bd1946759..2fdf33130 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt
@@ -17,6 +17,7 @@
package com.android.permissioncontroller.permission.ui.wear
import android.graphics.drawable.Drawable
+import android.permission.flags.Flags
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@@ -28,12 +29,13 @@ import androidx.compose.ui.res.stringResource
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo
import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModel
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupIcon
import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
import com.android.permissioncontroller.permission.utils.StringUtils
import com.android.permissioncontroller.permission.utils.Utils
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
import java.text.Collator
@Composable
@@ -41,7 +43,7 @@ fun WearManageStandardPermissionScreen(
viewModel: ManageStandardPermissionsViewModel,
onPermGroupClick: (String) -> Unit,
onCustomPermissionsClick: () -> Unit,
- onAutoRevokedClick: () -> Unit
+ onAutoRevokedClick: () -> Unit,
) {
val permissionGroups = viewModel.uiDataLiveData.observeAsState(emptyMap())
val numCustomPermGroups = viewModel.numCustomPermGroups.observeAsState(0)
@@ -55,7 +57,7 @@ fun WearManageStandardPermissionScreen(
numAutoRevoked.value,
onPermGroupClick,
onCustomPermissionsClick,
- onAutoRevokedClick
+ onAutoRevokedClick,
)
if (isLoading && permissionGroups.value.isNotEmpty()) {
@@ -77,7 +79,13 @@ internal fun getPermGroupChipParams(
}
return permissionGroups
// Removing Health Connect from the list of permissions to fix b/331260850
- .filterNot { Utils.isHealthPermissionGroup(it.key) }
+ .let {
+ if (Flags.replaceBodySensorPermissionEnabled()) {
+ it
+ } else {
+ it.filterNot { Utils.isHealthPermissionGroup(it.key) }
+ }
+ }
.mapNotNull {
val uiInfo = it.value ?: return@mapNotNull null
PermGroupChipParam(
@@ -85,7 +93,7 @@ internal fun getPermGroupChipParams(
label = getPermGroupLabel(context, it.key).toString(),
icon = getPermGroupIcon(context, it.key),
secondaryLabel =
- stringResource(summary, uiInfo.nonSystemGranted, uiInfo.nonSystemTotal)
+ stringResource(summary, uiInfo.nonSystemGranted, uiInfo.nonSystemTotal),
)
}
.sortedWith { lhs, rhs -> collator.compare(lhs.label, rhs.label) }
@@ -100,52 +108,52 @@ internal fun WearManageStandardPermissionContent(
numAutoRevoked: Int,
onPermGroupClick: (String) -> Unit,
onCustomPermissionsClick: () -> Unit,
- onAutoRevokedClick: () -> Unit
+ onAutoRevokedClick: () -> Unit,
) {
ScrollableScreen(
title = stringResource(R.string.app_permission_manager),
- isLoading = isLoading
+ isLoading = isLoading,
) {
for (params in permGroupChipParams) {
item {
- Chip(
+ WearPermissionButton(
label = params.label,
labelMaxLines = 3,
- icon = params.icon,
+ iconBuilder = params.icon?.let { WearPermissionIconBuilder.builder(it) },
secondaryLabel = params.secondaryLabel,
secondaryLabelMaxLines = 3,
- onClick = { onPermGroupClick(params.permGroupName) }
+ onClick = { onPermGroupClick(params.permGroupName) },
)
}
}
if (numCustomPermGroups > 0) {
item {
- Chip(
+ WearPermissionButton(
label = stringResource(R.string.additional_permissions),
labelMaxLines = 3,
- icon = R.drawable.ic_more_horizontal,
+ iconBuilder = WearPermissionIconBuilder.builder(R.drawable.ic_more_horizontal),
secondaryLabel =
StringUtils.getIcuPluralsString(
LocalContext.current,
R.string.additional_permissions_more,
- numCustomPermGroups
+ numCustomPermGroups,
),
secondaryLabelMaxLines = 3,
- onClick = onCustomPermissionsClick
+ onClick = onCustomPermissionsClick,
)
}
}
if (numAutoRevoked > 0) {
item {
- Chip(
+ WearPermissionButton(
label = stringResource(R.string.auto_revoke_permission_notification_title),
labelMaxLines = 3,
- icon = R.drawable.ic_info,
+ iconBuilder = WearPermissionIconBuilder.builder(R.drawable.ic_info),
secondaryLabel = stringResource(R.string.auto_revoke_setting_subtitle),
secondaryLabelMaxLines = 3,
- onClick = onAutoRevokedClick
+ onClick = onAutoRevokedClick,
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionsFragment.kt
index 16ed1f067..d88554562 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionsFragment.kt
@@ -27,13 +27,13 @@ import com.android.permissioncontroller.Constants
import com.android.permissioncontroller.permission.ui.handheld.ManageCustomPermissionsFragment
import com.android.permissioncontroller.permission.ui.handheld.PermissionAppsFragment
import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModel
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionTheme
class WearManageStandardPermissionsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
val activity = requireActivity()
val application = activity.getApplication()
@@ -42,20 +42,20 @@ class WearManageStandardPermissionsFragment : Fragment() {
val viewModel: ManageStandardPermissionsViewModel =
ViewModelProvider(
this,
- ViewModelProvider.AndroidViewModelFactory.getInstance(application)
+ ViewModelProvider.AndroidViewModelFactory.getInstance(application),
)
.get(ManageStandardPermissionsViewModel::class.java)
val onPermGroupClick: (String) -> Unit = { permGroupName ->
viewModel.showPermissionApps(
this,
- PermissionAppsFragment.createArgs(permGroupName, sessionId)
+ PermissionAppsFragment.createArgs(permGroupName, sessionId),
)
}
val onCustomPermGroupClick = {
viewModel.showCustomPermissions(
this,
- ManageCustomPermissionsFragment.createArgs(sessionId)
+ ManageCustomPermissionsFragment.createArgs(sessionId),
)
}
val onAutoRevokeClick = {
@@ -69,7 +69,7 @@ class WearManageStandardPermissionsFragment : Fragment() {
viewModel,
onPermGroupClick,
onCustomPermGroupClick,
- onAutoRevokeClick
+ onAutoRevokeClick,
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt
index 8e779cb8c..6f0d94d02 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt
@@ -19,7 +19,6 @@ package com.android.permissioncontroller.permission.ui.wear
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@@ -28,13 +27,13 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.Text
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.Category
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.ListSubheader
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionListSubHeader
/** Compose the screen associated to a [WearPermissionAppsFragment]. */
@Composable
@@ -46,6 +45,10 @@ fun WearPermissionAppsScreen(helper: WearPermissionAppsHelper) {
helper.locationProviderDialogViewModel.dialogVisibilityLiveData.observeAsState(false)
val appPermissionUsages = helper.wearViewModel.appPermissionUsages.observeAsState(emptyList())
var isLoading by remember { mutableStateOf(true) }
+ val dialogArgs =
+ helper.locationProviderDialogViewModel.locationProviderInterceptDialogArgs.observeAsState(
+ null
+ )
val title = helper.getTitle()
val subTitle = helper.getSubTitle()
@@ -53,21 +56,21 @@ fun WearPermissionAppsScreen(helper: WearPermissionAppsHelper) {
val chipsByCategory =
helper.getChipsByCategory(categorizedApps.value, appPermissionUsages.value)
Box(modifier = Modifier.fillMaxSize()) {
- val dialogArgs = helper.locationProviderDialogViewModel.locationProviderInterceptDialogArgs
- if (showLocationProviderDialog.value && dialogArgs != null) {
- LocationProviderDialogScreen(dialogArgs)
- } else {
- WearPermissionAppsContent(
- chipsByCategory = chipsByCategory,
- showSystem = showSystem.value,
- hasSystemApps = hasSystemApps.value,
- title = title,
- subtitle = subTitle,
- showAlways = showAlways,
- isLoading = isLoading,
- onShowSystemClick = helper.onShowSystemClick
- )
- }
+ WearPermissionAppsContent(
+ chipsByCategory = chipsByCategory,
+ showSystem = showSystem.value,
+ hasSystemApps = hasSystemApps.value,
+ title = title,
+ subtitle = subTitle,
+ showAlways = showAlways,
+ isLoading = isLoading,
+ onShowSystemClick = helper.onShowSystemClick,
+ )
+ LocationProviderDialogScreen(
+ showDialog = showLocationProviderDialog.value,
+ onDismissRequest = { helper.locationProviderDialogViewModel.dismissDialog() },
+ args = dialogArgs.value,
+ )
}
if (isLoading && categorizedApps.value.isNotEmpty()) {
isLoading = false
@@ -84,7 +87,7 @@ internal fun WearPermissionAppsContent(
subtitle: String,
showAlways: Boolean,
isLoading: Boolean,
- onShowSystemClick: (showSystem: Boolean) -> Unit
+ onShowSystemClick: (showSystem: Boolean) -> Unit,
) {
ScrollableScreen(title = title, subtitle = subtitle, isLoading = isLoading) {
val firstItemIndex = categoryOrder.indexOfFirst { !chipsByCategory[it].isNullOrEmpty() }
@@ -94,29 +97,22 @@ internal fun WearPermissionAppsContent(
continue
}
item {
- ListSubheader(
- modifier =
- Modifier.padding(
- top = if (index == firstItemIndex) 0.dp else 12.dp,
- bottom = 4.dp,
- start = 14.dp,
- end = 14.dp
- )
- ) {
+ WearPermissionListSubHeader(isFirstItemInAList = index == firstItemIndex) {
Text(text = stringResource(getCategoryString(category, showAlways)))
}
}
chips.forEach {
item {
- Chip(
+ WearPermissionButton(
label = it.title,
labelMaxLines = Int.MAX_VALUE,
secondaryLabel = it.summary,
secondaryLabelMaxLines = Int.MAX_VALUE,
- icon = it.icon,
+ iconBuilder =
+ it.icon?.let { icon -> WearPermissionIconBuilder.builder(icon) },
enabled = it.enabled,
onClick = { it.onClick() },
- modifier = Modifier.fillMaxWidth()
+ modifier = Modifier.fillMaxWidth(),
)
}
}
@@ -124,7 +120,7 @@ internal fun WearPermissionAppsContent(
if (hasSystemApps) {
item {
- Chip(
+ WearPermissionButton(
label =
if (showSystem) {
stringResource(R.string.menu_hide_system)
@@ -150,6 +146,7 @@ internal fun getCategoryString(category: String, showAlways: Boolean) =
} else {
R.string.allowed_header
}
+
Category.ALLOWED_FOREGROUND.categoryName -> R.string.allowed_foreground_header
Category.ASK.categoryName -> R.string.ask_header
Category.DENIED.categoryName -> R.string.denied_header
@@ -163,5 +160,5 @@ internal val categoryOrder =
Category.ALLOWED.categoryName,
Category.ALLOWED_FOREGROUND.categoryName,
Category.ASK.categoryName,
- Category.DENIED.categoryName
+ Category.DENIED.categoryName,
)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsFragment.kt
index 2ade4863a..2d302c2b4 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsFragment.kt
@@ -29,7 +29,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
-import com.android.permissioncontroller.permission.ui.model.v31.BasePermissionUsageDetailsViewModel
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.PermissionUsageDetailsViewModelFactory
/**
@@ -42,7 +42,7 @@ class WearPermissionUsageDetailsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
val permissionGroup =
arguments?.getString(Intent.EXTRA_PERMISSION_GROUP_NAME)
@@ -56,11 +56,10 @@ class WearPermissionUsageDetailsFragment : Fragment() {
val factory =
PermissionUsageDetailsViewModelFactory(
PermissionControllerApplication.get(),
- this,
- permissionGroup
+ permissionGroup,
)
val viewModel =
- ViewModelProvider(this, factory).get(BasePermissionUsageDetailsViewModel::class.java)
+ ViewModelProvider(this, factory).get(PermissionUsageDetailsViewModel::class.java)
viewModel.updateShowSystemAppsToggle(showSystem)
return ComposeView(requireContext()).apply {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt
index 1259c1ab5..bea10483b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt
@@ -30,22 +30,21 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
-import androidx.wear.compose.material.ChipDefaults
-import androidx.wear.compose.material.MaterialTheme
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.ui.model.v31.BasePermissionUsageDetailsViewModel
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.AppPermissionAccessUiInfo
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButtonStyle
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
@RequiresApi(Build.VERSION_CODES.S)
@Composable
fun WearPermissionUsageDetailsScreen(
permissionGroup: String,
- viewModel: BasePermissionUsageDetailsViewModel
+ viewModel: PermissionUsageDetailsViewModel,
) {
val context = LocalContext.current
val uiData = viewModel.getPermissionUsagesDetailsInfoUiLiveData().observeAsState(null)
@@ -56,7 +55,7 @@ fun WearPermissionUsageDetailsScreen(
val subtitle =
stringResource(
R.string.permission_group_usage_title,
- KotlinUtils.getPermGroupLabel(context, permissionGroup)
+ KotlinUtils.getPermGroupLabel(context, permissionGroup),
)
val hasSystemApps: Boolean =
@@ -80,7 +79,7 @@ fun WearPermissionUsageDetailsScreen(
uiInfo.accessStartTime,
uiInfo.accessEndTime,
uiInfo.showingAttribution,
- uiInfo.attributionTags
+ uiInfo.attributionTags,
)
context.startActivityAsUser(intent, uiInfo.userHandle)
}
@@ -108,7 +107,7 @@ fun WearPermissionUsageDetailsScreen(
onShowSystemClick,
appPermissionAccessUiInfoList,
onChipClick,
- onManagePermissionClick
+ onManagePermissionClick,
)
if (isLoading && uiData.value != null) {
@@ -126,29 +125,30 @@ internal fun WearPermissionUsageDetailsContent(
onShowSystemClick: (Boolean) -> Unit,
appPermissionAccessUiInfoList: List<AppPermissionAccessUiInfo>,
onChipClick: (AppPermissionAccessUiInfo) -> Unit,
- onManagePermissionClick: () -> Unit
+ onManagePermissionClick: () -> Unit,
) {
ScrollableScreen(title = title, subtitle = subtitle, isLoading = isLoading) {
if (appPermissionAccessUiInfoList.isEmpty()) {
- item { Chip(label = stringResource(R.string.no_apps), onClick = {}) }
+ item { WearPermissionButton(label = stringResource(R.string.no_apps), onClick = {}) }
} else {
for (uiInfo in appPermissionAccessUiInfoList) {
item {
- Chip(
+ WearPermissionButton(
label = uiInfo.packageLabel,
labelMaxLines = Int.MAX_VALUE,
secondaryLabel =
DateFormat.getTimeFormat(LocalContext.current)
.format(uiInfo.accessEndTime),
secondaryLabelMaxLines = Int.MAX_VALUE,
- icon = uiInfo.badgedPackageIcon,
- onClick = { onChipClick(uiInfo) }
+ iconBuilder =
+ uiInfo.badgedPackageIcon?.let { WearPermissionIconBuilder.builder(it) },
+ onClick = { onChipClick(uiInfo) },
)
}
}
if (hasSystemApps) {
item {
- Chip(
+ WearPermissionButton(
label =
if (showSystem) {
stringResource(R.string.menu_hide_system)
@@ -162,10 +162,9 @@ internal fun WearPermissionUsageDetailsContent(
}
}
item {
- Chip(
+ WearPermissionButton(
label = stringResource(R.string.manage_permission),
- textColor = MaterialTheme.colors.background,
- colors = ChipDefaults.primaryChipColors(),
+ style = WearPermissionButtonStyle.Primary,
onClick = { onManagePermissionClick() },
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt
index f83d3338d..8631d8b20 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt
@@ -17,6 +17,7 @@
package com.android.permissioncontroller.permission.ui.wear
import android.os.Build
+import android.permission.flags.Flags
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
@@ -32,17 +33,15 @@ import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.handheld.v31.PermissionUsageControlPreference
import com.android.permissioncontroller.permission.ui.viewmodel.v31.PermissionUsageViewModel
import com.android.permissioncontroller.permission.ui.viewmodel.v31.PermissionUsagesUiState
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
import com.android.permissioncontroller.permission.utils.Utils
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
import java.text.Collator
@RequiresApi(Build.VERSION_CODES.S)
@Composable
-fun WearPermissionUsageScreen(
- sessionId: Long,
- viewModel: PermissionUsageViewModel,
-) {
+fun WearPermissionUsageScreen(sessionId: Long, viewModel: PermissionUsageViewModel) {
val context = LocalContext.current
val permissionUsagesUiData = viewModel.permissionUsagesUiLiveData.observeAsState(null)
val showSystem = viewModel.showSystemAppsLiveData.observeAsState(false)
@@ -72,7 +71,13 @@ fun WearPermissionUsageScreen(
val permissionGroupPreferences =
permissionGroupWithUsageCountsEntries
// Removing Health Connect from the list of permissions to fix b/331260850
- .filterNot { Utils.isHealthPermissionGroup(it.key) }
+ .let {
+ if (Flags.replaceBodySensorPermissionEnabled()) {
+ it
+ } else {
+ it.filterNot { Utils.isHealthPermissionGroup(it.key) }
+ }
+ }
.map {
PermissionUsageControlPreference(
context,
@@ -97,7 +102,7 @@ fun WearPermissionUsageScreen(
hasSystemApps,
showSystem.value,
onShowSystemClick,
- permissionGroupPreferences
+ permissionGroupPreferences,
)
if (isLoading && isDataLoaded) {
@@ -111,31 +116,34 @@ internal fun WearPermissionUsageContent(
hasSystemApps: Boolean,
showSystem: Boolean,
onShowSystemClick: (Boolean) -> Unit,
- permissionGroupPreferences: List<PermissionUsageControlPreference>
+ permissionGroupPreferences: List<PermissionUsageControlPreference>,
) {
ScrollableScreen(
title = stringResource(R.string.permission_usage_title),
- isLoading = isLoading
+ isLoading = isLoading,
) {
if (permissionGroupPreferences.isEmpty()) {
- item { Chip(label = stringResource(R.string.no_permissions), onClick = {}) }
+ item {
+ WearPermissionButton(label = stringResource(R.string.no_permissions), onClick = {})
+ }
} else {
for (preference in permissionGroupPreferences) {
item {
- Chip(
+ WearPermissionButton(
label = preference.title.toString(),
labelMaxLines = Int.MAX_VALUE,
secondaryLabel = preference.summary.toString(),
secondaryLabelMaxLines = Int.MAX_VALUE,
- icon = preference.icon,
+ iconBuilder =
+ preference.icon?.let { WearPermissionIconBuilder.builder(it) },
enabled = preference.isEnabled,
- onClick = { preference.performClick() }
+ onClick = { preference.performClick() },
)
}
}
if (hasSystemApps) {
item {
- Chip(
+ WearPermissionButton(
label =
if (showSystem) {
stringResource(R.string.menu_hide_system)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsFragment.kt
index 64acfdd96..d454cc566 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsFragment.kt
@@ -41,14 +41,14 @@ import com.android.permissioncontroller.permission.ui.model.UnusedAppsViewModel.
import com.android.permissioncontroller.permission.ui.model.UnusedAppsViewModelFactory
import com.android.permissioncontroller.permission.ui.wear.model.WearUnusedAppsViewModel
import com.android.permissioncontroller.permission.ui.wear.model.WearUnusedAppsViewModel.UnusedAppChip
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionTheme
import com.android.settingslib.utils.applications.AppUtils
import java.text.Collator
/**
* This is a condensed version of
- * [com.android.permissioncontroller.permission.ui.UnusedAppsFragment.kt], tailored for Wear.
+ * [com.android.permissioncontroller.permission.ui.UnusedAppsFragment], tailored for Wear.
*
* A fragment displaying all applications that are unused as well as the option to remove them and
* to open them.
@@ -62,7 +62,7 @@ class WearUnusedAppsFragment : Fragment() {
private var sessionId: Long = 0L
private var isFirstLoad = false
private var categoryVisibilities: MutableList<Boolean> =
- MutableList(UnusedPeriod.values().size) { false }
+ MutableList(UnusedPeriod.entries.size) { false }
private var unusedAppsMap: MutableMap<UnusedPeriod, MutableMap<String, UnusedAppChip>> =
initUnusedAppsMap()
@@ -87,7 +87,7 @@ class WearUnusedAppsFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
isFirstLoad = true
context = requireContext()
@@ -101,7 +101,7 @@ class WearUnusedAppsFragment : Fragment() {
wearViewModel =
ViewModelProvider(
this,
- ViewModelProvider.AndroidViewModelFactory.getInstance(application)
+ ViewModelProvider.AndroidViewModelFactory.getInstance(application),
)
.get(WearUnusedAppsViewModel::class.java)
viewModel.unusedPackageCategoriesLiveData.observe(
@@ -111,7 +111,7 @@ class WearUnusedAppsFragment : Fragment() {
updatePackages(pkgs)
updateWearViewModel(false)
}
- }
+ },
)
if (!viewModel.unusedPackageCategoriesLiveData.isInitialized) {
@@ -125,7 +125,7 @@ class WearUnusedAppsFragment : Fragment() {
updateWearViewModel(false)
}
},
- SHOW_LOAD_DELAY_MS
+ SHOW_LOAD_DELAY_MS,
)
} else {
updatePackages(viewModel.unusedPackageCategoriesLiveData.value!!)
@@ -192,14 +192,14 @@ class WearUnusedAppsFragment : Fragment() {
getString(
R.string.auto_revoked_app_summary_two,
importantLabel,
- otherLabel
+ otherLabel,
)
}
else ->
getString(
R.string.auto_revoked_app_summary_many,
importantLabel,
- "${revokedPerms.size - 1}"
+ "${revokedPerms.size - 1}",
)
}
@@ -215,9 +215,9 @@ class WearUnusedAppsFragment : Fragment() {
AppUtils.getAppContentDescription(
context,
pkgName,
- user.getIdentifier()
+ user.getIdentifier(),
),
- onChipClicked
+ onChipClicked,
)
unusedAppsMap[period]!!.put(key, chip)
}
@@ -242,7 +242,7 @@ class WearUnusedAppsFragment : Fragment() {
for (period in allPeriods) {
Log.i(
LOG_TAG,
- "sessionId: $sessionId $period unused: " + "${categorizedPackages[period]}"
+ "sessionId: $sessionId $period unused: " + "${categorizedPackages[period]}",
)
for (revokedPackageInfo in categorizedPackages[period]!!) {
for (groupName in revokedPackageInfo.revokedGroups) {
@@ -251,7 +251,7 @@ class WearUnusedAppsFragment : Fragment() {
revokedPackageInfo.packageName,
revokedPackageInfo.user,
groupName,
- isNewlyRevoked
+ isNewlyRevoked,
)
}
}
@@ -292,7 +292,7 @@ class WearUnusedAppsFragment : Fragment() {
private fun compareUnusedApps(
lhs: Pair<String, UnusedAppChip>,
- rhs: Pair<String, UnusedAppChip>
+ rhs: Pair<String, UnusedAppChip>,
): Int {
var result = collator.compare(lhs.second.label, rhs.second.label)
if (result == 0) {
@@ -303,7 +303,7 @@ class WearUnusedAppsFragment : Fragment() {
private fun updateWearViewModel(isLoading: Boolean) {
wearViewModel.loadingLiveData.value = isLoading
- wearViewModel.unusedPeriodCategoryVisibilitiesLiveData.setValue(categoryVisibilities)
+ wearViewModel.unusedPeriodCategoryVisibilitiesLiveData.value = categoryVisibilities
// Need to copy to non mutable maps or compose will not update correctly
val map = mutableMapOf<UnusedPeriod, Map<String, UnusedAppChip>>()
@@ -311,6 +311,6 @@ class WearUnusedAppsFragment : Fragment() {
map.put(period, unusedAppsMap[period]!!.toMap())
}
- wearViewModel.unusedAppChipsLiveData.setValue(map.toMap())
+ wearViewModel.unusedAppChipsLiveData.value = map.toMap()
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt
index 423fa7759..c5eef53f2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt
@@ -25,10 +25,10 @@ import com.android.permissioncontroller.R
import com.android.permissioncontroller.hibernation.isHibernationEnabled
import com.android.permissioncontroller.permission.ui.model.UnusedAppsViewModel.UnusedPeriod
import com.android.permissioncontroller.permission.ui.model.UnusedAppsViewModel.UnusedPeriod.Companion.allPeriods
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.Icon
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
import com.android.permissioncontroller.permission.ui.wear.model.WearUnusedAppsViewModel
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
@Composable
fun WearUnusedAppsScreen(viewModel: WearUnusedAppsViewModel) {
@@ -38,12 +38,11 @@ fun WearUnusedAppsScreen(viewModel: WearUnusedAppsViewModel) {
val infoMsgCategoryVisibility =
viewModel.infoMsgCategoryVisibilityLiveData.observeAsState(false)
val unusedAppChips = viewModel.unusedAppChipsLiveData.observeAsState(mapOf())
-
ScrollableScreen(
showTimeText = true,
title = getScreenTitle(),
isLoading = loading.value,
- subtitle = getSubTitle(!infoMsgCategoryVisibility.value)
+ subtitle = getSubTitle(!infoMsgCategoryVisibility.value),
) {
for (period in allPeriods) {
if (!unusedAppChips.value.containsKey(period)) {
@@ -57,19 +56,22 @@ fun WearUnusedAppsScreen(viewModel: WearUnusedAppsViewModel) {
}
for (unusedAppChip in unusedAppChips.value[period]!!.values) {
item {
- Chip(
+ WearPermissionButton(
label = unusedAppChip.label,
secondaryLabel = unusedAppChip.summary,
- icon = unusedAppChip.icon,
- iconContentDescription = unusedAppChip.contentDescription,
- onClick = unusedAppChip.onClick
+ iconBuilder =
+ unusedAppChip.icon?.let {
+ WearPermissionIconBuilder.builder(it)
+ .contentDescription(unusedAppChip.contentDescription)
+ },
+ onClick = unusedAppChip.onClick,
)
}
}
}
// For info_msg_category
if (infoMsgCategoryVisibility.value) {
- item { Icon(icon = R.drawable.ic_info_outline, contentDescription = null) }
+ item { WearPermissionIconBuilder.builder(R.drawable.ic_info_outline).build() }
if (isHibernationEnabled()) {
item { Text(text = stringResource(R.string.unused_apps_page_summary)) }
} else {
@@ -108,5 +110,5 @@ private fun posByPeriod(period: UnusedPeriod) =
private fun categoryTitleByPeriod(period: UnusedPeriod) =
MessageFormat.format(
stringResource(R.string.last_opened_category_title),
- mapOf("count" to period.months)
+ mapOf("count" to period.months),
)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUtils.kt
index 7e4e939b9..6cdf47cec 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUtils.kt
@@ -22,7 +22,6 @@ import androidx.annotation.IntDef
import com.android.permissioncontroller.R
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
-import java.util.Locale
object WearUtils {
@Retention(AnnotationRetention.SOURCE)
@@ -48,7 +47,7 @@ object WearUtils {
res.getString(
R.string.wear_app_perms_7d_access,
summaryTimestamp.third,
- summaryTimestamp.first
+ summaryTimestamp.first,
)
else -> ""
}
@@ -57,7 +56,7 @@ object WearUtils {
@JvmStatic
private fun getPermissionLastAccessSummaryTimestamp(
lastAccessTime: Long?,
- context: Context
+ context: Context,
): Triple<String, Int, String> {
val midnightToday =
(ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).toEpochSecond() * 1000L)
@@ -78,8 +77,4 @@ object WearUtils {
}
return Triple(lastAccessTimeFormatted, lastAccessType, lastAccessDateFormatted)
}
-
- fun String.capitalize(): String = replaceFirstChar {
- if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
- }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt
deleted file mode 100644
index 1394c56ea..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Button.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.ui.wear.elements
-
-import androidx.annotation.DrawableRes
-import androidx.compose.foundation.layout.size
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import androidx.compose.ui.unit.Dp
-import androidx.wear.compose.material.Button
-import androidx.wear.compose.material.ButtonColors
-import androidx.wear.compose.material.ButtonDefaults
-import androidx.wear.compose.material.ButtonDefaults.DefaultButtonSize
-import androidx.wear.compose.material.ButtonDefaults.DefaultIconSize
-import androidx.wear.compose.material.ButtonDefaults.LargeButtonSize
-import androidx.wear.compose.material.ButtonDefaults.LargeIconSize
-import androidx.wear.compose.material.ButtonDefaults.SmallButtonSize
-import androidx.wear.compose.material.ButtonDefaults.SmallIconSize
-
-/**
- * This component is an alternative to [Button], providing the following:
- * - a convenient way of providing an icon and choosing its size from a range of sizes recommended
- * by the Wear guidelines;
- */
-@Composable
-public fun Button(
- imageVector: ImageVector,
- contentDescription: String,
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
- colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
- buttonSize: ButtonSize = ButtonSize.Default,
- iconRtlMode: IconRtlMode = IconRtlMode.Default,
- enabled: Boolean = true
-) {
- Button(
- icon = imageVector,
- contentDescription = contentDescription,
- onClick = onClick,
- modifier = modifier,
- colors = colors,
- buttonSize = buttonSize,
- iconRtlMode = iconRtlMode,
- enabled = enabled
- )
-}
-
-/**
- * This component is an alternative to [Button], providing the following:
- * - a convenient way of providing an icon and choosing its size from a range of sizes recommended
- * by the Wear guidelines;
- */
-@Composable
-public fun Button(
- @DrawableRes id: Int,
- contentDescription: String,
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
- colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
- buttonSize: ButtonSize = ButtonSize.Default,
- iconRtlMode: IconRtlMode = IconRtlMode.Default,
- enabled: Boolean = true
-) {
- Button(
- icon = id,
- contentDescription = contentDescription,
- onClick = onClick,
- modifier = modifier,
- colors = colors,
- buttonSize = buttonSize,
- iconRtlMode = iconRtlMode,
- enabled = enabled
- )
-}
-
-@Composable
-internal fun Button(
- icon: Any,
- contentDescription: String,
- onClick: () -> Unit,
- modifier: Modifier = Modifier,
- colors: ButtonColors = ButtonDefaults.primaryButtonColors(),
- buttonSize: ButtonSize = ButtonSize.Default,
- iconRtlMode: IconRtlMode = IconRtlMode.Default,
- enabled: Boolean = true
-) {
- Button(
- onClick = onClick,
- modifier = modifier.size(buttonSize.tapTargetSize),
- enabled = enabled,
- colors = colors
- ) {
- val iconModifier = Modifier.size(buttonSize.iconSize).align(Alignment.Center)
-
- Icon(
- icon = icon,
- contentDescription = contentDescription,
- modifier = iconModifier,
- rtlMode = iconRtlMode
- )
- }
-}
-
-public sealed class ButtonSize(public val iconSize: Dp, public val tapTargetSize: Dp) {
- public object Default :
- ButtonSize(iconSize = DefaultIconSize, tapTargetSize = DefaultButtonSize)
-
- public object Large : ButtonSize(iconSize = LargeIconSize, tapTargetSize = LargeButtonSize)
- public object Small : ButtonSize(iconSize = SmallIconSize, tapTargetSize = SmallButtonSize)
-
- /**
- * Custom sizes should follow the
- * [accessibility principles and guidance for touch targets](https://developer.android.com/training/wearables/accessibility#set-minimum).
- */
- public data class Custom(val customIconSize: Dp, val customTapTargetSize: Dp) :
- ButtonSize(iconSize = customIconSize, tapTargetSize = customTapTargetSize)
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/RemoteConnectionProgressIndicator.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/RemoteConnectionProgressIndicator.kt
deleted file mode 100644
index 244e7e3fd..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/RemoteConnectionProgressIndicator.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.ui.wear.elements
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clip
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.CircularProgressIndicator
-import androidx.wear.compose.material.Icon
-import androidx.wear.compose.material.MaterialTheme
-
-@Composable
-fun RemoteConnectionProgressIndicator(iconRes: Int, modifier: Modifier) {
- val indicatorPadding = 8.dp
- val iconSize = 48.dp
- val progressBarStrokeWidth = 4.dp
- Box(
- modifier = modifier.size(iconSize).clip(CircleShape),
- ) {
- CircularProgressIndicator(
- modifier = Modifier.size(iconSize - progressBarStrokeWidth + indicatorPadding),
- strokeWidth = progressBarStrokeWidth,
- )
- Icon(
- painter = painterResource(iconRes),
- contentDescription = null,
- modifier =
- Modifier.align(Alignment.Center)
- .size(iconSize - indicatorPadding - 8.dp)
- .clip(CircleShape),
- )
- }
-}
-
-@Composable
-fun RemoteConnectionSuccess(iconRes: Int, modifier: Modifier) {
- val indicatorPadding = 8.dp
- val iconSize = 48.dp
- val backgroundColor = MaterialTheme.colors.onSurface
- val contentColor = MaterialTheme.colors.surface
- Box(
- modifier = modifier.size(iconSize).clip(CircleShape).background(backgroundColor),
- ) {
- Icon(
- painter = painterResource(iconRes),
- contentDescription = null,
- tint = contentColor,
- modifier =
- Modifier.align(Alignment.Center)
- .size(iconSize - indicatorPadding - 8.dp)
- .clip(CircleShape),
- )
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Haptics.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Haptics.kt
deleted file mode 100644
index 817bf7efe..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Haptics.kt
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput
-
-import android.os.Build
-import android.view.View
-import androidx.compose.foundation.gestures.ScrollableState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalView
-import kotlin.math.abs
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.conflate
-import kotlinx.coroutines.flow.flow
-import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.withContext
-
-// This file is a copy of Haptics.kt from Horologist (go/horologist),
-// remove it once Wear Compose 1.4 is landed (b/325560444).
-
-private const val DEBUG = false
-
-/** Debug logging that can be enabled. */
-private inline fun debugLog(generateMsg: () -> String) {
- if (DEBUG) {
- println("RotaryHaptics: ${generateMsg()}")
- }
-}
-
-/**
- * Throttling events within specified timeframe. Only first and last events will be received. For a
- * flow emitting elements 1 to 30, with a 100ms delay between them:
- * ```
- * val flow = flow {
- * for (i in 1..30) {
- * delay(100)
- * emit(i)
- * }
- * }
- * ```
- *
- * With timeframe=1000 only those integers will be received: 1, 10, 20, 30 .
- */
-internal fun <T> Flow<T>.throttleLatest(timeframe: Long): Flow<T> = flow {
- conflate().collect {
- emit(it)
- delay(timeframe)
- }
-}
-
-/** Handles haptics for rotary usage */
-interface RotaryHapticHandler {
-
- /** Handles haptics when scroll is used */
- fun handleScrollHaptic(scrollDelta: Float)
-
- /** Handles haptics when scroll with snap is used */
- fun handleSnapHaptic(scrollDelta: Float)
-}
-
-/**
- * Default implementation of [RotaryHapticHandler]. It handles haptic feedback based on the
- * [scrollableState], scrolled pixels and [hapticsThresholdPx]. Haptic is not fired in this class,
- * instead it's sent to [hapticsChannel] where it'll performed later.
- *
- * @param scrollableState Haptic performed based on this state
- * @param hapticsChannel Channel to which haptic events will be sent
- * @param hapticsThresholdPx A scroll threshold after which haptic is produced.
- */
-class DefaultRotaryHapticHandler(
- private val scrollableState: ScrollableState,
- private val hapticsChannel: Channel<RotaryHapticsType>,
- private val hapticsThresholdPx: Long = 50,
-) : RotaryHapticHandler {
-
- private var overscrollHapticTriggered = false
- private var currScrollPosition = 0f
- private var prevHapticsPosition = 0f
-
- override fun handleScrollHaptic(scrollDelta: Float) {
- if (
- (scrollDelta > 0 && !scrollableState.canScrollForward) ||
- (scrollDelta < 0 && !scrollableState.canScrollBackward)
- ) {
- if (!overscrollHapticTriggered) {
- trySendHaptic(RotaryHapticsType.ScrollLimit)
- overscrollHapticTriggered = true
- }
- } else {
- overscrollHapticTriggered = false
- currScrollPosition += scrollDelta
- val diff = abs(currScrollPosition - prevHapticsPosition)
-
- if (diff >= hapticsThresholdPx) {
- trySendHaptic(RotaryHapticsType.ScrollTick)
- prevHapticsPosition = currScrollPosition
- }
- }
- }
-
- override fun handleSnapHaptic(scrollDelta: Float) {
- if (
- (scrollDelta > 0 && !scrollableState.canScrollForward) ||
- (scrollDelta < 0 && !scrollableState.canScrollBackward)
- ) {
- if (!overscrollHapticTriggered) {
- trySendHaptic(RotaryHapticsType.ScrollLimit)
- overscrollHapticTriggered = true
- }
- } else {
- overscrollHapticTriggered = false
- trySendHaptic(RotaryHapticsType.ScrollItemFocus)
- }
- }
-
- private fun trySendHaptic(rotaryHapticsType: RotaryHapticsType) {
- // Ok to ignore the ChannelResult because we default to capacity = 2 and DROP_OLDEST
- @Suppress("UNUSED_VARIABLE") val unused = hapticsChannel.trySend(rotaryHapticsType)
- }
-}
-
-/** Interface for Rotary haptic feedback */
-interface RotaryHapticFeedback {
- fun performHapticFeedback(type: RotaryHapticsType)
-}
-
-/** Rotary haptic types */
-@JvmInline
-value class RotaryHapticsType(private val type: Int) {
- companion object {
- /**
- * A scroll ticking haptic. Similar to texture haptic - performed each time when a
- * scrollable content is scrolled by a certain distance
- */
- val ScrollTick: RotaryHapticsType = RotaryHapticsType(1)
-
- /**
- * An item focus (snap) haptic. Performed when a scrollable content is snapped to a specific
- * item.
- */
- val ScrollItemFocus: RotaryHapticsType = RotaryHapticsType(2)
-
- /**
- * A limit(overscroll) haptic. Performed when a list reaches the limit (start or end) and
- * can't scroll further
- */
- val ScrollLimit: RotaryHapticsType = RotaryHapticsType(3)
- }
-}
-
-/** Remember disabled haptics handler */
-@Composable
-fun rememberDisabledHaptic(): RotaryHapticHandler = remember {
- object : RotaryHapticHandler {
-
- override fun handleScrollHaptic(scrollDelta: Float) {
- // Do nothing
- }
-
- override fun handleSnapHaptic(scrollDelta: Float) {
- // Do nothing
- }
- }
-}
-
-/**
- * Remember rotary haptic handler.
- *
- * @param scrollableState A scrollableState, used to determine whether the end of the scrollable was
- * reached or not.
- * @param throttleThresholdMs Throttling events within specified timeframe. Only first and last
- * events will be received. Check [throttleLatest] for more info.
- * @param hapticsThresholdPx A scroll threshold after which haptic is produced.
- * @param hapticsChannel Channel to which haptic events will be sent
- * @param rotaryHaptics Interface for Rotary haptic feedback which performs haptics
- */
-@Composable
-fun rememberRotaryHapticHandler(
- scrollableState: ScrollableState,
- throttleThresholdMs: Long = 30,
- hapticsThresholdPx: Long = 50,
- hapticsChannel: Channel<RotaryHapticsType> = rememberHapticChannel(),
- rotaryHaptics: RotaryHapticFeedback = rememberDefaultRotaryHapticFeedback(),
-): RotaryHapticHandler {
- return remember(scrollableState, hapticsChannel, rotaryHaptics) {
- DefaultRotaryHapticHandler(scrollableState, hapticsChannel, hapticsThresholdPx)
- }
- .apply {
- LaunchedEffect(hapticsChannel) {
- hapticsChannel.receiveAsFlow().throttleLatest(throttleThresholdMs).collect {
- hapticType ->
- // 'withContext' launches performHapticFeedback in a separate thread,
- // as otherwise it produces a visible lag (b/219776664)
- val currentTime = System.currentTimeMillis()
- debugLog { "Haptics started" }
- withContext(Dispatchers.Default) {
- debugLog {
- "Performing haptics, delay: " +
- "${System.currentTimeMillis() - currentTime}"
- }
- rotaryHaptics.performHapticFeedback(hapticType)
- }
- }
- }
- }
-}
-
-@Composable
-private fun rememberHapticChannel() = remember {
- Channel<RotaryHapticsType>(
- capacity = 2,
- onBufferOverflow = BufferOverflow.DROP_OLDEST,
- )
-}
-
-@Composable
-public fun rememberDefaultRotaryHapticFeedback(): RotaryHapticFeedback =
- LocalView.current.let { view -> remember { findDeviceSpecificHapticFeedback(view) } }
-
-internal fun findDeviceSpecificHapticFeedback(view: View): RotaryHapticFeedback =
- if (isSamsungWatch()) {
- SamsungWatchHapticFeedback(view)
- } else {
- DefaultRotaryHapticFeedback(view)
- }
-
-/** Default Rotary implementation for [RotaryHapticFeedback] */
-class DefaultRotaryHapticFeedback(private val view: View) : RotaryHapticFeedback {
-
- override fun performHapticFeedback(
- type: RotaryHapticsType,
- ) {
- when (type) {
- RotaryHapticsType.ScrollItemFocus -> {
- view.performHapticFeedback(SCROLL_ITEM_FOCUS)
- }
- RotaryHapticsType.ScrollTick -> {
- view.performHapticFeedback(SCROLL_TICK)
- }
- RotaryHapticsType.ScrollLimit -> {
- view.performHapticFeedback(SCROLL_LIMIT)
- }
- }
- }
-
- private companion object {
- // Hidden constants from HapticFeedbackConstants
- const val SCROLL_TICK: Int = 18
- const val SCROLL_ITEM_FOCUS: Int = 19
- const val SCROLL_LIMIT: Int = 20
- }
-}
-
-/** Implementation of [RotaryHapticFeedback] for Samsung devices */
-private class SamsungWatchHapticFeedback(private val view: View) : RotaryHapticFeedback {
- override fun performHapticFeedback(
- type: RotaryHapticsType,
- ) {
- when (type) {
- RotaryHapticsType.ScrollItemFocus -> {
- view.performHapticFeedback(102)
- }
- RotaryHapticsType.ScrollTick -> {
- view.performHapticFeedback(102)
- }
- RotaryHapticsType.ScrollLimit -> {
- view.performHapticFeedback(50107)
- }
- }
- }
-}
-
-private fun isSamsungWatch(): Boolean = Build.MANUFACTURER.contains("Samsung", ignoreCase = true)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Rotary.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Rotary.kt
deleted file mode 100644
index 19a6ea671..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Rotary.kt
+++ /dev/null
@@ -1,1232 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput
-
-import android.view.ViewConfiguration
-import androidx.compose.animation.core.AnimationState
-import androidx.compose.animation.core.CubicBezierEasing
-import androidx.compose.animation.core.Easing
-import androidx.compose.animation.core.FastOutSlowInEasing
-import androidx.compose.animation.core.SpringSpec
-import androidx.compose.animation.core.animateTo
-import androidx.compose.animation.core.copy
-import androidx.compose.animation.core.spring
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.MutatePriority
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.gestures.ScrollableDefaults
-import androidx.compose.foundation.gestures.ScrollableState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.input.rotary.RotaryInputModifierNode
-import androidx.compose.ui.input.rotary.RotaryScrollEvent
-import androidx.compose.ui.node.ModifierNodeElement
-import androidx.compose.ui.platform.InspectorInfo
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.debugInspectorInfo
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.util.fastSumBy
-import androidx.compose.ui.util.lerp
-import androidx.wear.compose.foundation.ExperimentalWearFoundationApi
-import androidx.wear.compose.foundation.lazy.ScalingLazyListState
-import androidx.wear.compose.foundation.rememberActiveFocusRequester
-import kotlin.math.abs
-import kotlin.math.absoluteValue
-import kotlin.math.sign
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.async
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.receiveAsFlow
-import kotlinx.coroutines.flow.transformLatest
-import kotlinx.coroutines.launch
-
-// This file is a copy of Rotary.kt from Horologist (go/horologist),
-// remove it once Wear Compose 1.4 is landed (b/325560444).
-
-/**
- * A modifier which connects rotary events with scrollable. This modifier supports scroll with
- * fling.
- *
- * @param scrollableState Scrollable state which will be scrolled while receiving rotary events
- * @param focusRequester Requests the focus for rotary input. By default comes from
- * [rememberActiveFocusRequester], which is used with [HierarchicalFocusCoordinator]
- * @param flingBehavior Logic describing fling behavior. If null fling will not happen.
- * @param rotaryHaptics Class which will handle haptic feedback
- * @param reverseDirection Reverse the direction of scrolling. Should be aligned with Scrollable
- * `reverseDirection` parameter
- */
-@OptIn(ExperimentalWearFoundationApi::class)
-@Suppress("ComposableModifierFactory")
-@Composable
-fun Modifier.rotaryWithScroll(
- scrollableState: ScrollableState,
- focusRequester: FocusRequester = rememberActiveFocusRequester(),
- flingBehavior: FlingBehavior? = ScrollableDefaults.flingBehavior(),
- rotaryHaptics: RotaryHapticHandler = rememberRotaryHapticHandler(scrollableState),
- reverseDirection: Boolean = false,
-): Modifier =
- rotaryHandler(
- rotaryScrollHandler =
- RotaryDefaults.rememberFlingHandler(scrollableState, flingBehavior),
- reverseDirection = reverseDirection,
- rotaryHaptics = rotaryHaptics,
- inspectorInfo =
- debugInspectorInfo {
- name = "rotaryWithFling"
- properties["scrollableState"] = scrollableState
- properties["focusRequester"] = focusRequester
- properties["flingBehavior"] = flingBehavior
- properties["rotaryHaptics"] = rotaryHaptics
- properties["reverseDirection"] = reverseDirection
- },
- )
- .focusRequester(focusRequester)
- .focusable()
-
-/**
- * A modifier which connects rotary events with scrollable. This modifier supports snap.
- *
- * @param focusRequester Requests the focus for rotary input. By default comes from
- * [rememberActiveFocusRequester], which is used with [HierarchicalFocusCoordinator]
- * @param rotaryScrollAdapter A connection between scrollable objects and rotary events
- * @param rotaryHaptics Class which will handle haptic feedback
- * @param reverseDirection Reverse the direction of scrolling. Should be aligned with Scrollable
- * `reverseDirection` parameter
- */
-@OptIn(ExperimentalWearFoundationApi::class)
-@Suppress("ComposableModifierFactory")
-@Composable
-fun Modifier.rotaryWithSnap(
- rotaryScrollAdapter: RotaryScrollAdapter,
- focusRequester: FocusRequester = rememberActiveFocusRequester(),
- snapParameters: SnapParameters = RotaryDefaults.snapParametersDefault,
- rotaryHaptics: RotaryHapticHandler =
- rememberRotaryHapticHandler(rotaryScrollAdapter.scrollableState),
- reverseDirection: Boolean = false,
-): Modifier =
- rotaryHandler(
- rotaryScrollHandler =
- RotaryDefaults.rememberSnapHandler(rotaryScrollAdapter, snapParameters),
- reverseDirection = reverseDirection,
- rotaryHaptics = rotaryHaptics,
- inspectorInfo =
- debugInspectorInfo {
- name = "rotaryWithFling"
- properties["rotaryScrollAdapter"] = rotaryScrollAdapter
- properties["focusRequester"] = focusRequester
- properties["snapParameters"] = snapParameters
- properties["rotaryHaptics"] = rotaryHaptics
- properties["reverseDirection"] = reverseDirection
- },
- )
- .focusRequester(focusRequester)
- .focusable()
-
-/** An extension function for creating [RotaryScrollAdapter] from [ScalingLazyListState] */
-@Composable
-fun ScalingLazyListState.toRotaryScrollAdapter(): RotaryScrollAdapter =
- remember(this) { ScalingLazyColumnRotaryScrollAdapter(this) }
-
-/** An implementation of rotary scroll adapter for [ScalingLazyColumn] */
-class ScalingLazyColumnRotaryScrollAdapter(
- override val scrollableState: ScalingLazyListState,
-) : RotaryScrollAdapter {
-
- /** Calculates an average height of an item by taking an average from visible items height. */
- override fun averageItemSize(): Float {
- val visibleItems = scrollableState.layoutInfo.visibleItemsInfo
- return (visibleItems.fastSumBy { it.unadjustedSize } / visibleItems.size).toFloat()
- }
-
- /** Current (centred) item index */
- override fun currentItemIndex(): Int = scrollableState.centerItemIndex
-
- /** An offset from the item centre */
- override fun currentItemOffset(): Float = scrollableState.centerItemScrollOffset.toFloat()
-
- /** The total count of items in ScalingLazyColumn */
- override fun totalItemsCount(): Int = scrollableState.layoutInfo.totalItemsCount
-}
-
-/** An adapter which connects scrollableState to Rotary */
-interface RotaryScrollAdapter {
-
- /** A scrollable state. Used for performing scroll when Rotary events received */
- val scrollableState: ScrollableState
-
- /** Average size of an item. Used for estimating the scrollable distance */
- fun averageItemSize(): Float
-
- /** A current item index. Used for scrolling */
- fun currentItemIndex(): Int
-
- /** An offset from the centre or the border of the current item. */
- fun currentItemOffset(): Float
-
- /** The total count of items in [scrollableState] */
- fun totalItemsCount(): Int
-}
-
-/** Defaults for rotary modifiers */
-object RotaryDefaults {
-
- /** Returns default [SnapParameters] */
- val snapParametersDefault: SnapParameters =
- SnapParameters(
- snapOffset = 0,
- thresholdDivider = 1.5f,
- resistanceFactor = 3f,
- )
-
- /** Returns whether the input is Low-res (a bezel) or high-res(a crown/rsb). */
- @Composable
- fun isLowResInput(): Boolean =
- LocalContext.current.packageManager.hasSystemFeature(
- "android.hardware.rotaryencoder.lowres"
- )
-
- /**
- * Handles scroll with fling.
- *
- * @param scrollableState Scrollable state which will be scrolled while receiving rotary events
- * @param flingBehavior Logic describing Fling behavior. If null - fling will not happen
- * @param isLowRes Whether the input is Low-res (a bezel) or high-res(a crown/rsb)
- */
- @Composable
- internal fun rememberFlingHandler(
- scrollableState: ScrollableState,
- flingBehavior: FlingBehavior? = null,
- isLowRes: Boolean = isLowResInput(),
- ): RotaryScrollHandler {
- val viewConfiguration = ViewConfiguration.get(LocalContext.current)
-
- return remember(scrollableState, flingBehavior, isLowRes) {
- // Remove unnecessary recompositions by disabling tracking of changes inside of
- // this block. This algorithm properly reads all updated values and
- // don't need recomposition when those values change.
- Snapshot.withoutReadObservation {
- debugLog { "isLowRes : $isLowRes" }
- fun rotaryFlingBehavior() =
- flingBehavior?.run {
- RotaryFlingBehavior(
- scrollableState,
- flingBehavior,
- viewConfiguration,
- flingTimeframe =
- if (isLowRes) lowResFlingTimeframe else highResFlingTimeframe,
- )
- }
-
- fun scrollBehavior() = RotaryScrollBehavior(scrollableState)
-
- if (isLowRes) {
- LowResRotaryScrollHandler(
- rotaryFlingBehaviorFactory = { rotaryFlingBehavior() },
- scrollBehaviorFactory = { scrollBehavior() },
- )
- } else {
- HighResRotaryScrollHandler(
- rotaryFlingBehaviorFactory = { rotaryFlingBehavior() },
- scrollBehaviorFactory = { scrollBehavior() },
- )
- }
- }
- }
- }
-
- /**
- * Handles scroll with snap
- *
- * @param rotaryScrollAdapter A connection between scrollable objects and rotary events
- * @param snapParameters Snap parameters
- */
- @Composable
- internal fun rememberSnapHandler(
- rotaryScrollAdapter: RotaryScrollAdapter,
- snapParameters: SnapParameters = snapParametersDefault,
- isLowRes: Boolean = isLowResInput(),
- ): RotaryScrollHandler {
- return remember(rotaryScrollAdapter, snapParameters) {
- // Remove unnecessary recompositions by disabling tracking of changes inside of
- // this block. This algorithm properly reads all updated values and
- // don't need recomposition when those values change.
- Snapshot.withoutReadObservation {
- debugLog { "isLowRes : $isLowRes" }
- if (isLowRes) {
- LowResSnapHandler(
- snapBehaviourFactory = {
- RotarySnapBehavior(rotaryScrollAdapter, snapParameters)
- },
- )
- } else {
- HighResSnapHandler(
- resistanceFactor = snapParameters.resistanceFactor,
- thresholdBehaviorFactory = {
- ThresholdBehavior(
- rotaryScrollAdapter,
- snapParameters.thresholdDivider,
- )
- },
- snapBehaviourFactory = {
- RotarySnapBehavior(rotaryScrollAdapter, snapParameters)
- },
- scrollBehaviourFactory = {
- RotaryScrollBehavior(rotaryScrollAdapter.scrollableState)
- },
- )
- }
- }
- }
- }
-
- private val lowResFlingTimeframe: Long = 100L
- private val highResFlingTimeframe: Long = 30L
-}
-
-/**
- * Parameters used for snapping
- *
- * @param snapOffset an optional offset to be applied when snapping the item. After the snap the
- * snapped items offset will be [snapOffset].
- */
-class SnapParameters(
- val snapOffset: Int,
- val thresholdDivider: Float,
- val resistanceFactor: Float,
-) {
- /** Returns a snapping offset in [Dp] */
- @Composable
- fun snapOffsetDp(): Dp {
- return with(LocalDensity.current) { snapOffset.toDp() }
- }
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as SnapParameters
-
- if (snapOffset != other.snapOffset) return false
- if (thresholdDivider != other.thresholdDivider) return false
- if (resistanceFactor != other.resistanceFactor) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = snapOffset
- result = 31 * result + thresholdDivider.hashCode()
- result = 31 * result + resistanceFactor.hashCode()
- return result
- }
-}
-
-/** An interface for handling scroll events */
-internal interface RotaryScrollHandler {
- /**
- * Handles scrolling events
- *
- * @param coroutineScope A scope for performing async actions
- * @param event A scrollable event from rotary input, containing scrollable delta and timestamp
- * @param rotaryHaptics
- */
- suspend fun handleScrollEvent(
- coroutineScope: CoroutineScope,
- event: TimestampedDelta,
- rotaryHaptics: RotaryHapticHandler,
- )
-}
-
-/**
- * Class responsible for Fling behaviour with rotary. It tracks and produces the fling when
- * necessary
- */
-internal class RotaryFlingBehavior(
- private val scrollableState: ScrollableState,
- private val flingBehavior: FlingBehavior,
- viewConfiguration: ViewConfiguration,
- private val flingTimeframe: Long,
-) {
-
- // A time range during which the fling is valid.
- // For simplicity it's twice as long as [flingTimeframe]
- private val timeRangeToFling = flingTimeframe * 2
-
- // A default fling factor for making fling slower
- private val flingScaleFactor = 0.7f
-
- private var previousVelocity = 0f
-
- private val rotaryVelocityTracker = RotaryVelocityTracker()
-
- private val minFlingSpeed = viewConfiguration.scaledMinimumFlingVelocity.toFloat()
- private val maxFlingSpeed = viewConfiguration.scaledMaximumFlingVelocity.toFloat()
- private var latestEventTimestamp: Long = 0
-
- private var flingVelocity: Float = 0f
- private var flingTimestamp: Long = 0
-
- /** Starts a new fling tracking session with specified timestamp */
- fun startFlingTracking(timestamp: Long) {
- rotaryVelocityTracker.start(timestamp)
- latestEventTimestamp = timestamp
- previousVelocity = 0f
- }
-
- /** Observing new event within a fling tracking session with new timestamp and delta */
- fun observeEvent(timestamp: Long, delta: Float) {
- rotaryVelocityTracker.move(timestamp, delta)
- latestEventTimestamp = timestamp
- }
-
- /** Performing fling if necessary and calling [beforeFling] lambda before it is triggered */
- suspend fun trackFling(beforeFling: () -> Unit) {
- val currentVelocity = rotaryVelocityTracker.velocity
- debugLog { "currentVelocity: $currentVelocity" }
-
- if (abs(currentVelocity) >= abs(previousVelocity)) {
- flingTimestamp = latestEventTimestamp
- flingVelocity = currentVelocity * flingScaleFactor
- }
- previousVelocity = currentVelocity
-
- // Waiting for a fixed amount of time before checking the fling
- delay(flingTimeframe)
-
- // For making a fling 2 criteria should be met:
- // 1) no more than
- // `rangeToFling` ms should pass between last fling detection
- // and the time of last motion event
- // 2) flingVelocity should exceed the minFlingSpeed
- debugLog {
- "Check fling: flingVelocity: $flingVelocity " +
- "minFlingSpeed: $minFlingSpeed, maxFlingSpeed: $maxFlingSpeed"
- }
- if (
- latestEventTimestamp - flingTimestamp < timeRangeToFling &&
- abs(flingVelocity) > minFlingSpeed
- ) {
- // Stops scrollAnimationCoroutine because a fling will be performed
- beforeFling()
- val velocity = flingVelocity.coerceIn(-maxFlingSpeed, maxFlingSpeed)
- scrollableState.scroll(MutatePriority.UserInput) {
- with(flingBehavior) {
- debugLog { "Flinging with velocity $velocity" }
- performFling(velocity)
- }
- }
- }
- }
-}
-
-/**
- * A rotary event object which contains a [timestamp] of the rotary event and a scrolled [delta].
- */
-internal data class TimestampedDelta(val timestamp: Long, val delta: Float)
-
-/**
- * This class does a smooth animation when the scroll by N pixels is done. This animation works well
- * on Rsb(high-res) and Bezel(low-res) devices.
- */
-internal class RotaryScrollBehavior(
- private val scrollableState: ScrollableState,
-) {
- private var sequentialAnimation = false
- private var scrollAnimation = AnimationState(0f)
- private var prevPosition = 0f
-
- /** Handles scroll event to [targetValue] */
- suspend fun handleEvent(targetValue: Float) {
- scrollableState.scroll(MutatePriority.UserInput) {
- debugLog { "ScrollAnimation value before start: ${scrollAnimation.value}" }
-
- scrollAnimation.animateTo(
- targetValue,
- animationSpec = spring(),
- sequentialAnimation = sequentialAnimation,
- ) {
- val delta = value - prevPosition
- debugLog { "Animated by $delta, value: $value" }
- scrollBy(delta)
- prevPosition = value
- sequentialAnimation = value != this.targetValue
- }
- }
- }
-}
-
-/**
- * A helper class for snapping with rotary. Uses animateScrollToItem method for snapping to the Nth
- * item.
- */
-internal class RotarySnapBehavior(
- private val rotaryScrollAdapter: RotaryScrollAdapter,
- private val snapParameters: SnapParameters,
-) {
- private var snapTarget: Int = rotaryScrollAdapter.currentItemIndex()
- private var sequentialSnap: Boolean = false
-
- private var anim = AnimationState(0f)
- private var expectedDistance = 0f
-
- private val defaultStiffness = 200f
- private var snapTargetUpdated = true
-
- /**
- * Preparing snapping. This method should be called before [snapToTargetItem] is called.
- *
- * Snapping is done for current + [moveForElements] items.
- *
- * If [sequentialSnap] is true, items are summed up together. For example, if
- * [prepareSnapForItems] is called with [moveForElements] = 2, 3, 5 -> then the snapping will
- * happen to current + 10 items
- *
- * If [sequentialSnap] is false, then [moveForElements] are not summed up together.
- */
- fun prepareSnapForItems(moveForElements: Int, sequentialSnap: Boolean) {
- this.sequentialSnap = sequentialSnap
- if (sequentialSnap) {
- snapTarget += moveForElements
- } else {
- snapTarget = rotaryScrollAdapter.currentItemIndex() + moveForElements
- }
- snapTargetUpdated = true
- snapTarget = snapTarget.coerceIn(0 until rotaryScrollAdapter.totalItemsCount())
- }
-
- /** Performs snapping to the closest item. */
- suspend fun snapToClosestItem() {
- // Snapping to the closest item by using performFling method with 0 speed
- rotaryScrollAdapter.scrollableState.scroll(MutatePriority.UserInput) {
- debugLog { "snap to closest item" }
- var prevPosition = 0f
- AnimationState(0f).animateTo(
- targetValue = -rotaryScrollAdapter.currentItemOffset(),
- animationSpec = tween(durationMillis = 100, easing = FastOutSlowInEasing),
- ) {
- val animDelta = value - prevPosition
- scrollBy(animDelta)
- prevPosition = value
- }
- snapTarget = rotaryScrollAdapter.currentItemIndex()
- }
- }
-
- /** Returns true if top edge was reached */
- fun topEdgeReached(): Boolean = snapTarget <= 0
-
- /** Returns true if bottom edge was reached */
- fun bottomEdgeReached(): Boolean = snapTarget >= rotaryScrollAdapter.totalItemsCount() - 1
-
- /** Performs snapping to the specified in [prepareSnapForItems] element */
- suspend fun snapToTargetItem() {
- if (sequentialSnap) {
- anim = anim.copy(0f)
- } else {
- anim = AnimationState(0f)
- }
- rotaryScrollAdapter.scrollableState.scroll(MutatePriority.UserInput) {
- // If snapTargetUpdated is true - then the target was updated so we
- // need to do snap again
- while (snapTargetUpdated) {
- snapTargetUpdated = false
- var latestCenterItem: Int
- var continueFirstScroll = true
- debugLog { "snapTarget $snapTarget" }
- while (continueFirstScroll) {
- latestCenterItem = rotaryScrollAdapter.currentItemIndex()
- anim = anim.copy(0f)
- expectedDistance = expectedDistanceTo(snapTarget, snapParameters.snapOffset)
- debugLog {
- "expectedDistance = $expectedDistance, " +
- "scrollableState.centerItemScrollOffset " +
- "${rotaryScrollAdapter.currentItemOffset()}"
- }
- continueFirstScroll = false
- var prevPosition = 0f
-
- anim.animateTo(
- expectedDistance,
- animationSpec =
- SpringSpec(
- stiffness = defaultStiffness,
- visibilityThreshold = 0.1f,
- ),
- sequentialAnimation = (anim.velocity != 0f),
- ) {
- val animDelta = value - prevPosition
- debugLog {
- "First animation, value:$value, velocity:$velocity, " +
- "animDelta:$animDelta"
- }
-
- // Exit animation if snap target was updated
- if (snapTargetUpdated) cancelAnimation()
-
- scrollBy(animDelta)
- prevPosition = value
-
- if (latestCenterItem != rotaryScrollAdapter.currentItemIndex()) {
- continueFirstScroll = true
- cancelAnimation()
- return@animateTo
- }
-
- debugLog { "centerItemIndex = ${rotaryScrollAdapter.currentItemIndex()}" }
- if (rotaryScrollAdapter.currentItemIndex() == snapTarget) {
- debugLog { "Target is visible. Cancelling first animation" }
- debugLog {
- "scrollableState.centerItemScrollOffset " +
- "${rotaryScrollAdapter.currentItemOffset()}"
- }
- expectedDistance = -rotaryScrollAdapter.currentItemOffset()
- continueFirstScroll = false
- cancelAnimation()
- return@animateTo
- }
- }
- }
- // Exit animation if snap target was updated
- if (snapTargetUpdated) continue
-
- anim = anim.copy(0f)
- var prevPosition = 0f
- anim.animateTo(
- expectedDistance,
- animationSpec =
- SpringSpec(
- stiffness = defaultStiffness,
- visibilityThreshold = 0.1f,
- ),
- sequentialAnimation = (anim.velocity != 0f),
- ) {
- // Exit animation if snap target was updated
- if (snapTargetUpdated) cancelAnimation()
-
- val animDelta = value - prevPosition
- debugLog { "Final animation. velocity:$velocity, animDelta:$animDelta" }
- scrollBy(animDelta)
- prevPosition = value
- }
- }
- }
- }
-
- private fun expectedDistanceTo(index: Int, targetScrollOffset: Int): Float {
- val averageSize = rotaryScrollAdapter.averageItemSize()
- val indexesDiff = index - rotaryScrollAdapter.currentItemIndex()
- debugLog { "Average size $averageSize" }
- return (averageSize * indexesDiff) + targetScrollOffset -
- rotaryScrollAdapter.currentItemOffset()
- }
-}
-
-/**
- * A modifier which handles rotary events. It accepts ScrollHandler as the input - a class where
- * main logic about how scroll should be handled is lying
- */
-internal fun Modifier.rotaryHandler(
- rotaryScrollHandler: RotaryScrollHandler,
- reverseDirection: Boolean,
- rotaryHaptics: RotaryHapticHandler,
- inspectorInfo: InspectorInfo.() -> Unit,
-): Modifier =
- this then
- RotaryHandlerElement(
- rotaryScrollHandler,
- reverseDirection,
- rotaryHaptics,
- inspectorInfo,
- )
-
-/**
- * Batching requests for scrolling events. This function combines all events together (except first)
- * within specified timeframe. Should help with performance on high-res devices.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-internal fun Flow<TimestampedDelta>.batchRequestsWithinTimeframe(
- timeframe: Long
-): Flow<TimestampedDelta> {
- var delta = 0f
- var lastTimestamp = -timeframe
- return if (timeframe == 0L) {
- this
- } else {
- this.transformLatest {
- delta += it.delta
- debugLog { "Batching requests. delta:$delta" }
- if (lastTimestamp + timeframe <= it.timestamp) {
- lastTimestamp = it.timestamp
- debugLog { "No events before, delta= $delta" }
- emit(TimestampedDelta(it.timestamp, delta))
- } else {
- delay(timeframe)
- debugLog { "After delay, delta= $delta" }
- if (delta > 0f) {
- emit(TimestampedDelta(it.timestamp, delta))
- }
- }
- delta = 0f
- }
- }
-}
-
-/**
- * A scroll handler for RSB(high-res) without snapping and with or without fling A list is scrolled
- * by the number of pixels received from the rotary device.
- *
- * This class is a little bit different from LowResScrollHandler class - it has a filtering for
- * events which are coming with wrong sign ( this happens to rsb devices, especially at the end of
- * the scroll)
- *
- * This scroll handler supports fling. It can be set with [RotaryFlingBehavior].
- */
-internal class HighResRotaryScrollHandler(
- private val rotaryFlingBehaviorFactory: () -> RotaryFlingBehavior?,
- private val scrollBehaviorFactory: () -> RotaryScrollBehavior,
- private val hapticsThreshold: Long = 50,
-) : RotaryScrollHandler {
-
- // This constant is specific for high-res devices. Because that input values
- // can sometimes come with different sign, we have to filter them in this threshold
- private val gestureThresholdTime = 200L
- private var scrollJob: Job = CompletableDeferred<Unit>()
- private var flingJob: Job = CompletableDeferred<Unit>()
-
- private var previousScrollEventTime = 0L
- private var rotaryScrollDistance = 0f
-
- private var rotaryFlingBehavior: RotaryFlingBehavior? = rotaryFlingBehaviorFactory()
- private var scrollBehavior: RotaryScrollBehavior = scrollBehaviorFactory()
-
- override suspend fun handleScrollEvent(
- coroutineScope: CoroutineScope,
- event: TimestampedDelta,
- rotaryHaptics: RotaryHapticHandler,
- ) {
- val time = event.timestamp
- val isOppositeScrollValue = isOppositeValueAfterScroll(event.delta)
-
- if (isNewScrollEvent(time)) {
- debugLog { "New scroll event" }
- resetTracking(time)
- rotaryScrollDistance = event.delta
- } else {
- // Due to the physics of Rotary side button, some events might come
- // with an opposite axis value - either at the start or at the end of the motion.
- // We don't want to use these values for fling calculations.
- if (!isOppositeScrollValue) {
- rotaryFlingBehavior?.observeEvent(event.timestamp, event.delta)
- } else {
- debugLog { "Opposite value after scroll :${event.delta}" }
- }
- rotaryScrollDistance += event.delta
- }
-
- scrollJob.cancel()
-
- rotaryHaptics.handleScrollHaptic(event.delta)
- debugLog { "Rotary scroll distance: $rotaryScrollDistance" }
-
- previousScrollEventTime = time
- scrollJob = coroutineScope.async { scrollBehavior.handleEvent(rotaryScrollDistance) }
-
- if (rotaryFlingBehavior != null) {
- flingJob.cancel()
- flingJob =
- coroutineScope.async {
- rotaryFlingBehavior?.trackFling(
- beforeFling = {
- debugLog { "Calling before fling section" }
- scrollJob.cancel()
- scrollBehavior = scrollBehaviorFactory()
- }
- )
- }
- }
- }
-
- private fun isOppositeValueAfterScroll(delta: Float): Boolean =
- rotaryScrollDistance * delta < 0f && (abs(delta) < abs(rotaryScrollDistance))
-
- private fun isNewScrollEvent(timestamp: Long): Boolean {
- val timeDelta = timestamp - previousScrollEventTime
- return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime
- }
-
- private fun resetTracking(timestamp: Long) {
- scrollBehavior = scrollBehaviorFactory()
- rotaryFlingBehavior = rotaryFlingBehaviorFactory()
- rotaryFlingBehavior?.startFlingTracking(timestamp)
- }
-}
-
-/**
- * A scroll handler for Bezel(low-res) without snapping. This scroll handler supports fling. It can
- * be set with RotaryFlingBehavior.
- */
-internal class LowResRotaryScrollHandler(
- private val rotaryFlingBehaviorFactory: () -> RotaryFlingBehavior?,
- private val scrollBehaviorFactory: () -> RotaryScrollBehavior,
-) : RotaryScrollHandler {
-
- private val gestureThresholdTime = 200L
- private var previousScrollEventTime = 0L
- private var rotaryScrollDistance = 0f
-
- private var scrollJob: Job = CompletableDeferred<Unit>()
- private var flingJob: Job = CompletableDeferred<Unit>()
-
- private var rotaryFlingBehavior: RotaryFlingBehavior? = rotaryFlingBehaviorFactory()
- private var scrollBehavior: RotaryScrollBehavior = scrollBehaviorFactory()
-
- override suspend fun handleScrollEvent(
- coroutineScope: CoroutineScope,
- event: TimestampedDelta,
- rotaryHaptics: RotaryHapticHandler,
- ) {
- val time = event.timestamp
-
- if (isNewScrollEvent(time)) {
- resetTracking(time)
- rotaryScrollDistance = event.delta
- } else {
- rotaryFlingBehavior?.observeEvent(event.timestamp, event.delta)
- rotaryScrollDistance += event.delta
- }
-
- scrollJob.cancel()
- flingJob.cancel()
-
- rotaryHaptics.handleScrollHaptic(event.delta)
- debugLog { "Rotary scroll distance: $rotaryScrollDistance" }
-
- previousScrollEventTime = time
- scrollJob = coroutineScope.async { scrollBehavior.handleEvent(rotaryScrollDistance) }
-
- flingJob =
- coroutineScope.async {
- rotaryFlingBehavior?.trackFling(
- beforeFling = {
- debugLog { "Calling before fling section" }
- scrollJob.cancel()
- scrollBehavior = scrollBehaviorFactory()
- },
- )
- }
- }
-
- private fun isNewScrollEvent(timestamp: Long): Boolean {
- val timeDelta = timestamp - previousScrollEventTime
- return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime
- }
-
- private fun resetTracking(timestamp: Long) {
- scrollBehavior = scrollBehaviorFactory()
- debugLog { "Velocity tracker reset" }
- rotaryFlingBehavior = rotaryFlingBehaviorFactory()
- rotaryFlingBehavior?.startFlingTracking(timestamp)
- }
-}
-
-/**
- * A scroll handler for RSB(high-res) with snapping and without fling Snapping happens after a
- * threshold is reached ( set in [RotarySnapBehavior])
- *
- * This scroll handler doesn't support fling.
- */
-internal class HighResSnapHandler(
- private val resistanceFactor: Float,
- private val thresholdBehaviorFactory: () -> ThresholdBehavior,
- private val snapBehaviourFactory: () -> RotarySnapBehavior,
- private val scrollBehaviourFactory: () -> RotaryScrollBehavior,
-) : RotaryScrollHandler {
- private val gestureThresholdTime = 200L
- private val snapDelay = 100L
- private val maxSnapsPerEvent = 2
-
- private var scrollJob: Job = CompletableDeferred<Unit>()
- private var snapJob: Job = CompletableDeferred<Unit>()
-
- private var previousScrollEventTime = 0L
- private var snapAccumulator = 0f
- private var rotaryScrollDistance = 0f
- private var scrollInProgress = false
-
- private var snapBehaviour = snapBehaviourFactory()
- private var scrollBehaviour = scrollBehaviourFactory()
- private var thresholdBehavior = thresholdBehaviorFactory()
-
- private val scrollEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.5f, 1.0f)
-
- override suspend fun handleScrollEvent(
- coroutineScope: CoroutineScope,
- event: TimestampedDelta,
- rotaryHaptics: RotaryHapticHandler,
- ) {
- val time = event.timestamp
-
- if (isNewScrollEvent(time)) {
- debugLog { "New scroll event" }
- resetTracking()
- snapJob.cancel()
- snapBehaviour = snapBehaviourFactory()
- scrollBehaviour = scrollBehaviourFactory()
- thresholdBehavior = thresholdBehaviorFactory()
- thresholdBehavior.startThresholdTracking(time)
- snapAccumulator = 0f
- rotaryScrollDistance = 0f
- }
-
- if (!isOppositeValueAfterScroll(event.delta)) {
- thresholdBehavior.observeEvent(event.timestamp, event.delta)
- } else {
- debugLog { "Opposite value after scroll :${event.delta}" }
- }
-
- thresholdBehavior.applySmoothing()
- val snapThreshold = thresholdBehavior.snapThreshold()
-
- snapAccumulator += event.delta
- if (!snapJob.isActive) {
- val resistanceCoeff =
- 1 - scrollEasing.transform(rotaryScrollDistance.absoluteValue / snapThreshold)
- rotaryScrollDistance += event.delta * resistanceCoeff
- }
-
- debugLog { "Snap accumulator: $snapAccumulator" }
- debugLog { "Rotary scroll distance: $rotaryScrollDistance" }
-
- debugLog { "snapThreshold: $snapThreshold" }
- previousScrollEventTime = time
-
- if (abs(snapAccumulator) > snapThreshold) {
- scrollInProgress = false
- scrollBehaviour = scrollBehaviourFactory()
- scrollJob.cancel()
-
- val snapDistance =
- (snapAccumulator / snapThreshold)
- .toInt()
- .coerceIn(-maxSnapsPerEvent..maxSnapsPerEvent)
- snapAccumulator -= snapThreshold * snapDistance
- val sequentialSnap = snapJob.isActive
-
- debugLog {
- "Snap threshold reached: snapDistance:$snapDistance, " +
- "sequentialSnap: $sequentialSnap, " +
- "snap accumulator remaining: $snapAccumulator"
- }
- if (
- (!snapBehaviour.topEdgeReached() && snapDistance < 0) ||
- (!snapBehaviour.bottomEdgeReached() && snapDistance > 0)
- ) {
- rotaryHaptics.handleSnapHaptic(event.delta)
- }
-
- snapBehaviour.prepareSnapForItems(snapDistance, sequentialSnap)
- if (!snapJob.isActive) {
- snapJob.cancel()
- snapJob =
- coroutineScope.async {
- debugLog { "Snap started" }
- try {
- snapBehaviour.snapToTargetItem()
- } finally {
- debugLog { "Snap called finally" }
- }
- }
- }
- rotaryScrollDistance = 0f
- } else {
- if (!snapJob.isActive) {
- scrollJob.cancel()
- debugLog { "Scrolling for $rotaryScrollDistance/$resistanceFactor px" }
- scrollJob =
- coroutineScope.async {
- scrollBehaviour.handleEvent(rotaryScrollDistance / resistanceFactor)
- }
- delay(snapDelay)
- scrollInProgress = false
- scrollBehaviour = scrollBehaviourFactory()
- rotaryScrollDistance = 0f
- snapAccumulator = 0f
- snapBehaviour.prepareSnapForItems(0, false)
-
- snapJob.cancel()
- snapJob = coroutineScope.async { snapBehaviour.snapToClosestItem() }
- }
- }
- }
-
- private fun isOppositeValueAfterScroll(delta: Float): Boolean =
- sign(rotaryScrollDistance) * sign(delta) == -1f && (abs(delta) < abs(rotaryScrollDistance))
-
- private fun isNewScrollEvent(timestamp: Long): Boolean {
- val timeDelta = timestamp - previousScrollEventTime
- return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime
- }
-
- private fun resetTracking() {
- scrollInProgress = true
- }
-}
-
-/**
- * A scroll handler for RSB(high-res) with snapping and without fling Snapping happens after a
- * threshold is reached ( set in [RotarySnapBehavior])
- *
- * This scroll handler doesn't support fling.
- */
-internal class LowResSnapHandler(
- private val snapBehaviourFactory: () -> RotarySnapBehavior,
-) : RotaryScrollHandler {
- private val gestureThresholdTime = 200L
-
- private var snapJob: Job = CompletableDeferred<Unit>()
-
- private var previousScrollEventTime = 0L
- private var snapAccumulator = 0f
- private var scrollInProgress = false
-
- private var snapBehaviour = snapBehaviourFactory()
-
- override suspend fun handleScrollEvent(
- coroutineScope: CoroutineScope,
- event: TimestampedDelta,
- rotaryHaptics: RotaryHapticHandler,
- ) {
- val time = event.timestamp
-
- if (isNewScrollEvent(time)) {
- debugLog { "New scroll event" }
- resetTracking()
- snapJob.cancel()
- snapBehaviour = snapBehaviourFactory()
- snapAccumulator = 0f
- }
-
- snapAccumulator += event.delta
-
- debugLog { "Snap accumulator: $snapAccumulator" }
-
- previousScrollEventTime = time
-
- if (abs(snapAccumulator) > 1f) {
- scrollInProgress = false
-
- val snapDistance = sign(snapAccumulator).toInt()
- rotaryHaptics.handleSnapHaptic(event.delta)
- val sequentialSnap = snapJob.isActive
- debugLog {
- "Snap threshold reached: snapDistance:$snapDistance, " +
- "sequentialSnap: $sequentialSnap, " +
- "snap accumulator: $snapAccumulator"
- }
-
- snapBehaviour.prepareSnapForItems(snapDistance, sequentialSnap)
- if (!snapJob.isActive) {
- snapJob.cancel()
- snapJob =
- coroutineScope.async {
- debugLog { "Snap started" }
- try {
- snapBehaviour.snapToTargetItem()
- } finally {
- debugLog { "Snap called finally" }
- }
- }
- }
- snapAccumulator = 0f
- }
- }
-
- private fun isNewScrollEvent(timestamp: Long): Boolean {
- val timeDelta = timestamp - previousScrollEventTime
- return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime
- }
-
- private fun resetTracking() {
- scrollInProgress = true
- }
-}
-
-internal class ThresholdBehavior(
- private val rotaryScrollAdapter: RotaryScrollAdapter,
- private val thresholdDivider: Float,
- private val minVelocity: Float = 300f,
- private val maxVelocity: Float = 3000f,
- private val smoothingConstant: Float = 0.4f,
-) {
- private val thresholdDividerEasing: Easing = CubicBezierEasing(0.5f, 0.0f, 0.5f, 1.0f)
-
- private val rotaryVelocityTracker = RotaryVelocityTracker()
-
- private var smoothedVelocity = 0f
-
- fun startThresholdTracking(time: Long) {
- rotaryVelocityTracker.start(time)
- smoothedVelocity = 0f
- }
-
- fun observeEvent(timestamp: Long, delta: Float) {
- rotaryVelocityTracker.move(timestamp, delta)
- }
-
- fun applySmoothing() {
- if (rotaryVelocityTracker.velocity != 0.0f) {
- // smooth the velocity
- smoothedVelocity =
- exponentialSmoothing(
- currentVelocity = rotaryVelocityTracker.velocity.absoluteValue,
- prevVelocity = smoothedVelocity,
- smoothingConstant = smoothingConstant,
- )
- }
- debugLog { "rotaryVelocityTracker velocity: ${rotaryVelocityTracker.velocity}" }
- debugLog { "SmoothedVelocity: $smoothedVelocity" }
- }
-
- fun snapThreshold(): Float {
- val thresholdDividerFraction =
- thresholdDividerEasing.transform(
- inverseLerp(
- minVelocity,
- maxVelocity,
- smoothedVelocity,
- ),
- )
- return rotaryScrollAdapter.averageItemSize() /
- lerp(
- 1f,
- thresholdDivider,
- thresholdDividerFraction,
- )
- }
-
- private fun exponentialSmoothing(
- currentVelocity: Float,
- prevVelocity: Float,
- smoothingConstant: Float,
- ): Float = smoothingConstant * currentVelocity + (1 - smoothingConstant) * prevVelocity
-}
-
-private data class RotaryHandlerElement(
- private val rotaryScrollHandler: RotaryScrollHandler,
- private val reverseDirection: Boolean,
- private val rotaryHaptics: RotaryHapticHandler,
- private val inspectorInfo: InspectorInfo.() -> Unit,
-) : ModifierNodeElement<RotaryInputNode>() {
- override fun create(): RotaryInputNode =
- RotaryInputNode(
- rotaryScrollHandler,
- reverseDirection,
- rotaryHaptics,
- )
-
- override fun update(node: RotaryInputNode) {
- debugLog { "Update launched!" }
- node.rotaryScrollHandler = rotaryScrollHandler
- node.reverseDirection = reverseDirection
- node.rotaryHaptics = rotaryHaptics
- }
-
- override fun InspectorInfo.inspectableProperties() {
- inspectorInfo()
- }
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other == null || this::class != other::class) return false
-
- other as RotaryHandlerElement
-
- if (rotaryScrollHandler != other.rotaryScrollHandler) return false
- if (reverseDirection != other.reverseDirection) return false
- if (rotaryHaptics != other.rotaryHaptics) return false
- if (inspectorInfo != other.inspectorInfo) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = rotaryScrollHandler.hashCode()
- result = 31 * result + reverseDirection.hashCode()
- result = 31 * result + rotaryHaptics.hashCode()
- result = 31 * result + inspectorInfo.hashCode()
- return result
- }
-}
-
-private class RotaryInputNode(
- var rotaryScrollHandler: RotaryScrollHandler,
- var reverseDirection: Boolean,
- var rotaryHaptics: RotaryHapticHandler,
-) : RotaryInputModifierNode, Modifier.Node() {
-
- val channel = Channel<TimestampedDelta>(capacity = Channel.CONFLATED)
- val flow = channel.receiveAsFlow()
-
- override fun onAttach() {
- coroutineScope.launch {
- flow.collectLatest {
- debugLog {
- "Scroll event received: " + "delta:${it.delta}, timestamp:${it.timestamp}"
- }
- rotaryScrollHandler.handleScrollEvent(this, it, rotaryHaptics)
- }
- }
- }
-
- override fun onRotaryScrollEvent(event: RotaryScrollEvent): Boolean = false
-
- override fun onPreRotaryScrollEvent(event: RotaryScrollEvent): Boolean {
- debugLog { "onPreRotaryScrollEvent" }
- channel.trySend(
- TimestampedDelta(
- event.uptimeMillis,
- event.verticalScrollPixels * if (reverseDirection) -1f else 1f,
- ),
- )
- return true
- }
-}
-
-private fun inverseLerp(start: Float, stop: Float, value: Float): Float {
- return ((value - start) / (stop - start)).coerceIn(0f, 1f)
-}
-
-/** Debug logging that can be enabled. */
-private const val DEBUG = false
-
-private inline fun debugLog(generateMsg: () -> String) {
- if (DEBUG) {
- println("RotaryScroll: ${generateMsg()}")
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/RotaryVelocityTracker.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/RotaryVelocityTracker.kt
deleted file mode 100644
index 1719ecef3..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/RotaryVelocityTracker.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput
-
-import androidx.compose.ui.input.pointer.util.VelocityTracker1D
-
-// This file is a copy of RotaryVelocityTracker.kt from Horologist (go/horologist),
-// remove it once Wear Compose 1.4 is landed (b/325560444).
-
-/** A wrapper around VelocityTracker1D to provide support for rotary input. */
-class RotaryVelocityTracker {
- private var velocityTracker: VelocityTracker1D = VelocityTracker1D(true)
-
- /** Retrieve the last computed velocity. */
- val velocity: Float
- get() = velocityTracker.calculateVelocity()
-
- /** Start tracking motion. */
- fun start(currentTime: Long) {
- velocityTracker.resetTracking()
- velocityTracker.addDataPoint(currentTime, 0f)
- }
-
- /** Continue tracking motion as the input rotates. */
- fun move(currentTime: Long, delta: Float) {
- velocityTracker.addDataPoint(currentTime, delta)
- }
-
- /** Stop tracking motion. */
- fun end() {
- velocityTracker.resetTracking()
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearGrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearGrantPermissionsViewModel.kt
index 54a6e7c9f..0b1faf2b2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearGrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearGrantPermissionsViewModel.kt
@@ -43,6 +43,9 @@ class WearGrantPermissionsViewModel : ViewModel() {
/** A livedata which stores the permission group button visibilities. */
val buttonVisibilitiesLiveData = MutableLiveData<List<Boolean>>()
+ /** A livedata to control screen visibility */
+ val showDialog = MutableLiveData<Boolean>()
+
init {
groupNameLiveData.value = ""
iconLiveData.value = null
@@ -51,12 +54,14 @@ class WearGrantPermissionsViewModel : ViewModel() {
locationVisibilitiesLiveData.value = emptyList()
preciseLocationCheckedLiveData.value = false
buttonVisibilitiesLiveData.value = emptyList()
+ showDialog.value = false
}
}
/** Factory for a WearGrantPermissionsViewModel */
class WearGrantPermissionsViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST") return WearGrantPermissionsViewModel() as T
+ @Suppress("UNCHECKED_CAST")
+ return WearGrantPermissionsViewModel() as T
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearLocationProviderInterceptDialogViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearLocationProviderInterceptDialogViewModel.kt
index 009ff952c..ec7647f3c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearLocationProviderInterceptDialogViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearLocationProviderInterceptDialogViewModel.kt
@@ -32,10 +32,14 @@ import com.android.permissioncontroller.permission.utils.Utils
class WearLocationProviderInterceptDialogViewModel : ViewModel() {
private val showDialogLiveData = MutableLiveData<Boolean>()
val dialogVisibilityLiveData: LiveData<Boolean> = showDialogLiveData
- var locationProviderInterceptDialogArgs: LocationProviderInterceptDialogArgs? = null
+ private val _locationProviderInterceptDialogArgs =
+ MutableLiveData<LocationProviderInterceptDialogArgs?>()
+ var locationProviderInterceptDialogArgs: LiveData<LocationProviderInterceptDialogArgs?> =
+ _locationProviderInterceptDialogArgs
init {
showDialogLiveData.value = false
+ _locationProviderInterceptDialogArgs.value = null
}
private fun applicationInfo(context: Context, packageName: String): ApplicationInfo? {
@@ -51,7 +55,7 @@ class WearLocationProviderInterceptDialogViewModel : ViewModel() {
fun showDialog(context: Context, packageName: String) {
val applicationInfo = applicationInfo(context, packageName) ?: return
val appLabel = Utils.getAppLabel(applicationInfo, context)
- locationProviderInterceptDialogArgs =
+ _locationProviderInterceptDialogArgs.value =
LocationProviderInterceptDialogArgs(
iconId = R.drawable.ic_dialog_alert_material,
titleId = android.R.string.dialog_alert_title,
@@ -61,13 +65,13 @@ class WearLocationProviderInterceptDialogViewModel : ViewModel() {
onOkButtonClick = { dismissDialog() },
onLocationSettingsClick = {
context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))
- }
+ },
)
showDialogLiveData.value = true
}
fun dismissDialog() {
- locationProviderInterceptDialogArgs = null
+ _locationProviderInterceptDialogArgs.value = null
showDialogLiveData.value = false
}
}
@@ -75,7 +79,8 @@ class WearLocationProviderInterceptDialogViewModel : ViewModel() {
/** Factory for an AppPermissionGroupsRevokeDialogViewModel */
class WearLocationProviderInterceptDialogViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST") return WearLocationProviderInterceptDialogViewModel() as T
+ @Suppress("UNCHECKED_CAST")
+ return WearLocationProviderInterceptDialogViewModel() as T
}
}
@@ -86,5 +91,5 @@ data class LocationProviderInterceptDialogArgs(
val okButtonTitleId: Int,
val locationSettingsId: Int,
val onOkButtonClick: () -> Unit,
- val onLocationSettingsClick: () -> Unit
+ val onLocationSettingsClick: () -> Unit,
)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt
deleted file mode 100644
index 933cf19f9..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-package com.android.permissioncontroller.permission.ui.wear.theme
-
-import android.content.Context
-import android.os.Build
-import androidx.annotation.RequiresApi
-import androidx.annotation.StringRes
-import androidx.annotation.VisibleForTesting
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.text.font.DeviceFontFamilyName
-import androidx.compose.ui.text.font.Font
-import androidx.compose.ui.text.font.FontFamily
-import androidx.wear.compose.material.Colors
-import androidx.wear.compose.material.MaterialTheme
-import androidx.wear.compose.material.Typography
-import com.android.permissioncontroller.R
-
-/** The Material 3 Theme Wrapper for Supporting RRO. */
-@Composable
-fun WearPermissionTheme(content: @Composable () -> Unit) {
- val context = LocalContext.current
- val colors =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- overlayColors(context)
- .copy(error = MaterialTheme.colors.error, onError = MaterialTheme.colors.onError)
- } else {
- MaterialTheme.colors
- }
- MaterialTheme(colors = colors, typography = deviceDefaultTypography(context), content = content)
-}
-
-/**
- * Creates a dynamic color maps that can be overlaid. 100 - Lightest shade; 0 - Darkest Shade; In
- * wear we only support dark theme for the time being. Thus the fill colors and variants are dark
- * and anything on top is light. We will use this custom redirection until wear compose material
- * supports color scheming.
- *
- * The mapping is best case match on wear material color tokens from
- * /android/clockwork/common/wearable/wearmaterial/color/res/values/color-tokens.xml
- *
- * @param context The context required to get system resource data.
- */
-@RequiresApi(Build.VERSION_CODES.S)
-@VisibleForTesting
-internal fun overlayColors(context: Context): Colors {
- val tonalPalette = dynamicTonalPalette(context)
- return Colors(
- background = Color.Black,
- onBackground = Color.White,
- primary = tonalPalette.primary90,
- primaryVariant = tonalPalette.primary80,
- onPrimary = tonalPalette.primary10,
- secondary = tonalPalette.tertiary90,
- secondaryVariant = tonalPalette.tertiary60,
- onSecondary = tonalPalette.tertiary10,
- surface = tonalPalette.neutral20,
- onSurface = tonalPalette.neutral95,
- onSurfaceVariant = tonalPalette.neutralVariant80,
- )
-}
-
-private fun fontFamily(context: Context, @StringRes id: Int): FontFamily {
- val typefaceName = context.resources.getString(id)
- val font = Font(familyName = DeviceFontFamilyName(typefaceName))
- return FontFamily(font)
-}
-
-/*
- Only customizes font family. The material 3 roles to 2.5 are mapped to the best case matching of
- google3/java/com/google/android/wearable/libraries/compose/theme/GoogleMaterialTheme.kt
-*/
-internal fun deviceDefaultTypography(context: Context): Typography {
- val defaultTypography = Typography()
- return Typography(
- display1 =
- defaultTypography.display1.copy(
- fontFamily =
- fontFamily(context, R.string.wear_material_compose_display_1_font_family)
- ),
- display2 =
- defaultTypography.display2.copy(
- fontFamily =
- fontFamily(context, R.string.wear_material_compose_display_2_font_family)
- ),
- display3 =
- defaultTypography.display3.copy(
- fontFamily =
- fontFamily(context, R.string.wear_material_compose_display_3_font_family)
- ),
- title1 =
- defaultTypography.title1.copy(
- fontFamily = fontFamily(context, R.string.wear_material_compose_title_1_font_family)
- ),
- title2 =
- defaultTypography.title2.copy(
- fontFamily = fontFamily(context, R.string.wear_material_compose_title_2_font_family)
- ),
- title3 =
- defaultTypography.title3.copy(
- fontFamily = fontFamily(context, R.string.wear_material_compose_title_3_font_family)
- ),
- body1 =
- defaultTypography.body1.copy(
- fontFamily = fontFamily(context, R.string.wear_material_compose_body_1_font_family)
- ),
- body2 =
- defaultTypography.body2.copy(
- fontFamily = fontFamily(context, R.string.wear_material_compose_body_2_font_family)
- ),
- button =
- defaultTypography.button.copy(
- fontFamily = fontFamily(context, R.string.wear_material_compose_button_font_family)
- ),
- caption1 =
- defaultTypography.caption1.copy(
- fontFamily =
- fontFamily(context, R.string.wear_material_compose_caption_1_font_family)
- ),
- caption2 =
- defaultTypography.caption2.copy(
- fontFamily =
- fontFamily(context, R.string.wear_material_compose_caption_2_font_family)
- ),
- caption3 =
- defaultTypography.caption3.copy(
- fontFamily =
- fontFamily(context, R.string.wear_material_compose_caption_3_font_family)
- ),
- )
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTonalPalette.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTonalPalette.kt
deleted file mode 100644
index a86af8b3d..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTonalPalette.kt
+++ /dev/null
@@ -1,191 +0,0 @@
-@file:Suppress("unused")
-
-package com.android.permissioncontroller.permission.ui.wear.theme
-
-import android.R
-import android.content.Context
-import android.os.Build
-import androidx.annotation.ColorRes
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
-import androidx.compose.ui.graphics.Color
-
-/**
- * Tonal Palette structure in Material.
- *
- * A tonal palette is comprised of 5 tonal ranges. Each tonal range includes the 13 stops, or tonal
- * swatches.
- *
- * Tonal range names are:
- * - Neutral (N)
- * - Neutral variant (NV)
- * - Primary (P)
- * - Secondary (S)
- * - Tertiary (T)
- */
-internal class WearPermissionTonalPalette(
- // The neutral tonal range.
- val neutral100: Color,
- val neutral99: Color,
- val neutral95: Color,
- val neutral90: Color,
- val neutral80: Color,
- val neutral70: Color,
- val neutral60: Color,
- val neutral50: Color,
- val neutral40: Color,
- val neutral30: Color,
- val neutral20: Color,
- val neutral10: Color,
- val neutral0: Color,
-
- // The neutral variant tonal range, sometimes called "neutral 2"
- val neutralVariant100: Color,
- val neutralVariant99: Color,
- val neutralVariant95: Color,
- val neutralVariant90: Color,
- val neutralVariant80: Color,
- val neutralVariant70: Color,
- val neutralVariant60: Color,
- val neutralVariant50: Color,
- val neutralVariant40: Color,
- val neutralVariant30: Color,
- val neutralVariant20: Color,
- val neutralVariant10: Color,
- val neutralVariant0: Color,
-
- // The primary tonal range, also known as accent 1
- val primary100: Color,
- val primary99: Color,
- val primary95: Color,
- val primary90: Color,
- val primary80: Color,
- val primary70: Color,
- val primary60: Color,
- val primary50: Color,
- val primary40: Color,
- val primary30: Color,
- val primary20: Color,
- val primary10: Color,
- val primary0: Color,
-
- // The Secondary tonal range, also know as accent 2
- val secondary100: Color,
- val secondary99: Color,
- val secondary95: Color,
- val secondary90: Color,
- val secondary80: Color,
- val secondary70: Color,
- val secondary60: Color,
- val secondary50: Color,
- val secondary40: Color,
- val secondary30: Color,
- val secondary20: Color,
- val secondary10: Color,
- val secondary0: Color,
-
- // The tertiary tonal range, also known as accent 3
- val tertiary100: Color,
- val tertiary99: Color,
- val tertiary95: Color,
- val tertiary90: Color,
- val tertiary80: Color,
- val tertiary70: Color,
- val tertiary60: Color,
- val tertiary50: Color,
- val tertiary40: Color,
- val tertiary30: Color,
- val tertiary20: Color,
- val tertiary10: Color,
- val tertiary0: Color,
-)
-/** Dynamic colors for wear compose material to support resource overlay. */
-@RequiresApi(Build.VERSION_CODES.S)
-// TODO: once we have proper support for this on Wear 6+, we will do something similar to
-// https://source.corp.google.com/h/android/platform/superproject/+/androidx-main:frameworks/support/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DynamicTonalPalette.android.kt;l=307-362?q=dynamicTonalPalette&sq=repo:android%2Fplatform%2Fsuperproject%20b:androidx-main
-// Tracking Bug: b/270720571
-internal fun dynamicTonalPalette(context: Context) =
- WearPermissionTonalPalette(
- // The neutral tonal range from the generated dynamic color palette.
- neutral100 = ColorResourceHelper.getColor(context, R.color.system_neutral1_0),
- neutral99 = ColorResourceHelper.getColor(context, R.color.system_neutral1_10),
- neutral95 = ColorResourceHelper.getColor(context, R.color.system_neutral1_50),
- neutral90 = ColorResourceHelper.getColor(context, R.color.system_neutral1_100),
- neutral80 = ColorResourceHelper.getColor(context, R.color.system_neutral1_200),
- neutral70 = ColorResourceHelper.getColor(context, R.color.system_neutral1_300),
- neutral60 = ColorResourceHelper.getColor(context, R.color.system_neutral1_400),
- neutral50 = ColorResourceHelper.getColor(context, R.color.system_neutral1_500),
- neutral40 = ColorResourceHelper.getColor(context, R.color.system_neutral1_600),
- neutral30 = ColorResourceHelper.getColor(context, R.color.system_neutral1_700),
- neutral20 = ColorResourceHelper.getColor(context, R.color.system_neutral1_800),
- neutral10 = ColorResourceHelper.getColor(context, R.color.system_neutral1_900),
- neutral0 = ColorResourceHelper.getColor(context, R.color.system_neutral1_1000),
-
- // The neutral variant tonal range, sometimes called "neutral 2", from the
- // generated dynamic color palette.
- neutralVariant100 = ColorResourceHelper.getColor(context, R.color.system_neutral2_0),
- neutralVariant99 = ColorResourceHelper.getColor(context, R.color.system_neutral2_10),
- neutralVariant95 = ColorResourceHelper.getColor(context, R.color.system_neutral2_50),
- neutralVariant90 = ColorResourceHelper.getColor(context, R.color.system_neutral2_100),
- neutralVariant80 = ColorResourceHelper.getColor(context, R.color.system_neutral2_200),
- neutralVariant70 = ColorResourceHelper.getColor(context, R.color.system_neutral2_300),
- neutralVariant60 = ColorResourceHelper.getColor(context, R.color.system_neutral2_400),
- neutralVariant50 = ColorResourceHelper.getColor(context, R.color.system_neutral2_500),
- neutralVariant40 = ColorResourceHelper.getColor(context, R.color.system_neutral2_600),
- neutralVariant30 = ColorResourceHelper.getColor(context, R.color.system_neutral2_700),
- neutralVariant20 = ColorResourceHelper.getColor(context, R.color.system_neutral2_800),
- neutralVariant10 = ColorResourceHelper.getColor(context, R.color.system_neutral2_900),
- neutralVariant0 = ColorResourceHelper.getColor(context, R.color.system_neutral2_1000),
-
- // The primary tonal range from the generated dynamic color palette.
- primary100 = ColorResourceHelper.getColor(context, R.color.system_accent1_0),
- primary99 = ColorResourceHelper.getColor(context, R.color.system_accent1_10),
- primary95 = ColorResourceHelper.getColor(context, R.color.system_accent1_50),
- primary90 = ColorResourceHelper.getColor(context, R.color.system_accent1_100),
- primary80 = ColorResourceHelper.getColor(context, R.color.system_accent1_200),
- primary70 = ColorResourceHelper.getColor(context, R.color.system_accent1_300),
- primary60 = ColorResourceHelper.getColor(context, R.color.system_accent1_400),
- primary50 = ColorResourceHelper.getColor(context, R.color.system_accent1_500),
- primary40 = ColorResourceHelper.getColor(context, R.color.system_accent1_600),
- primary30 = ColorResourceHelper.getColor(context, R.color.system_accent1_700),
- primary20 = ColorResourceHelper.getColor(context, R.color.system_accent1_800),
- primary10 = ColorResourceHelper.getColor(context, R.color.system_accent1_900),
- primary0 = ColorResourceHelper.getColor(context, R.color.system_accent1_1000),
-
- // The secondary tonal range from the generated dynamic color palette.
- secondary100 = ColorResourceHelper.getColor(context, R.color.system_accent2_0),
- secondary99 = ColorResourceHelper.getColor(context, R.color.system_accent2_10),
- secondary95 = ColorResourceHelper.getColor(context, R.color.system_accent2_50),
- secondary90 = ColorResourceHelper.getColor(context, R.color.system_accent2_100),
- secondary80 = ColorResourceHelper.getColor(context, R.color.system_accent2_200),
- secondary70 = ColorResourceHelper.getColor(context, R.color.system_accent2_300),
- secondary60 = ColorResourceHelper.getColor(context, R.color.system_accent2_400),
- secondary50 = ColorResourceHelper.getColor(context, R.color.system_accent2_500),
- secondary40 = ColorResourceHelper.getColor(context, R.color.system_accent2_600),
- secondary30 = ColorResourceHelper.getColor(context, R.color.system_accent2_700),
- secondary20 = ColorResourceHelper.getColor(context, R.color.system_accent2_800),
- secondary10 = ColorResourceHelper.getColor(context, R.color.system_accent2_900),
- secondary0 = ColorResourceHelper.getColor(context, R.color.system_accent2_1000),
-
- // The tertiary tonal range from the generated dynamic color palette.
- tertiary100 = ColorResourceHelper.getColor(context, R.color.system_accent3_0),
- tertiary99 = ColorResourceHelper.getColor(context, R.color.system_accent3_10),
- tertiary95 = ColorResourceHelper.getColor(context, R.color.system_accent3_50),
- tertiary90 = ColorResourceHelper.getColor(context, R.color.system_accent3_100),
- tertiary80 = ColorResourceHelper.getColor(context, R.color.system_accent3_200),
- tertiary70 = ColorResourceHelper.getColor(context, R.color.system_accent3_300),
- tertiary60 = ColorResourceHelper.getColor(context, R.color.system_accent3_400),
- tertiary50 = ColorResourceHelper.getColor(context, R.color.system_accent3_500),
- tertiary40 = ColorResourceHelper.getColor(context, R.color.system_accent3_600),
- tertiary30 = ColorResourceHelper.getColor(context, R.color.system_accent3_700),
- tertiary20 = ColorResourceHelper.getColor(context, R.color.system_accent3_800),
- tertiary10 = ColorResourceHelper.getColor(context, R.color.system_accent3_900),
- tertiary0 = ColorResourceHelper.getColor(context, R.color.system_accent3_1000),
- )
-
-private object ColorResourceHelper {
- @DoNotInline
- fun getColor(context: Context, @ColorRes id: Int): Color {
- return Color(context.resources.getColor(id, context.theme))
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt
index a5f78aa53..081a467bd 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt
@@ -24,8 +24,8 @@ import android.content.ContextWrapper
import android.content.pm.ComponentInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
-import android.os.Looper
import android.os.UserHandle
+import androidx.arch.core.executor.ArchTaskExecutor
import java.util.concurrent.Executors
import kotlinx.coroutines.asCoroutineDispatcher
@@ -51,8 +51,8 @@ val IPC = Executors.newFixedThreadPool(IPC_THREAD_POOL_COUNT).asCoroutineDispatc
/** Assert that an operation is running on main thread */
fun ensureMainThread() =
- check(Looper.myLooper() == Looper.getMainLooper()) {
- "Only meant to be used on the main thread"
+ check(ArchTaskExecutor.getInstance().isMainThread) {
+ ("Only meant to be used on the main thread, current thread is " + Thread.currentThread())
}
/** A more readable version of [PackageManager.updatePermissionFlags] */
@@ -72,5 +72,7 @@ fun PackageManager.updatePermissionFlags(
val ResolveInfo.componentInfo: ComponentInfo
get() {
return (activityInfo as ComponentInfo?)
- ?: serviceInfo ?: providerInfo ?: throw IllegalStateException("Missing ComponentInfo!")
+ ?: serviceInfo
+ ?: providerInfo
+ ?: throw IllegalStateException("Missing ComponentInfo!")
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index fb33aaffc..51f098371 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
@@ -165,7 +165,7 @@ object KotlinUtils {
val first: A,
val second: B,
val third: C,
- val fourth: D
+ val fourth: D,
)
/**
@@ -179,7 +179,7 @@ object KotlinUtils {
DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_CAMERA_MIC_ICONS_ENABLED,
- true
+ true,
)
}
@@ -190,7 +190,7 @@ object KotlinUtils {
DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_LOCATION_INDICATORS_ENABLED,
- false
+ false,
)
}
@@ -211,7 +211,7 @@ object KotlinUtils {
DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_PHOTO_PICKER_PROMPT_ENABLED,
- true
+ true,
)
}
@@ -236,7 +236,7 @@ object KotlinUtils {
DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PRIVACY,
PERMISSION_RATIONALE_ENABLED,
- true
+ true,
)
}
@@ -249,7 +249,7 @@ object KotlinUtils {
DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PRIVACY,
SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED,
- true
+ true,
) &&
!DeviceUtils.isAuto(context) &&
!DeviceUtils.isTelevision(context) &&
@@ -262,7 +262,7 @@ object KotlinUtils {
return DeviceConfig.getLong(
DeviceConfig.NAMESPACE_PRIVACY,
PROPERTY_SAFETY_LABEL_CHANGES_JOB_INTERVAL_MILLIS,
- Duration.ofDays(30).toMillis()
+ Duration.ofDays(30).toMillis(),
)
}
@@ -278,7 +278,7 @@ object KotlinUtils {
*/
fun <K> getMapAndListDifferences(
newValues: Collection<K>,
- oldValues: Map<K, *>
+ oldValues: Map<K, *>,
): Pair<Set<K>, Set<K>> {
val mapHas = oldValues.keys.toMutableSet()
val listHas = newValues.toMutableSet()
@@ -301,7 +301,7 @@ object KotlinUtils {
fun sortPreferenceGroup(
group: PreferenceGroup,
compare: (lhs: Preference, rhs: Preference) -> Int,
- hasHeader: Boolean
+ hasHeader: Boolean,
) {
val preferences = mutableListOf<Preference>()
for (i in 0 until group.preferenceCount) {
@@ -369,7 +369,7 @@ object KotlinUtils {
return groupInfo.loadSafeLabel(
context.packageManager,
0f,
- TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM
+ TextUtils.SAFE_STRING_FLAG_FIRST_LINE or TextUtils.SAFE_STRING_FLAG_TRIM,
)
}
@@ -407,7 +407,7 @@ object KotlinUtils {
.loadSafeLabel(
context.packageManager,
20000.toFloat(),
- TextUtils.SAFE_STRING_FLAG_TRIM
+ TextUtils.SAFE_STRING_FLAG_TRIM,
)
} catch (e: PackageManager.NameNotFoundException) {
permName
@@ -431,7 +431,7 @@ object KotlinUtils {
Utils.applyTint(
context,
permInfo.loadUnbadgedIcon(context.packageManager),
- android.R.attr.colorControlNormal
+ android.R.attr.colorControlNormal,
)
}
@@ -445,7 +445,7 @@ object KotlinUtils {
Utils.applyTint(
context,
context.getDrawable(R.drawable.ic_perm_device_info),
- android.R.attr.colorControlNormal
+ android.R.attr.colorControlNormal,
)
}
}
@@ -461,7 +461,8 @@ object KotlinUtils {
fun getPermInfoDescription(context: Context, permName: String): CharSequence {
return try {
val permInfo = context.packageManager.getPermissionInfo(permName, 0)
- permInfo.loadDescription(context.packageManager) ?: ""
+ permInfo.loadDescription(context.packageManager)
+ ?: permInfo.loadLabel(context.packageManager)
} catch (e: PackageManager.NameNotFoundException) {
""
}
@@ -511,7 +512,7 @@ object KotlinUtils {
fun getBadgedPackageIconBitmap(
application: Application,
user: UserHandle,
- packageName: String
+ packageName: String,
): Bitmap? {
val drawable = getBadgedPackageIcon(application, packageName, user)
@@ -547,7 +548,7 @@ object KotlinUtils {
Bitmap.createBitmap(
pkgIcon.intrinsicWidth,
pkgIcon.intrinsicHeight,
- Bitmap.Config.ARGB_8888
+ Bitmap.Config.ARGB_8888,
)
// Draw the icon so it can be displayed.
val canvas = Canvas(pkgIconBmp)
@@ -597,7 +598,7 @@ object KotlinUtils {
activity: Activity,
uid: Int,
requestedPermissions: List<String>,
- requestCode: Int
+ requestCode: Int,
) {
// A clone profile doesn't have a MediaProvider. If the app's user is a clone profile, open
// the photo picker in the parent profile
@@ -653,7 +654,7 @@ object KotlinUtils {
return opsManager.unsafeCheckOpNoThrow(
OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
uid,
- packageName
+ packageName,
) == MODE_ALLOWED
}
return true
@@ -692,7 +693,7 @@ object KotlinUtils {
app: Application,
group: LightAppPermGroup,
vararg flags: Pair<Int, Boolean>,
- filterPermissions: List<String> = group.permissions.keys.toList()
+ filterPermissions: List<String> = group.permissions.keys.toList(),
): LightAppPermGroup {
var flagMask = 0
var flagsToSet = 0
@@ -718,7 +719,7 @@ object KotlinUtils {
permName,
group.packageName,
group.userHandle,
- *flags
+ *flags,
)
}
newPerms[permName] =
@@ -727,7 +728,7 @@ object KotlinUtils {
perm.permInfo,
perm.isGranted,
perm.flags or flagsToSet,
- perm.foregroundPerms
+ perm.foregroundPerms,
)
}
return LightAppPermGroup(
@@ -735,7 +736,8 @@ object KotlinUtils {
group.permGroupInfo,
newPerms,
group.hasInstallToRuntimeSplit,
- group.specialLocationGrant
+ group.specialLocationGrant,
+ group.specialFixedStorageGrant,
)
}
@@ -766,7 +768,7 @@ object KotlinUtils {
isOneTime,
userFixed,
withoutAppOps,
- filterPermissions
+ filterPermissions,
)
}
@@ -785,7 +787,7 @@ object KotlinUtils {
fun grantBackgroundRuntimePermissions(
app: Application,
group: LightAppPermGroup,
- filterPermissions: Collection<String> = group.permissions.keys
+ filterPermissions: Collection<String> = group.permissions.keys,
): LightAppPermGroup {
return grantRuntimePermissions(
app,
@@ -794,7 +796,7 @@ object KotlinUtils {
isOneTime = false,
userFixed = false,
withoutAppOps = false,
- filterPermissions = filterPermissions
+ filterPermissions = filterPermissions,
)
}
@@ -806,7 +808,7 @@ object KotlinUtils {
isOneTime: Boolean = false,
userFixed: Boolean = false,
withoutAppOps: Boolean = false,
- filterPermissions: Collection<String> = group.permissions.keys
+ filterPermissions: Collection<String> = group.permissions.keys,
): LightAppPermGroup {
val deviceId = group.deviceId
val newPerms = group.permissions.toMutableMap()
@@ -837,7 +839,7 @@ object KotlinUtils {
group.packageInfo.packageName,
PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
permFlags,
- user
+ user,
)
}
}
@@ -846,7 +848,7 @@ object KotlinUtils {
if (shouldKillForAnyPermission) {
(app.getSystemService(ActivityManager::class.java) as ActivityManager).killUid(
group.packageInfo.uid,
- KILL_REASON_APP_OP_CHANGE
+ KILL_REASON_APP_OP_CHANGE,
)
}
val newGroup =
@@ -855,7 +857,8 @@ object KotlinUtils {
group.permGroupInfo,
newPerms,
group.hasInstallToRuntimeSplit,
- group.specialLocationGrant
+ group.specialLocationGrant,
+ group.specialFixedStorageGrant,
)
// If any permission in the group is one time granted, start one time permission session.
if (newGroup.permissions.any { it.value.isOneTime && it.value.isGranted }) {
@@ -867,7 +870,7 @@ object KotlinUtils {
Utils.getOneTimePermissionsTimeout(),
Utils.getOneTimePermissionsKilledDelay(false),
ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
- ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE
+ ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE,
)
} else {
context
@@ -876,7 +879,7 @@ object KotlinUtils {
group.packageName,
Utils.getOneTimePermissionsTimeout(),
ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_RESET_TIMER,
- ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE
+ ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE,
)
}
}
@@ -904,7 +907,7 @@ object KotlinUtils {
group: LightAppPermGroup,
isOneTime: Boolean,
userFixed: Boolean = false,
- withoutAppOps: Boolean = false
+ withoutAppOps: Boolean = false,
): Pair<LightPermission, Boolean> {
val pkgInfo = group.packageInfo
val user = UserHandle.getUserHandleForUid(pkgInfo.uid)
@@ -942,7 +945,7 @@ object KotlinUtils {
group.packageName,
PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
oldFlags,
- user
+ user,
)
// TODO: Update this method once AppOp is device aware
disallowAppOp(app, perm, group)
@@ -1018,7 +1021,7 @@ object KotlinUtils {
group.packageInfo.packageName,
PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
newFlags,
- user
+ user,
)
}
@@ -1047,7 +1050,7 @@ object KotlinUtils {
userFixed: Boolean = false,
oneTime: Boolean = false,
forceRemoveRevokedCompat: Boolean = false,
- filterPermissions: Collection<String> = group.permissions.keys
+ filterPermissions: Collection<String> = group.permissions.keys,
): LightAppPermGroup {
return revokeRuntimePermissions(
app,
@@ -1056,7 +1059,7 @@ object KotlinUtils {
userFixed,
oneTime,
forceRemoveRevokedCompat,
- filterPermissions
+ filterPermissions,
)
}
@@ -1079,7 +1082,7 @@ object KotlinUtils {
userFixed: Boolean = false,
oneTime: Boolean = false,
forceRemoveRevokedCompat: Boolean = false,
- filterPermissions: Collection<String> = group.permissions.keys
+ filterPermissions: Collection<String> = group.permissions.keys,
): LightAppPermGroup {
return revokeRuntimePermissions(
app,
@@ -1088,7 +1091,7 @@ object KotlinUtils {
userFixed,
oneTime,
forceRemoveRevokedCompat,
- filterPermissions
+ filterPermissions,
)
}
@@ -1100,7 +1103,7 @@ object KotlinUtils {
userFixed: Boolean,
oneTime: Boolean,
forceRemoveRevokedCompat: Boolean = false,
- filterPermissions: Collection<String>
+ filterPermissions: Collection<String>,
): LightAppPermGroup {
val deviceId = group.deviceId
val wasOneTime = group.isOneTime
@@ -1117,7 +1120,7 @@ object KotlinUtils {
userFixed,
oneTime,
forceRemoveRevokedCompat,
- group
+ group,
)
newPerms[newPerm.name] = newPerm
shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
@@ -1127,7 +1130,7 @@ object KotlinUtils {
if (shouldKillForAnyPermission && !shouldSkipKillForGroup(app, group)) {
(app.getSystemService(ActivityManager::class.java) as ActivityManager).killUid(
group.packageInfo.uid,
- KILL_REASON_APP_OP_CHANGE
+ KILL_REASON_APP_OP_CHANGE,
)
}
@@ -1137,7 +1140,8 @@ object KotlinUtils {
group.permGroupInfo,
newPerms,
group.hasInstallToRuntimeSplit,
- group.specialLocationGrant
+ group.specialLocationGrant,
+ group.specialFixedStorageGrant,
)
if (wasOneTime && !anyPermsOfPackageOneTimeGranted(app, newGroup.packageInfo, newGroup)) {
@@ -1165,7 +1169,7 @@ object KotlinUtils {
packageName: String,
permissionGroupName: String,
user: UserHandle,
- postRevokeHandler: Runnable?
+ postRevokeHandler: Runnable?,
) {
GlobalScope.launch(Dispatchers.Main) {
val group =
@@ -1192,7 +1196,7 @@ object KotlinUtils {
private fun anyPermsOfPackageOneTimeGranted(
app: Application,
packageInfo: LightPackageInfo,
- group: LightAppPermGroup? = null
+ group: LightAppPermGroup? = null,
): Boolean {
val user = group?.userHandle ?: UserHandle.getUserHandleForUid(packageInfo.uid)
if (group?.isOneTime == true) {
@@ -1233,7 +1237,7 @@ object KotlinUtils {
userFixed: Boolean,
oneTime: Boolean,
forceRemoveRevokedCompat: Boolean,
- group: LightAppPermGroup
+ group: LightAppPermGroup,
): Pair<LightPermission, Boolean> {
// Do not touch permissions fixed by the system.
if (perm.isSystemFixed) {
@@ -1259,14 +1263,14 @@ object KotlinUtils {
!isPermissionSplitFromNonRuntime(
app,
perm.name,
- group.packageInfo.targetSdkVersion
+ group.packageInfo.targetSdkVersion,
)
) {
// Revoke the permission if needed.
context.packageManager.revokeRuntimePermission(
group.packageInfo.packageName,
perm.name,
- user
+ user,
)
isGranted = false
if (forceRemoveRevokedCompat) {
@@ -1311,7 +1315,7 @@ object KotlinUtils {
group.packageInfo.packageName,
PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
newFlags,
- user
+ user,
)
}
@@ -1332,7 +1336,7 @@ object KotlinUtils {
.cancelBackgroundAccessWarningNotification(
group.packageInfo.packageName,
user,
- true
+ true,
)
}
}
@@ -1372,7 +1376,7 @@ object KotlinUtils {
private fun allowAppOp(
app: Application,
perm: LightPermission,
- group: LightAppPermGroup
+ group: LightAppPermGroup,
): Boolean {
val packageName = group.packageInfo.packageName
val uid = group.packageInfo.uid
@@ -1434,7 +1438,7 @@ object KotlinUtils {
private fun disallowAppOp(
app: Application,
perm: LightPermission,
- group: LightAppPermGroup
+ group: LightAppPermGroup,
): Boolean {
val packageName = group.packageInfo.packageName
val uid = group.packageInfo.uid
@@ -1473,7 +1477,7 @@ object KotlinUtils {
uid: Int,
packageName: String,
mode: Int,
- manager: AppOpsManager
+ manager: AppOpsManager,
): Boolean {
val currentMode = manager.unsafeCheckOpRaw(op, uid, packageName)
if (currentMode == mode) {
@@ -1492,7 +1496,7 @@ object KotlinUtils {
app,
POST_NOTIFICATIONS,
group.packageName,
- group.userHandle
+ group.userHandle,
)
}
@@ -1512,7 +1516,7 @@ object KotlinUtils {
app: Application,
permission: String,
packageName: String,
- user: UserHandle
+ user: UserHandle,
): Boolean {
val userContext: Context = Utils.getUserContext(app, user)
if (
@@ -1556,7 +1560,7 @@ object KotlinUtils {
var resolveInfos =
context.packageManager.queryIntentActivities(
intentToResolve,
- MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE
+ MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE,
)
if (resolveInfos.size <= 0) {
@@ -1566,7 +1570,7 @@ object KotlinUtils {
resolveInfos =
context.packageManager.queryIntentActivities(
intentToResolve,
- MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE
+ MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE,
)
}
return resolveInfos.size > 0
@@ -1582,14 +1586,14 @@ object KotlinUtils {
fun setFlagsWhenLocationAccuracyChanged(
app: Application,
group: LightAppPermGroup,
- isFineSelected: Boolean
+ isFineSelected: Boolean,
) {
if (isFineSelected) {
setGroupFlags(
app,
group,
PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to true,
- filterPermissions = listOf(ACCESS_FINE_LOCATION)
+ filterPermissions = listOf(ACCESS_FINE_LOCATION),
)
val fineIsOneTime =
group.permissions[Manifest.permission.ACCESS_FINE_LOCATION]?.isOneTime ?: false
@@ -1598,20 +1602,20 @@ object KotlinUtils {
group,
PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to false,
PackageManager.FLAG_PERMISSION_ONE_TIME to fineIsOneTime,
- filterPermissions = listOf(Manifest.permission.ACCESS_COARSE_LOCATION)
+ filterPermissions = listOf(Manifest.permission.ACCESS_COARSE_LOCATION),
)
} else {
setGroupFlags(
app,
group,
PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to false,
- filterPermissions = listOf(ACCESS_FINE_LOCATION)
+ filterPermissions = listOf(ACCESS_FINE_LOCATION),
)
setGroupFlags(
app,
group,
PackageManager.FLAG_PERMISSION_SELECTED_LOCATION_ACCURACY to true,
- filterPermissions = listOf(Manifest.permission.ACCESS_COARSE_LOCATION)
+ filterPermissions = listOf(Manifest.permission.ACCESS_COARSE_LOCATION),
)
}
}
@@ -1629,7 +1633,7 @@ object KotlinUtils {
DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_PRIVACY,
SAFETY_PROTECTION_RESOURCES_ENABLED,
- false
+ false,
) &&
context
.getResources()
@@ -1658,7 +1662,7 @@ object KotlinUtils {
fun getAppStoreIntent(
context: Context,
installerPackageName: String?,
- packageName: String?
+ packageName: String?,
): Intent? {
val intent: Intent = Intent(Intent.ACTION_SHOW_APP_INFO).setPackage(installerPackageName)
val result: Intent? = resolveActivityForIntent(context, intent)
@@ -1720,7 +1724,7 @@ object KotlinUtils {
/** Get the [value][LiveData.getValue], suspending until [isInitialized] if not yet so */
suspend fun <T, LD : LiveData<T>> LD.getInitializedValue(
observe: LD.(Observer<T?>) -> Unit = { observeForever(it) },
- isValueInitialized: LD.() -> Boolean = { value != null }
+ isValueInitialized: LD.() -> Boolean = { value != null },
): T? {
return if (isValueInitialized()) {
value
@@ -1754,7 +1758,7 @@ suspend fun <T, LD : LiveData<T>> LD.getInitializedValue(
suspend inline fun <T, R> Iterable<T>.mapInParallel(
context: CoroutineContext,
scope: CoroutineScope = GlobalScope,
- crossinline transform: suspend CoroutineScope.(T) -> R
+ crossinline transform: suspend CoroutineScope.(T) -> R,
): List<R> = map { scope.async(context) { transform(it) } }.map { it.await() }
/**
@@ -1765,7 +1769,7 @@ suspend inline fun <T, R> Iterable<T>.mapInParallel(
suspend inline fun <T> Iterable<T>.forEachInParallel(
context: CoroutineContext,
scope: CoroutineScope = GlobalScope,
- crossinline action: suspend CoroutineScope.(T) -> Unit
+ crossinline action: suspend CoroutineScope.(T) -> Unit,
) {
mapInParallel(context, scope) { action(it) }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
index 0d1d960ab..93a1a66df 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
@@ -27,6 +27,7 @@ import android.util.Log
import com.android.modules.utils.build.SdkLevel
import com.android.permission.safetylabel.DataCategoryConstants
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils
/**
* This file contains the canonical mapping of permission to permission group, used in the
@@ -139,13 +140,30 @@ object PermissionMapping {
PLATFORM_PERMISSIONS[Manifest.permission.NEARBY_WIFI_DEVICES] =
Manifest.permission_group.NEARBY_DEVICES
}
-
// Ranging permission will be supported from Android B+, update this when isAtLeastB()
// is available.
if (SdkLevel.isAtLeastV() && Flags.rangingPermissionEnabled()) {
PLATFORM_PERMISSIONS[Manifest.permission.RANGING] =
Manifest.permission_group.NEARBY_DEVICES
}
+ // Android XR permissions
+ if (android.xr.Flags.xrManifestEntries()) {
+ PLATFORM_PERMISSIONS[Manifest.permission.EYE_TRACKING_COARSE] =
+ Manifest.permission_group.XR_TRACKING
+ PLATFORM_PERMISSIONS[Manifest.permission.FACE_TRACKING] =
+ Manifest.permission_group.XR_TRACKING
+ PLATFORM_PERMISSIONS[Manifest.permission.HAND_TRACKING] =
+ Manifest.permission_group.XR_TRACKING
+ PLATFORM_PERMISSIONS[Manifest.permission.SCENE_UNDERSTANDING_COARSE] =
+ Manifest.permission_group.XR_TRACKING
+
+ PLATFORM_PERMISSIONS[Manifest.permission.EYE_TRACKING_FINE] =
+ Manifest.permission_group.XR_TRACKING_SENSITIVE
+ PLATFORM_PERMISSIONS[Manifest.permission.HEAD_TRACKING] =
+ Manifest.permission_group.XR_TRACKING_SENSITIVE
+ PLATFORM_PERMISSIONS[Manifest.permission.SCENE_UNDERSTANDING_FINE] =
+ Manifest.permission_group.XR_TRACKING_SENSITIVE
+ }
// Any updates to the permissions for the CALL_LOG permission group must also be made in
// Permissions {@link com.android.role.controller.model.Permissions} in the role
@@ -182,13 +200,18 @@ object PermissionMapping {
Manifest.permission_group.CAMERA
}
- PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS] = Manifest.permission_group.SENSORS
-
if (SdkLevel.isAtLeastT()) {
PLATFORM_PERMISSIONS[Manifest.permission.POST_NOTIFICATIONS] =
Manifest.permission_group.NOTIFICATIONS
- PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS_BACKGROUND] =
+ }
+
+ if (!Flags.replaceBodySensorPermissionEnabled()) {
+ PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS] =
Manifest.permission_group.SENSORS
+ if (SdkLevel.isAtLeastT()) {
+ PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS_BACKGROUND] =
+ Manifest.permission_group.SENSORS
+ }
}
for ((permission, permissionGroup) in PLATFORM_PERMISSIONS) {
@@ -328,6 +351,9 @@ object PermissionMapping {
PLATFORM_PERMISSIONS[permission] = HEALTH_PERMISSION_GROUP
PLATFORM_PERMISSION_GROUPS[HEALTH_PERMISSION_GROUP]?.add(permission)
HEALTH_PERMISSIONS_SET.add(permission)
+ if (Flags.replaceBodySensorPermissionEnabled()) {
+ AdminRestrictedPermissionsUtils.addAdminRestrictedPermission(permission)
+ }
}
}
@@ -344,7 +370,7 @@ object PermissionMapping {
val appSupportsPickerPrompt =
group.permissions[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED]?.isImplicit ==
- false
+ false
return if (appSupportsPickerPrompt) {
PARTIAL_MEDIA_PERMISSIONS
@@ -353,12 +379,6 @@ object PermissionMapping {
}
}
- /** Returns true if the given permission is a health platform permission. */
- @JvmStatic
- fun isHealthPermission(permissionName: String): Boolean {
- return HEALTH_PERMISSIONS_SET.contains(permissionName)
- }
-
/**
* Returns the platform permission group for the permission that the provided op backs, if any.
*/
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
index e5de63f32..327142896 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -35,9 +35,11 @@ import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
import static android.content.Context.MODE_PRIVATE;
import static android.content.Intent.EXTRA_PACKAGE_NAME;
+import static android.content.Intent.EXTRA_REASON;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -77,6 +79,7 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.hardware.SensorPrivacyManager;
import android.health.connect.HealthConnectManager;
+import android.health.connect.HealthPermissions;
import android.os.Binder;
import android.os.Build;
import android.os.Parcelable;
@@ -126,6 +129,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.Set;
+import java.util.function.Supplier;
public final class Utils {
@@ -546,8 +550,14 @@ public final class Utils {
if (group.equals(Manifest.permission_group.UNDEFINED)) {
List<PermissionInfo> undefinedPerms = new ArrayList<>();
for (PermissionInfo permissionInfo : installedRuntime) {
+ if (Flags.replaceBodySensorPermissionEnabled()
+ && (permissionInfo.name.equals(Manifest.permission.BODY_SENSORS) ||
+ permissionInfo.name.equals(Manifest.permission.BODY_SENSORS_BACKGROUND))) {
+ continue;
+ }
+
String permGroup =
- PermissionMapping.getGroupOfPlatformPermission(permissionInfo.name);
+ PermissionMapping.getGroupOfPlatformPermission(permissionInfo.name);
if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) {
undefinedPerms.add(permissionInfo);
}
@@ -1099,17 +1109,31 @@ public final class Utils {
return false;
}
+ // Always show Fitness&Wellness chip on Wear.
+ if (Flags.replaceBodySensorPermissionEnabled()
+ && pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ return true;
+ }
+
// Check in permission is already granted as we should not hide it in the UX at that point.
List<String> grantedPermissions = packageInfo.getGrantedPermissions();
for (PermissionInfo permission : permissions) {
boolean isCurrentlyGranted = grantedPermissions.contains(permission.name);
if (isCurrentlyGranted) {
- Log.d(LOG_TAG, "At least one Health permission group permission is granted, "
+ Log.d(
+ LOG_TAG,
+ "At least one Health permission group permission is granted, "
+ "show permission group entry");
return true;
}
}
+ // When none health permission is granted, exempt health permission view usage intent filter
+ // if all the requested health permissions are from permission splits.
+ if (isRequestFromSplitHealthPermission(packageInfo)) {
+ return true;
+ }
+
Intent viewUsageIntent = new Intent(Intent.ACTION_VIEW_PERMISSION_USAGE);
viewUsageIntent.addCategory(HealthConnectManager.CATEGORY_HEALTH_PERMISSIONS);
viewUsageIntent.setPackage(packageInfo.getPackageName());
@@ -1124,6 +1148,71 @@ public final class Utils {
}
/**
+ * Returns true if the request is being made as the result of a split health permission from
+ * BODY_SENSORS call.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private static boolean isRequestFromSplitHealthPermission(LightPackageInfo packageInfo) {
+ // Sdk check to make sure HealthConnectManager.isHealthPermission() is supported.
+ if (!SdkLevel.isAtLeastU() || !Flags.replaceBodySensorPermissionEnabled()) {
+ return false;
+ }
+
+ PermissionControllerApplication app = PermissionControllerApplication.get();
+ PackageManager pm = app.getPackageManager();
+ String packageName = packageInfo.getPackageName();
+ UserHandle user = UserHandle.getUserHandleForUid(packageInfo.getUid());
+ Context context = Utils.getUserContext(app, user);
+
+ List<String> requestedHealthPermissions = new ArrayList<>();
+ for (String permission : packageInfo.getRequestedPermissions()) {
+ if (HealthConnectManager.isHealthPermission(context, permission)) {
+ requestedHealthPermissions.add(permission);
+ }
+ }
+
+ // Split permission only applies to READ_HEART_RATE.
+ if (!requestedHealthPermissions.contains(HealthPermissions.READ_HEART_RATE)) {
+ return false;
+ }
+
+ // If there are other health permissions (other than READ_HEALTH_DATA_IN_BACKGROUND)
+ // don't consider this a pure split-permission request.
+ if (requestedHealthPermissions.size() > 2) {
+ return false;
+ }
+
+ boolean isBackgroundPermissionRequested =
+ requestedHealthPermissions.contains(
+ HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
+ // If there are two health permissions declared, make sure the other is
+ // READ_HEALTH_DATA_IN_BACKGROUND.
+ if (requestedHealthPermissions.size() == 2 && !isBackgroundPermissionRequested) {
+ return false;
+ }
+
+ // If READ_HEALTH_DATA_IN_BACKGROUND is requested, check permission flag to see if is from
+ // split permission.
+ if (isBackgroundPermissionRequested) {
+ int readHealthDataInBackgroundFlag =
+ pm.getPermissionFlags(
+ HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND, packageName, user);
+ if (!isFromSplitPermission(readHealthDataInBackgroundFlag)) {
+ return false;
+ }
+ }
+
+ // Check READ_HEART_RATE permission flag to see if is from split permission.
+ int readHeartRateFlag =
+ pm.getPermissionFlags(HealthPermissions.READ_HEART_RATE, packageName, user);
+ return isFromSplitPermission(readHeartRateFlag);
+ }
+
+ private static boolean isFromSplitPermission(int permissionFlag) {
+ return (permissionFlag & FLAG_PERMISSION_REVOKE_WHEN_REQUESTED) != 0;
+ }
+
+ /**
* Get a device protected storage based shared preferences. Avoid storing sensitive data in it.
*
* @param context the context to get the shared preferences
@@ -1495,6 +1584,19 @@ public final class Utils {
context.startActivity(healthConnectIntent);
}
+
+ /**
+ * Navigate to health connect settings Wear privacy dashboard.
+ *
+ * @param context The current Context
+ */
+ public static void navigateToWearHealthConnectSettingsPrivacyDashboard(
+ @NonNull Context context) {
+ Intent privacyDashboardHealthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS);
+ privacyDashboardHealthConnectIntent.putExtra(EXTRA_REASON, "privacy_dashboard");
+ context.startActivity(privacyDashboardHealthConnectIntent);
+ }
+
/**
* Navigate to health connect settings for an app
* @param context The current Context
@@ -1566,18 +1668,40 @@ public final class Utils {
public static String getEnterpriseString(@NonNull Context context,
@NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) {
return SdkLevel.isAtLeastT()
- ? getUpdatableEnterpriseString(
- context, updatableStringId, defaultStringId, formatArgs)
+ ? getUpdatableEnterpriseString(context, updatableStringId,
+ () -> context.getString(defaultStringId, formatArgs), formatArgs)
: context.getString(defaultStringId, formatArgs);
}
+ /**
+ * Selects the appropriate enterprise string for the provided resource ID and a fallback string
+ */
+ @NonNull
+ public static String getEnterpriseString(@NonNull Context context,
+ @NonNull String updatableStringId, @NonNull String defaultString) {
+ return SdkLevel.isAtLeastT()
+ ? getUpdatableEnterpriseString(context, updatableStringId, () -> defaultString)
+ : defaultString;
+ }
+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@NonNull
private static String getUpdatableEnterpriseString(@NonNull Context context,
- @NonNull String updatableStringId, int defaultStringId, @NonNull Object... formatArgs) {
+ @NonNull String updatableStringId, @NonNull Supplier<String> defaultStringLoader,
+ @NonNull Object... formatArgs) {
DevicePolicyManager dpm = getSystemServiceSafe(context, DevicePolicyManager.class);
- return dpm.getResources().getString(updatableStringId, () -> context.getString(
- defaultStringId, formatArgs), formatArgs);
+ return dpm.getResources().getString(updatableStringId, defaultStringLoader, formatArgs);
+ }
+
+ /**
+ * Returns the profile label from the {@link UserManager} for the provided profile
+ */
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @NonNull
+ public static String getProfileLabel(@NonNull UserHandle profile, @NonNull Context context) {
+ Context profileContext = context.createContextAsUser(profile, 0);
+ UserManager profileUserManager = profileContext.getSystemService(UserManager.class);
+ return profileUserManager.getProfileLabel();
}
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java
index 4fde616e3..a3b885752 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java
@@ -54,6 +54,12 @@ public final class AdminRestrictedPermissionsUtils {
if (SdkLevel.isAtLeastT()) {
ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND);
}
+
+ }
+
+ /** Adds a new permission to the list of admin restricted permissions. */
+ public static void addAdminRestrictedPermission(String permission) {
+ ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add(permission);
}
/**
@@ -91,7 +97,6 @@ public final class AdminRestrictedPermissionsUtils {
boolean isAdminRestrictedSensorPermissionGroup = permissionGroup != null
&& PermissionMapping.getPlatformPermissionNamesOfGroup(permissionGroup).stream()
.anyMatch(ADMIN_RESTRICTED_SENSORS_PERMISSIONS::contains);
-
if (!ADMIN_RESTRICTED_SENSORS_PERMISSIONS.contains(permission)
&& !isAdminRestrictedSensorPermissionGroup) {
return true;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/Role.md b/PermissionController/src/com/android/permissioncontroller/role/Role.md
index d4a514784..29a184190 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/Role.md
+++ b/PermissionController/src/com/android/permissioncontroller/role/Role.md
@@ -55,13 +55,26 @@ apps, separated by a colon (`:`) with the package name, for instance
receive short text messages, photos, videos, and more". For default apps, this string will appear in
the default app detail page as a footer. This attribute is required if the role is `visible`.
- `exclusive`: Whether the role is exclusive. If a role is exclusive, at most one application is
-allowed to be its holder.
+allowed to be its holder. This attribute is being deprecated and `exclusivity` should be used.
+- `exclusivity`: Whether the role is exclusive and what type of exclusivity behavior it has. A role
+can have exclusivity of `none`, `user`, or `profileGroup`.
+ - `none`: Role allows multiple holders
+ - `user`: Role allows at most one holder within each user
+ - `profileGroup`: (SDK 36+ only, falls back to `user` on lower SDK) Role allows at most one holder
+within a profile group (e.g. full user and work profile)
- `fallBackToDefaultHolder`: Whether the role should fall back to the default holder. This attribute
is optional and defaults to `false`.
- `featureFlag`: Optional feature flag for the role be available, as the fully qualified name of
the Java method on the `Flags` class which will be invoked via reflection. Note that any new
aconfig library dependency will need corresponding jarjar rules for PermissionController and the
system service JAR.
+- `ignoreDisabledSystemPackageWhenGranting`: Whether the role should ignore the requested
+permissions of the disabled system package (if any) when granting permissions. If `false`, the
+permission will need to be requested by the disabled system package as well, if there is one. This
+attribute is optional and defaults to the opposite of `systemOnly` on Android S+, or `true` below
+Android S. **Note:** Extra care should be taken when adding a runtime permission to a role with
+this attribute explicitly set to `true`, because that may allow apps to update and silently obtain
+a new runtime permission.
- `label`: The string resource for the label of the role, e.g. `@string/role_sms_label`, which says
"Default SMS app". For default apps, this string will appear in the default app detail page as the
title. This attribute is required if the role is `visible`.
diff --git a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING
index 46b148e68..83b513c04 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING
@@ -7,6 +7,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases"
}
],
"mainline-presubmit": [
@@ -24,6 +27,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]"
}
],
"permission-mainline-presubmit": [
@@ -41,11 +47,17 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases"
}
],
"postsubmit": [
{
"name": "CtsRoleTestCases"
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases"
}
],
"mainline-postsubmit": [
@@ -60,6 +72,9 @@
"exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]"
}
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/UserPackage.java b/PermissionController/src/com/android/permissioncontroller/role/UserPackage.java
new file mode 100644
index 000000000..29f6904ea
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/UserPackage.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role;
+
+import android.content.pm.ApplicationInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.core.os.ParcelCompat;
+
+import java.util.Objects;
+
+/**
+ * Data class storing a {@link UserHandle} and a package name.
+ */
+public class UserPackage implements Parcelable {
+ @NonNull
+ public final UserHandle user;
+ @NonNull
+ public final String packageName;
+
+ private UserPackage(@NonNull UserHandle user, @NonNull String packageName) {
+ this.user = user;
+ this.packageName = packageName;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof UserPackage) {
+ UserPackage other = (UserPackage) obj;
+ return Objects.equals(user, other.user)
+ && Objects.equals(packageName, other.packageName);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(user, packageName);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeParcelable(user, 0);
+ out.writeString(packageName);
+ }
+
+ public static final Parcelable.Creator<UserPackage> CREATOR =
+ new Parcelable.Creator<>() {
+ @Override
+ public UserPackage createFromParcel(Parcel in) {
+ UserHandle user =
+ ParcelCompat.readParcelable(
+ in, UserHandle.class.getClassLoader(), UserHandle.class);
+ String packageName = in.readString();
+ if (user == null || packageName == null) {
+ return null;
+ }
+ return UserPackage.of(user, packageName);
+ }
+
+ @Override
+ public UserPackage[] newArray(int size) {
+ return new UserPackage[size];
+ }
+ };
+
+ /** Returns a {@link UserPackage} represented by the specified user and package name */
+ @NonNull
+ public static UserPackage of(@NonNull UserHandle user, @NonNull String packageName) {
+ return new UserPackage(user, packageName);
+ }
+
+ /** Returns a {@link UserPackage} using user and package name from an {@link ApplicationInfo} */
+ @NonNull
+ public static UserPackage from(@NonNull ApplicationInfo applicationInfo) {
+ return new UserPackage(
+ UserHandle.getUserHandleForUid(applicationInfo.uid), applicationInfo.packageName);
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java
index 2f515f02c..814a312c6 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java
@@ -16,6 +16,8 @@
package com.android.permissioncontroller.role.ui;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.ROLE_SETTINGS_FRAGMENT_ACTION_REPORTED;
+
import android.app.Activity;
import android.app.role.RoleManager;
import android.content.Context;
@@ -25,7 +27,6 @@ import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.ArrayMap;
-import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -33,12 +34,16 @@ import androidx.appcompat.content.res.AppCompatResources;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.Preference;
+import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.modules.utils.build.SdkLevel;
+import com.android.permission.flags.Flags;
+import com.android.permissioncontroller.PermissionControllerStatsLog;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.Utils;
import com.android.permissioncontroller.role.utils.PackageUtils;
@@ -46,6 +51,7 @@ import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
import com.android.permissioncontroller.role.utils.SettingsCompat;
import com.android.role.controller.model.Role;
import com.android.role.controller.model.Roles;
+import com.android.settingslib.utils.applications.AppUtils;
import java.util.List;
import java.util.Objects;
@@ -63,12 +69,20 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
implements DefaultAppConfirmationDialogFragment.Listener,
Preference.OnPreferenceClickListener {
+ private static final String PREFERENCE_KEY_RECOMMENDED_CATEGORY =
+ DefaultAppChildFragment.class.getName() + ".preference.RECOMMENDED_CATEGORY";
+ private static final String PREFERENCE_KEY_OTHERS_CATEGORY =
+ DefaultAppChildFragment.class.getName() + ".preference.OTHERS_CATEGORY";
private static final String PREFERENCE_KEY_NONE = DefaultAppChildFragment.class.getName()
+ ".preference.NONE";
private static final String PREFERENCE_KEY_DESCRIPTION = DefaultAppChildFragment.class.getName()
+ ".preference.DESCRIPTION";
private static final String PREFERENCE_KEY_OTHER_NFC_SERVICES =
DefaultAppChildFragment.class.getName() + ".preference.OTHER_NFC_SERVICES";
+ private static final String PREFERENCE_EXTRA_PACKAGE_NAME =
+ DefaultAppChildFragment.class.getName() + ".extra.PACKAGE_NAME";
+ private static final String PREFERENCE_EXTRA_UID = DefaultAppChildFragment.class.getName()
+ + ".extra.UID";
@NonNull
private String mRoleName;
@@ -121,52 +135,65 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
ViewModelProvider.Factory viewModelFactory = new DefaultAppViewModel.Factory(mRole, mUser,
activity.getApplication());
mViewModel = new ViewModelProvider(this, viewModelFactory).get(DefaultAppViewModel.class);
- mViewModel.getRoleLiveData().observe(this, this::onRoleChanged);
+ mViewModel.getRecommendedLiveData().observe(this,
+ applicationItems -> onApplicationListChanged());
+ mViewModel.getLiveData().observe(this, applicationItems -> onApplicationListChanged());
mViewModel.getManageRoleHolderStateLiveData().observe(this,
this::onManageRoleHolderStateChanged);
}
- private void onRoleChanged(
- @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) {
+ private void onApplicationListChanged() {
+ List<RoleApplicationItem> recommendedApplicationItems =
+ mViewModel.getRecommendedLiveData().getValue();
+ if (recommendedApplicationItems == null) {
+ return;
+ }
+ List<RoleApplicationItem> otherApplicationItems = mViewModel.getLiveData().getValue();
+ if (otherApplicationItems == null) {
+ return;
+ }
+
PF preferenceFragment = requirePreferenceFragment();
PreferenceManager preferenceManager = preferenceFragment.getPreferenceManager();
Context context = preferenceManager.getContext();
PreferenceScreen preferenceScreen = preferenceFragment.getPreferenceScreen();
- Preference oldDescriptionPreference = null;
+ PreferenceCategory oldRecommendedPreferenceCategory = null;
+ PreferenceCategory oldOthersPreferenceCategory = null;
ArrayMap<String, Preference> oldPreferences = new ArrayMap<>();
if (preferenceScreen == null) {
preferenceScreen = preferenceManager.createPreferenceScreen(context);
preferenceFragment.setPreferenceScreen(preferenceScreen);
} else {
- for (int i = preferenceScreen.getPreferenceCount() - 1; i >= 0; --i) {
- Preference preference = preferenceScreen.getPreference(i);
-
- preferenceScreen.removePreference(preference);
- preference.setOrder(Preference.DEFAULT_ORDER);
- oldPreferences.put(preference.getKey(), preference);
+ if (Flags.defaultAppsRecommendationEnabled()) {
+ oldRecommendedPreferenceCategory =
+ preferenceScreen.findPreference(PREFERENCE_KEY_RECOMMENDED_CATEGORY);
+ clearPreferenceCategory(oldRecommendedPreferenceCategory, oldPreferences);
+ oldOthersPreferenceCategory =
+ preferenceScreen.findPreference(PREFERENCE_KEY_OTHERS_CATEGORY);
+ clearPreferenceCategory(oldOthersPreferenceCategory, oldPreferences);
}
+ clearPreferences(preferenceScreen, oldPreferences);
}
- if (mRole.shouldShowNone()) {
- Drawable icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle);
- String title = getString(R.string.default_app_none);
- boolean noHolderApplication = !hasHolderApplication(qualifyingApplications);
- addPreference(PREFERENCE_KEY_NONE, icon, title, noHolderApplication, null,
- oldPreferences, preferenceScreen, context);
- }
-
- int qualifyingApplicationsSize = qualifyingApplications.size();
- for (int i = 0; i < qualifyingApplicationsSize; i++) {
- Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get(i);
- ApplicationInfo qualifyingApplicationInfo = qualifyingApplication.first;
- boolean isHolderApplication = qualifyingApplication.second;
-
- String key = qualifyingApplicationInfo.packageName;
- Drawable icon = Utils.getBadgedIcon(context, qualifyingApplicationInfo);
- String title = Utils.getFullAppLabel(qualifyingApplicationInfo, context);
- addPreference(key, icon, title, isHolderApplication, qualifyingApplicationInfo,
- oldPreferences, preferenceScreen, context);
+ if (Flags.defaultAppsRecommendationEnabled() && !recommendedApplicationItems.isEmpty()) {
+ addApplicationPreferenceCategory(oldRecommendedPreferenceCategory,
+ PREFERENCE_KEY_RECOMMENDED_CATEGORY,
+ getString(R.string.default_app_recommended), preferenceScreen, false, false,
+ recommendedApplicationItems, oldPreferences, context);
+ if (mRole.shouldShowNone() || !otherApplicationItems.isEmpty()) {
+ boolean noneChecked = !(hasHolderApplication(recommendedApplicationItems)
+ || hasHolderApplication(otherApplicationItems));
+ addApplicationPreferenceCategory(oldOthersPreferenceCategory,
+ PREFERENCE_KEY_OTHERS_CATEGORY, getString(R.string.default_app_others),
+ preferenceScreen, true, noneChecked, otherApplicationItems, oldPreferences,
+ context);
+ }
+ } else {
+ boolean noneChecked = !hasHolderApplication(otherApplicationItems);
+ addNonePreferenceIfNeeded(preferenceScreen, noneChecked, oldPreferences, context);
+ addApplicationPreferences(preferenceScreen, otherApplicationItems, oldPreferences,
+ context);
}
addNonPaymentNfcServicesPreference(preferenceScreen, oldPreferences, context);
@@ -175,24 +202,93 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
preferenceFragment.onPreferenceScreenChanged();
}
- private static boolean hasHolderApplication(
- @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) {
- int qualifyingApplicationsSize = qualifyingApplications.size();
- for (int i = 0; i < qualifyingApplicationsSize; i++) {
- Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get(i);
- boolean isHolderApplication = qualifyingApplication.second;
+ private static void clearPreferenceCategory(@Nullable PreferenceCategory preferenceCategory,
+ @NonNull ArrayMap<String, Preference> oldPreferences) {
+ if (preferenceCategory == null) {
+ return;
+ }
+ clearPreferences(preferenceCategory, oldPreferences);
+ preferenceCategory.getParent().removePreference(preferenceCategory);
+ preferenceCategory.setOrder(Preference.DEFAULT_ORDER);
+ }
+
+ private static void clearPreferences(@NonNull PreferenceGroup preferenceGroup,
+ @NonNull ArrayMap<String, Preference> oldPreferences) {
+ for (int i = preferenceGroup.getPreferenceCount() - 1; i >= 0; --i) {
+ Preference preference = preferenceGroup.getPreference(i);
- if (isHolderApplication) {
+ preferenceGroup.removePreference(preference);
+ preference.setOrder(Preference.DEFAULT_ORDER);
+ oldPreferences.put(preference.getKey(), preference);
+ }
+ }
+
+ private void addApplicationPreferenceCategory(
+ @Nullable PreferenceCategory oldPreferenceCategory, @NonNull String key,
+ @Nullable String title, @NonNull PreferenceScreen preferenceScreen,
+ boolean addNonePreferenceIfNeeded, boolean noneChecked,
+ @NonNull List<RoleApplicationItem> applicationItems,
+ @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context) {
+ PreferenceCategory preferenceCategory = oldPreferenceCategory;
+ if (preferenceCategory == null) {
+ preferenceCategory = new PreferenceCategory(context);
+ preferenceCategory.setKey(key);
+ preferenceCategory.setTitle(title);
+ }
+ preferenceScreen.addPreference(preferenceCategory);
+ if (addNonePreferenceIfNeeded) {
+ addNonePreferenceIfNeeded(preferenceCategory, noneChecked, oldPreferences, context);
+ }
+ addApplicationPreferences(preferenceCategory, applicationItems, oldPreferences, context);
+ }
+
+ private static boolean hasHolderApplication(
+ @NonNull List<RoleApplicationItem> applicationItems) {
+ int applicationItemsSize = applicationItems.size();
+ for (int i = 0; i < applicationItemsSize; i++) {
+ RoleApplicationItem applicationItem = applicationItems.get(i);
+ if (applicationItem.isHolderApplication()) {
return true;
}
}
return false;
}
- private void addPreference(@NonNull String key, @NonNull Drawable icon,
- @NonNull CharSequence title, boolean checked, @Nullable ApplicationInfo applicationInfo,
- @NonNull ArrayMap<String, Preference> oldPreferences,
- @NonNull PreferenceScreen preferenceScreen, @NonNull Context context) {
+ private void addNonePreferenceIfNeeded(@NonNull PreferenceGroup preferenceGroup,
+ boolean checked, @NonNull ArrayMap<String, Preference> oldPreferences,
+ @NonNull Context context) {
+ if (!mRole.shouldShowNone()) {
+ return;
+ }
+
+ Drawable icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle);
+ String title = getString(R.string.default_app_none);
+ addApplicationPreference(preferenceGroup, PREFERENCE_KEY_NONE, icon, title, checked, null,
+ oldPreferences, context);
+ }
+
+ private void addApplicationPreferences(@NonNull PreferenceGroup preferenceGroup,
+ @NonNull List<RoleApplicationItem> applicationItems,
+ @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context) {
+ int applicationItemsSize = applicationItems.size();
+ for (int i = 0; i < applicationItemsSize; i++) {
+ RoleApplicationItem applicationItem = applicationItems.get(i);
+ ApplicationInfo applicationInfo = applicationItem.getApplicationInfo();
+ int userId = UserHandle.getUserHandleForUid(applicationInfo.uid).getIdentifier();
+ String key = applicationInfo.packageName + "@" + userId;
+ Drawable icon = Utils.getBadgedIcon(context, applicationInfo);
+ String title = Utils.getFullAppLabel(applicationInfo, context);
+ boolean isHolderApplication = applicationItem.isHolderApplication();
+
+ addApplicationPreference(preferenceGroup, key, icon, title, isHolderApplication,
+ applicationInfo, oldPreferences, context);
+ }
+ }
+
+ private void addApplicationPreference(@NonNull PreferenceGroup preferenceGroup,
+ @NonNull String key, @NonNull Drawable icon, @NonNull CharSequence title,
+ boolean checked, @Nullable ApplicationInfo applicationInfo,
+ @NonNull ArrayMap<String, Preference> oldPreferences, @NonNull Context context) {
RoleApplicationPreference roleApplicationPreference =
(RoleApplicationPreference) oldPreferences.get(key);
TwoStatePreference preference;
@@ -205,19 +301,33 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
preference.setPersistent(false);
preference.setOnPreferenceChangeListener((preference2, newValue) -> false);
preference.setOnPreferenceClickListener(this);
+ // In the cases we need this (see #onPreferenceClick()), this should never be null.
+ // This method (addPreference) is used for both legitimate apps and the `NONE` item,
+ // the `NONE` item passes a null applicationinfo object. NFC uses a different preference
+ // method for adding, and a different onclick method
+ if (applicationInfo != null) {
+ UserHandle user = UserHandle.getUserHandleForUid(applicationInfo.uid);
+ roleApplicationPreference.setContentDescription(
+ AppUtils.getAppContentDescription(
+ context, applicationInfo.packageName, user.getIdentifier()));
+ Bundle extras = preference.getExtras();
+ extras.putString(PREFERENCE_EXTRA_PACKAGE_NAME, applicationInfo.packageName);
+ extras.putInt(PREFERENCE_EXTRA_UID, applicationInfo.uid);
+ }
} else {
preference = roleApplicationPreference.asTwoStatePreference();
}
preference.setChecked(checked);
if (applicationInfo != null) {
+ UserHandle user = UserHandle.getUserHandleForUid(applicationInfo.uid);
roleApplicationPreference.setRestrictionIntent(
- mRole.getApplicationRestrictionIntentAsUser(applicationInfo, mUser, context));
+ mRole.getApplicationRestrictionIntentAsUser(applicationInfo, user, context));
RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, roleApplicationPreference,
- applicationInfo, mUser, context);
+ applicationInfo, user, context);
}
- preferenceScreen.addPreference(preference);
+ preferenceGroup.addPreference(preference);
}
private void onManageRoleHolderStateChanged(int state) {
@@ -243,22 +353,27 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
if (Objects.equals(key, PREFERENCE_KEY_NONE)) {
mViewModel.setNoneDefaultApp();
} else {
- String packageName = key;
+ String packageName =
+ preference.getExtras().getString(PREFERENCE_EXTRA_PACKAGE_NAME);
+ int uid = preference.getExtras().getInt(PREFERENCE_EXTRA_UID);
CharSequence confirmationMessage =
RoleUiBehaviorUtils.getConfirmationMessage(mRole, packageName,
requireContext());
if (confirmationMessage != null) {
- DefaultAppConfirmationDialogFragment.show(packageName, confirmationMessage, this);
+ DefaultAppConfirmationDialogFragment.show(packageName, uid, confirmationMessage,
+ this);
} else {
- setDefaultApp(packageName);
+ setDefaultApp(packageName, uid);
}
}
return true;
}
@Override
- public void setDefaultApp(@NonNull String packageName) {
- mViewModel.setDefaultApp(packageName);
+ public void setDefaultApp(@NonNull String packageName, int uid) {
+ PermissionControllerStatsLog.write(
+ ROLE_SETTINGS_FRAGMENT_ACTION_REPORTED, uid, packageName, mRoleName);
+ mViewModel.setDefaultApp(packageName, UserHandle.getUserHandleForUid(uid));
}
private void addNonPaymentNfcServicesPreference(@NonNull PreferenceScreen preferenceScreen,
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppConfirmationDialogFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppConfirmationDialogFragment.java
index 843854bf4..9a9606291 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppConfirmationDialogFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppConfirmationDialogFragment.java
@@ -32,24 +32,27 @@ import androidx.fragment.app.Fragment;
public class DefaultAppConfirmationDialogFragment extends DialogFragment {
private String mPackageName;
+ private int mUid;
private CharSequence mMessage;
/**
* Create a new instance of this fragment.
*
* @param packageName the package name of the application
+ * @param uid the UID the specified package is running in
* @param message the confirmation message
*
* @return a new instance of this fragment
*
- * @see #show(String, CharSequence, Fragment)
+ * @see #show(String, int, CharSequence, Fragment)
*/
@NonNull
public static DefaultAppConfirmationDialogFragment newInstance(@NonNull String packageName,
- @NonNull CharSequence message) {
+ int uid, @NonNull CharSequence message) {
DefaultAppConfirmationDialogFragment fragment = new DefaultAppConfirmationDialogFragment();
Bundle arguments = new Bundle();
arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
+ arguments.putInt(Intent.EXTRA_UID, uid);
arguments.putCharSequence(Intent.EXTRA_TEXT, message);
fragment.setArguments(arguments);
return fragment;
@@ -59,14 +62,15 @@ public class DefaultAppConfirmationDialogFragment extends DialogFragment {
* Show a new instance of this fragment.
*
* @param packageName the package name of the application
+ * @param uid the UID the specified package is running in
* @param message the confirmation message
* @param fragment the parent fragment
*
- * @see #newInstance(String, CharSequence)
+ * @see #newInstance(String, int, CharSequence)
*/
- public static void show(@NonNull String packageName, @NonNull CharSequence message,
- @NonNull Fragment fragment) {
- newInstance(packageName, message).show(fragment.getChildFragmentManager(), null);
+ public static void show(@NonNull String packageName, int uid,
+ @NonNull CharSequence message, @NonNull Fragment fragment) {
+ newInstance(packageName, uid, message).show(fragment.getChildFragmentManager(), null);
}
@Override
@@ -75,6 +79,7 @@ public class DefaultAppConfirmationDialogFragment extends DialogFragment {
Bundle arguments = getArguments();
mPackageName = arguments.getString(Intent.EXTRA_PACKAGE_NAME);
+ mUid = arguments.getInt(Intent.EXTRA_UID);
mMessage = arguments.getCharSequence(Intent.EXTRA_TEXT);
}
@@ -90,7 +95,7 @@ public class DefaultAppConfirmationDialogFragment extends DialogFragment {
private void onOk() {
Listener listener = (Listener) getParentFragment();
- listener.setDefaultApp(mPackageName);
+ listener.setDefaultApp(mPackageName, mUid);
}
/**
@@ -103,6 +108,6 @@ public class DefaultAppConfirmationDialogFragment extends DialogFragment {
*
* @param packageName the package name of the application
*/
- void setDefaultApp(@NonNull String packageName);
+ void setDefaultApp(@NonNull String packageName, int uid);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
index 0b96eb8ba..fd91d0d3b 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
@@ -22,6 +22,7 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.UserHandle;
+import android.permission.flags.Flags;
import android.provider.Settings;
import android.util.ArrayMap;
@@ -36,12 +37,15 @@ import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
+import com.android.modules.utils.build.SdkLevel;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.Utils;
+import com.android.permissioncontroller.role.UserPackage;
import com.android.permissioncontroller.role.utils.PackageUtils;
import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
import com.android.role.controller.model.Role;
import com.android.role.controller.model.Roles;
+import com.android.settingslib.utils.applications.AppUtils;
import java.util.List;
@@ -129,47 +133,55 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
} else {
oldWorkPreferenceCategory =
preferenceScreen.findPreference(PREFERENCE_KEY_WORK_CATEGORY);
- clearPreferenceCategory(
- oldWorkPreferenceCategory, preferenceScreen, oldWorkPreferences);
+ clearPreferenceCategory(oldWorkPreferenceCategory, oldWorkPreferences);
oldPrivatePreferenceCategory =
preferenceScreen.findPreference(PREFERENCE_KEY_PRIVATE_CATEGORY);
- clearPreferenceCategory(
- oldPrivatePreferenceCategory, preferenceScreen, oldPrivatePreferences);
+ clearPreferenceCategory(oldPrivatePreferenceCategory, oldPrivatePreferences);
clearPreferences(preferenceScreen, oldPreferences);
}
- addPreferences(preferenceScreen, roleItems, oldPreferences, this, mViewModel.getUser(),
+ addRolePreferences(preferenceScreen, roleItems, oldPreferences, this, mViewModel.getUser(),
context);
addMoreDefaultAppsPreference(preferenceScreen, oldPreferences, context);
addManageDomainUrlsPreference(preferenceScreen, oldPreferences, context);
if (hasWorkProfile && !workRoleItems.isEmpty()) {
+ String defaultWorkTitle;
+ if (SdkLevel.isAtLeastV() && Flags.useProfileLabelsForDefaultAppSectionTitles()) {
+ defaultWorkTitle = Utils.getProfileLabel(mViewModel.getWorkProfile(), context);
+ } else {
+ defaultWorkTitle = context.getString(R.string.default_apps_for_work);
+ }
String workTitle = Utils.getEnterpriseString(context,
- DefaultAppSettings.WORK_PROFILE_DEFAULT_APPS_TITLE,
- R.string.default_apps_for_work);
- addPreferenceCategory(oldWorkPreferenceCategory, PREFERENCE_KEY_WORK_CATEGORY,
+ DefaultAppSettings.WORK_PROFILE_DEFAULT_APPS_TITLE, defaultWorkTitle);
+ addRolePreferenceCategory(oldWorkPreferenceCategory, PREFERENCE_KEY_WORK_CATEGORY,
workTitle, preferenceScreen, workRoleItems, oldWorkPreferences, this,
mViewModel.getWorkProfile(), context);
}
if (hasPrivateProfile && !privateRoleItems.isEmpty()) {
- String privateTitle = context.getString(R.string.default_apps_for_private_profile);
- addPreferenceCategory(oldPrivatePreferenceCategory, PREFERENCE_KEY_PRIVATE_CATEGORY,
- privateTitle, preferenceScreen, privateRoleItems, oldPrivatePreferences, this,
- mViewModel.getPrivateProfile(), context);
+ String privateTitle;
+ if (SdkLevel.isAtLeastV() && Flags.useProfileLabelsForDefaultAppSectionTitles()) {
+ privateTitle = Utils.getProfileLabel(mViewModel.getPrivateProfile(), context);
+ } else {
+ privateTitle = context.getString(R.string.default_apps_for_private_profile);
+ }
+ addRolePreferenceCategory(oldPrivatePreferenceCategory,
+ PREFERENCE_KEY_PRIVATE_CATEGORY, privateTitle, preferenceScreen,
+ privateRoleItems, oldPrivatePreferences, this, mViewModel.getPrivateProfile(),
+ context);
}
preferenceFragment.onPreferenceScreenChanged();
}
private static void clearPreferenceCategory(@Nullable PreferenceCategory preferenceCategory,
- @NonNull PreferenceScreen preferenceScreen,
@NonNull ArrayMap<String, Preference> oldPreferences) {
if (preferenceCategory == null) {
return;
}
clearPreferences(preferenceCategory, oldPreferences);
- preferenceScreen.removePreference(preferenceCategory);
+ preferenceCategory.getParent().removePreference(preferenceCategory);
preferenceCategory.setOrder(Preference.DEFAULT_ORDER);
}
@@ -185,7 +197,7 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
}
@NonNull
- private void addPreferenceCategory(
+ private void addRolePreferenceCategory(
@Nullable PreferenceCategory oldPreferenceCategory, @NonNull String key,
@Nullable String title, @NonNull PreferenceScreen preferenceScreen,
@NonNull List<RoleItem> roleItems, @NonNull ArrayMap<String, Preference> oldPreferences,
@@ -198,11 +210,10 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
preferenceCategory.setTitle(title);
}
preferenceScreen.addPreference(preferenceCategory);
- addPreferences(preferenceCategory, roleItems, oldPreferences, listener,
- user, context);
+ addRolePreferences(preferenceCategory, roleItems, oldPreferences, listener, user, context);
}
- private void addPreferences(@NonNull PreferenceGroup preferenceGroup,
+ private void addRolePreferences(@NonNull PreferenceGroup preferenceGroup,
@NonNull List<RoleItem> roleItems, @NonNull ArrayMap<String, Preference> oldPreferences,
@NonNull Preference.OnPreferenceClickListener listener, @NonNull UserHandle user,
@NonNull Context context) {
@@ -232,10 +243,16 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
if (holderApplicationInfos.isEmpty()) {
preference.setIcon(null);
preference.setSummary(R.string.default_app_none);
+ rolePreference.setSummaryContentDescription(null);
} else {
ApplicationInfo holderApplicationInfo = holderApplicationInfos.get(0);
preference.setIcon(Utils.getBadgedIcon(context, holderApplicationInfo));
preference.setSummary(Utils.getAppLabel(holderApplicationInfo, context));
+ UserPackage userPackage = UserPackage.from(holderApplicationInfo);
+ rolePreference.setSummaryContentDescription(
+ AppUtils.getAppContentDescription(context,
+ userPackage.packageName,
+ userPackage.user.getIdentifier()));
}
RoleUiBehaviorUtils.preparePreferenceAsUser(role, holderApplicationInfos,
rolePreference, user, context);
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListViewModel.java
index 5bc25df54..4a280bd58 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListViewModel.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListViewModel.java
@@ -28,10 +28,15 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
+import com.android.permission.flags.Flags;
import com.android.permissioncontroller.permission.utils.Utils;
import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.util.RoleFlags;
import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
/**
* {@link ViewModel} for the list of default apps.
@@ -55,14 +60,54 @@ public class DefaultAppListViewModel extends AndroidViewModel {
super(application);
mUser = Process.myUserHandle();
+ UserHandle profileParent = UserUtils.getProfileParentOrSelf(mUser, application);
+ UserHandle workProfile = UserUtils.getWorkProfileOrSelf(application);
+ boolean isProfileParent = Objects.equals(mUser, profileParent);
+ boolean isWorkProfile = Objects.equals(mUser, workProfile);
+ RoleListLiveData liveData = new RoleListLiveData(true, mUser, application);
RoleListSortFunction sortFunction = new RoleListSortFunction(application);
- mLiveData = Transformations.map(new RoleListLiveData(true, mUser, application),
- sortFunction);
- mWorkProfile = UserUtils.getWorkProfile(application);
- mWorkLiveData = mWorkProfile != null ? Transformations.map(new RoleListLiveData(true,
- mWorkProfile, application), sortFunction) : null;
+ // Only show the work profile section if the current user is a full user
+ mWorkProfile = isProfileParent ? UserUtils.getWorkProfile(application) : null;
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ Predicate<RoleItem> exclusivityPredicate = roleItem ->
+ roleItem.getRole().getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP;
+ if (mWorkProfile != null) {
+ // Show profile group exclusive roles from work profile in primary group.
+ RoleListLiveData workLiveData =
+ new RoleListLiveData(true, mWorkProfile, application);
+ mLiveData = Transformations.map(
+ new MergeRoleListLiveData(liveData,
+ Transformations.map(workLiveData,
+ new ListLiveDataFilterFunction<>(exclusivityPredicate))),
+ sortFunction);
+ mWorkLiveData = Transformations.map(
+ Transformations.map(workLiveData,
+ new ListLiveDataFilterFunction<>(exclusivityPredicate.negate())),
+ sortFunction);
+ } else if (Flags.crossUserRoleUxBugfixEnabled() && isWorkProfile) {
+ // Show profile group exclusive roles from the profile parent (full user) in primary
+ // group when the current user (primary group) is a work profile
+ RoleListLiveData profileParentLiveData =
+ new RoleListLiveData(true, profileParent, application);
+ mLiveData = Transformations.map(
+ new MergeRoleListLiveData(liveData,
+ Transformations.map(profileParentLiveData,
+ new ListLiveDataFilterFunction<>(exclusivityPredicate))),
+ sortFunction);
+ mWorkLiveData = null;
+ } else {
+ mLiveData = Transformations.map(liveData, sortFunction);
+ mWorkLiveData = null;
+ }
+ } else {
+ mLiveData = Transformations.map(liveData, sortFunction);
+ mWorkLiveData = mWorkProfile != null ? Transformations.map(
+ new RoleListLiveData(true, mWorkProfile, application), sortFunction) : null;
+ }
- UserHandle privateProfile = UserUtils.getPrivateProfile(application);
+ // Only show the private profile section if the current user is a full user
+ UserHandle privateProfile =
+ isProfileParent ? UserUtils.getPrivateProfile(application) : null;
if (privateProfile != null && Utils.shouldShowInSettings(
privateProfile, application.getSystemService(UserManager.class))) {
mPrivateProfile = privateProfile;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java
index c89e1f71e..f4ba94f1d 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java
@@ -18,10 +18,8 @@ package com.android.permissioncontroller.role.ui;
import android.app.Application;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.os.UserHandle;
import android.util.Log;
-import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
@@ -30,9 +28,12 @@ import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.permissioncontroller.role.utils.UserUtils;
import com.android.role.controller.model.Role;
import java.util.List;
+import java.util.function.Predicate;
/**
* {@link ViewModel} for a default app.
@@ -47,7 +48,10 @@ public class DefaultAppViewModel extends AndroidViewModel {
private final UserHandle mUser;
@NonNull
- private final LiveData<List<Pair<ApplicationInfo, Boolean>>> mRoleLiveData;
+ private final LiveData<List<RoleApplicationItem>> mRecommendedLiveData;
+
+ @NonNull
+ private final LiveData<List<RoleApplicationItem>> mLiveData;
@NonNull
private final ManageRoleHolderStateLiveData mManageRoleHolderStateLiveData =
@@ -58,15 +62,43 @@ public class DefaultAppViewModel extends AndroidViewModel {
super(application);
mRole = role;
- mUser = user;
+ // If EXCLUSIVITY_PROFILE_GROUP this user should be profile parent
+ mUser = role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP
+ ? UserUtils.getProfileParentOrSelf(user, application)
+ : user;
+ RoleLiveData userLiveData = new RoleLiveData(role, mUser, application);
+ RoleSortFunction sortFunction = new RoleSortFunction(application);
+ LiveData<List<RoleApplicationItem>> liveData;
+ if (role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ // Context user might be work profile, ensure we get a non-null UserHandle if work
+ // profile exists. getWorkProfile returns null if context user is work profile.
+ UserHandle workProfile = UserUtils.getWorkProfileOrSelf(application);
+ if (workProfile != null) {
+ RoleLiveData workLiveData = new RoleLiveData(role, workProfile, application);
+ liveData = Transformations.map(new MergeRoleLiveData(userLiveData, workLiveData),
+ sortFunction);
+ } else {
+ liveData = Transformations.map(userLiveData, sortFunction);
+ }
+ } else {
+ liveData = Transformations.map(userLiveData, sortFunction);
+ }
+ Predicate<RoleApplicationItem> recommendedApplicationFilter =
+ RoleUiBehaviorUtils.getRecommendedApplicationFilter(role, application);
+ mRecommendedLiveData = Transformations.map(liveData,
+ new ListLiveDataFilterFunction<>(recommendedApplicationFilter));
+ mLiveData = Transformations.map(liveData,
+ new ListLiveDataFilterFunction<>(recommendedApplicationFilter.negate()));
+ }
- mRoleLiveData = Transformations.map(new RoleLiveData(mRole, mUser, application),
- new RoleSortFunction(application));
+ @NonNull
+ public LiveData<List<RoleApplicationItem>> getRecommendedLiveData() {
+ return mRecommendedLiveData;
}
@NonNull
- public LiveData<List<Pair<ApplicationInfo, Boolean>>> getRoleLiveData() {
- return mRoleLiveData;
+ public LiveData<List<RoleApplicationItem>> getLiveData() {
+ return mLiveData;
}
@NonNull
@@ -79,13 +111,13 @@ public class DefaultAppViewModel extends AndroidViewModel {
*
* @param packageName the package name of the application
*/
- public void setDefaultApp(@NonNull String packageName) {
+ public void setDefaultApp(@NonNull String packageName, @NonNull UserHandle user) {
if (mManageRoleHolderStateLiveData.getValue() != ManageRoleHolderStateLiveData.STATE_IDLE) {
Log.i(LOG_TAG, "Trying to set default app while another request is on-going");
return;
}
mManageRoleHolderStateLiveData.setRoleHolderAsUser(mRole.getName(), packageName, true, 0,
- mUser, getApplication());
+ user, getApplication());
}
/**
@@ -93,12 +125,15 @@ public class DefaultAppViewModel extends AndroidViewModel {
*/
public void setNoneDefaultApp() {
Context context = getApplication();
- mRole.onNoneHolderSelectedAsUser(mUser, context);
+ UserHandle user = mRole.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP
+ ? UserUtils.getProfileParentOrSelf(mUser, context)
+ : mUser;
+ mRole.onNoneHolderSelectedAsUser(user, context);
if (mManageRoleHolderStateLiveData.getValue() != ManageRoleHolderStateLiveData.STATE_IDLE) {
Log.i(LOG_TAG, "Trying to set default app while another request is on-going");
return;
}
- mManageRoleHolderStateLiveData.clearRoleHoldersAsUser(mRole.getName(), 0, mUser, context);
+ mManageRoleHolderStateLiveData.clearRoleHoldersAsUser(mRole.getName(), 0, user, context);
}
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/ListLiveDataFilterFunction.java b/PermissionController/src/com/android/permissioncontroller/role/ui/ListLiveDataFilterFunction.java
new file mode 100644
index 000000000..8657db0ab
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/ListLiveDataFilterFunction.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import kotlin.jvm.functions.Function1;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+/**
+ * A function for
+ * {@link androidx.lifecycle.Transformations#map(androidx.lifecycle.LiveData, Function1)}
+ * that filters a live data for a list.
+ *
+ * @param <T> the type of the list elements
+ */
+public class ListLiveDataFilterFunction<T> implements Function1<List<T>, List<T>> {
+ private final Predicate<T> mPredicate;
+
+ public ListLiveDataFilterFunction(@NonNull Predicate<T> predicate) {
+ mPredicate = predicate;
+ }
+
+ @NonNull
+ @Override
+ public List<T> invoke(@Nullable List<T> items) {
+ List<T> filteredItems = new ArrayList<>();
+ if (items != null) {
+ int itemsSize = items.size();
+ for (int i = 0; i < itemsSize; i++) {
+ T item = items.get(i);
+ if (mPredicate.test(item)) {
+ filteredItems.add(item);
+ }
+ }
+ }
+ return filteredItems;
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/ListLiveDataSortFunction.java b/PermissionController/src/com/android/permissioncontroller/role/ui/ListLiveDataSortFunction.java
new file mode 100644
index 000000000..36e7afcc8
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/ListLiveDataSortFunction.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui;
+
+import androidx.annotation.NonNull;
+
+import kotlin.jvm.functions.Function1;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A function for
+ * {@link androidx.lifecycle.Transformations#map(androidx.lifecycle.LiveData, Function1)}
+ * that sorts a live data for a list.
+ *
+ * @param <T> the type of the list elements
+ */
+public class ListLiveDataSortFunction<T> implements Function1<List<T>, List<T>> {
+
+ @NonNull
+ private final Comparator<T> mComparator;
+
+ public ListLiveDataSortFunction(@NonNull Comparator<T> comparator) {
+ mComparator = comparator;
+ }
+
+ @Override
+ public List<T> invoke(List<T> items) {
+ List<T> sortedItems = new ArrayList<>(items);
+ sortedItems.sort(mComparator);
+ return sortedItems;
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/MergeRoleListLiveData.java b/PermissionController/src/com/android/permissioncontroller/role/ui/MergeRoleListLiveData.java
index 0318800f4..0c2d96b2c 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/MergeRoleListLiveData.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/MergeRoleListLiveData.java
@@ -14,16 +14,14 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.ui.specialappaccess;
+package com.android.permissioncontroller.role.ui;
import android.util.ArrayMap;
import androidx.annotation.NonNull;
+import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
-import com.android.permissioncontroller.role.ui.RoleItem;
-import com.android.permissioncontroller.role.ui.RoleListLiveData;
-
import java.util.ArrayList;
import java.util.List;
@@ -33,14 +31,14 @@ import java.util.List;
public class MergeRoleListLiveData extends MediatorLiveData<List<RoleItem>> {
@NonNull
- private final RoleListLiveData[] mLiveDatas;
+ private final LiveData<List<RoleItem>>[] mLiveDatas;
- public MergeRoleListLiveData(@NonNull RoleListLiveData... liveDatas) {
+ public MergeRoleListLiveData(@NonNull LiveData<List<RoleItem>>... liveDatas) {
mLiveDatas = liveDatas;
int liveDatasLength = mLiveDatas.length;
for (int i = 0; i < liveDatasLength; i++) {
- RoleListLiveData liveData = mLiveDatas[i];
+ LiveData<List<RoleItem>> liveData = mLiveDatas[i];
addSource(liveData, roleItems -> onRoleListChanged());
}
@@ -50,7 +48,7 @@ public class MergeRoleListLiveData extends MediatorLiveData<List<RoleItem>> {
ArrayMap<String, RoleItem> mergedRoleItemMap = new ArrayMap<>();
int liveDatasLength = mLiveDatas.length;
for (int liveDatasIndex = 0; liveDatasIndex < liveDatasLength; liveDatasIndex++) {
- RoleListLiveData liveData = mLiveDatas[liveDatasIndex];
+ LiveData<List<RoleItem>> liveData = mLiveDatas[liveDatasIndex];
List<RoleItem> roleItems = liveData.getValue();
if (roleItems == null) {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/MergeRoleLiveData.java b/PermissionController/src/com/android/permissioncontroller/role/ui/MergeRoleLiveData.java
index dab59a1ab..72c2264ce 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/MergeRoleLiveData.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/MergeRoleLiveData.java
@@ -14,23 +14,18 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.ui.specialappaccess;
-
-import android.content.pm.ApplicationInfo;
-import android.util.Pair;
+package com.android.permissioncontroller.role.ui;
import androidx.annotation.NonNull;
import androidx.lifecycle.MediatorLiveData;
-import com.android.permissioncontroller.role.ui.RoleLiveData;
-
import java.util.ArrayList;
import java.util.List;
/**
* {@link MediatorLiveData} that merges multiple {@link RoleLiveData} instances.
*/
-public class MergeRoleLiveData extends MediatorLiveData<List<Pair<ApplicationInfo, Boolean>>> {
+public class MergeRoleLiveData extends MediatorLiveData<List<RoleApplicationItem>> {
@NonNull
private final RoleLiveData[] mLiveDatas;
@@ -42,23 +37,23 @@ public class MergeRoleLiveData extends MediatorLiveData<List<Pair<ApplicationInf
for (int i = 0; i < liveDatasLength; i++) {
RoleLiveData liveData = mLiveDatas[i];
- addSource(liveData, roleItems -> onRoleChanged());
+ addSource(liveData, items -> onRoleChanged());
}
}
private void onRoleChanged() {
- List<Pair<ApplicationInfo, Boolean>> mergedQualifyingApplications = new ArrayList<>();
+ List<RoleApplicationItem> mergedItems = new ArrayList<>();
int liveDatasLength = mLiveDatas.length;
for (int i = 0; i < liveDatasLength; i++) {
RoleLiveData liveData = mLiveDatas[i];
- List<Pair<ApplicationInfo, Boolean>> qualifyingApplications = liveData.getValue();
- if (qualifyingApplications == null) {
+ List<RoleApplicationItem> items = liveData.getValue();
+ if (items == null) {
return;
}
- mergedQualifyingApplications.addAll(qualifyingApplications);
+ mergedItems.addAll(items);
}
- setValue(mergedQualifyingApplications);
+ setValue(mergedItems);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java
index 97ed74c01..89a17004b 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java
@@ -30,7 +30,6 @@ import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
-import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -44,6 +43,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
+import androidx.core.os.BundleCompat;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
@@ -51,12 +51,15 @@ import com.android.permissioncontroller.PermissionControllerStatsLog;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.PackageRemovalMonitor;
import com.android.permissioncontroller.permission.utils.Utils;
+import com.android.permissioncontroller.role.UserPackage;
import com.android.permissioncontroller.role.model.UserDeniedManager;
import com.android.permissioncontroller.role.utils.PackageUtils;
import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
import com.android.permissioncontroller.role.utils.UiUtils;
+import com.android.permissioncontroller.role.utils.UserUtils;
import com.android.role.controller.model.Role;
import com.android.role.controller.model.Roles;
+import com.android.settingslib.utils.applications.AppUtils;
import java.util.ArrayList;
import java.util.List;
@@ -108,6 +111,7 @@ public class RequestRoleFragment extends DialogFragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setCancelable(false);
Bundle arguments = getArguments();
mPackageName = arguments.getString(Intent.EXTRA_PACKAGE_NAME);
@@ -233,7 +237,7 @@ public class RequestRoleFragment extends DialogFragment {
ViewModelProvider.Factory viewModelFactory = new RequestRoleViewModel.Factory(mRole,
requireActivity().getApplication());
mViewModel = new ViewModelProvider(this, viewModelFactory).get(RequestRoleViewModel.class);
- mViewModel.getRoleLiveData().observe(this, this::onRoleDataChanged);
+ mViewModel.getLiveData().observe(this, this::onApplicationListChanged);
mViewModel.getManageRoleHolderStateLiveData().observe(this,
this::onManageRoleHolderStateChanged);
}
@@ -269,9 +273,9 @@ public class RequestRoleFragment extends DialogFragment {
setDeniedOnceAndFinish();
}
- private void onRoleDataChanged(
- @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) {
- mAdapter.replace(qualifyingApplications);
+ private void onApplicationListChanged(
+ @NonNull List<RoleApplicationItem> applicationItems) {
+ mAdapter.replace(applicationItems);
updateUi();
}
@@ -293,29 +297,31 @@ public class RequestRoleFragment extends DialogFragment {
}
private void setRoleHolder() {
- String packageName = mAdapter.getCheckedPackageName();
+ UserPackage userPackage = mAdapter.getCheckedUserPackage();
Context context = requireContext();
- UserHandle user = Process.myUserHandle();
- if (packageName == null) {
+ if (userPackage == null) {
reportRequestResult(PermissionControllerStatsLog
.ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_GRANTED_ANOTHER,
null);
+ UserHandle user = mRole.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP
+ ? UserUtils.getProfileParentOrSelf(Process.myUserHandle(), context)
+ : Process.myUserHandle();
mRole.onNoneHolderSelectedAsUser(user, context);
mViewModel.getManageRoleHolderStateLiveData().clearRoleHoldersAsUser(mRoleName, 0, user,
context);
} else {
- boolean isRequestingApplication = Objects.equals(packageName, mPackageName);
+ boolean isRequestingApplication = isRequestingApplication(userPackage);
if (isRequestingApplication) {
reportRequestResult(PermissionControllerStatsLog
.ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED, null);
} else {
reportRequestResult(PermissionControllerStatsLog
.ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_GRANTED_ANOTHER,
- packageName);
+ userPackage);
}
int flags = isRequestingApplication ? RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP : 0;
mViewModel.getManageRoleHolderStateLiveData().setRoleHolderAsUser(mRoleName,
- packageName, true, flags, user, context);
+ userPackage.packageName, true, flags, userPackage.user, context);
}
}
@@ -329,11 +335,13 @@ public class RequestRoleFragment extends DialogFragment {
ManageRoleHolderStateLiveData liveData =
mViewModel.getManageRoleHolderStateLiveData();
String packageName = liveData.getLastPackageName();
- if (packageName != null) {
- mRole.onHolderSelectedAsUser(packageName, liveData.getLastUser(),
- requireContext());
+ UserHandle user = liveData.getLastUser();
+ UserPackage userPackage = packageName != null && user != null
+ ? UserPackage.of(user, packageName) : null;
+ if (userPackage != null) {
+ mRole.onHolderSelectedAsUser(packageName, user, requireContext());
}
- if (Objects.equals(packageName, mPackageName)) {
+ if (isRequestingApplication(userPackage)) {
Log.i(LOG_TAG, "Application added as a role holder, role: " + mRoleName
+ ", package: " + mPackageName);
clearDeniedSetResultOkAndFinish();
@@ -357,8 +365,8 @@ public class RequestRoleFragment extends DialogFragment {
boolean dontAskAgain = mDontAskAgainCheck != null && mDontAskAgainCheck.isChecked();
mAdapter.setDontAskAgain(dontAskAgain);
AlertDialog dialog = getDialog();
- boolean hasRoleData = mViewModel.getRoleLiveData().getValue() != null;
- dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled && hasRoleData
+ boolean hasApplicationList = mViewModel.getLiveData().getValue() != null;
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled && hasApplicationList
&& (dontAskAgain || !mAdapter.isHolderApplicationChecked()));
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setEnabled(enabled);
}
@@ -383,20 +391,23 @@ public class RequestRoleFragment extends DialogFragment {
requireActivity().finish();
}
- private void reportRequestResult(int result, @Nullable String grantedAnotherPackageName) {
- String holderPackageName = getHolderPackageName();
- reportRequestResult(getApplicationUid(mPackageName), mPackageName, mRoleName,
- getQualifyingApplicationCount(), getQualifyingApplicationUid(holderPackageName),
- holderPackageName, getQualifyingApplicationUid(grantedAnotherPackageName),
- grantedAnotherPackageName, result);
+ private void reportRequestResult(int result, @Nullable UserPackage grantedUserPackage) {
+ UserPackage holderUserPackage = getHolderUserPackage();
+ reportRequestResult(getRequestingApplicationUid(), mPackageName, mRoleName,
+ getQualifyingApplicationCount(),
+ getQualifyingApplicationUid(holderUserPackage),
+ holderUserPackage != null ? holderUserPackage.packageName : null,
+ getQualifyingApplicationUid(grantedUserPackage),
+ grantedUserPackage != null ? grantedUserPackage.packageName : null,
+ result);
}
- private int getApplicationUid(@NonNull String packageName) {
- int uid = getQualifyingApplicationUid(packageName);
+ private int getRequestingApplicationUid() {
+ int uid = getQualifyingApplicationUid(UserPackage.of(Process.myUserHandle(), mPackageName));
if (uid != -1) {
return uid;
}
- ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName,
+ ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(mPackageName,
requireActivity());
if (applicationInfo == null) {
return -1;
@@ -404,19 +415,19 @@ public class RequestRoleFragment extends DialogFragment {
return applicationInfo.uid;
}
- private int getQualifyingApplicationUid(@Nullable String packageName) {
- if (packageName == null || mAdapter == null) {
+ private int getQualifyingApplicationUid(@Nullable UserPackage userPackage) {
+ if (userPackage == null || mAdapter == null) {
return -1;
}
int count = mAdapter.getCount();
for (int i = 0; i < count; i++) {
- Pair<ApplicationInfo, Boolean> qualifyingApplication = mAdapter.getItem(i);
- if (qualifyingApplication == null) {
+ RoleApplicationItem applicationItem = mAdapter.getItem(i);
+ if (applicationItem == null) {
// Skip the "None" item.
continue;
}
- ApplicationInfo applicationInfo = qualifyingApplication.first;
- if (Objects.equals(applicationInfo.packageName, packageName)) {
+ ApplicationInfo applicationInfo = applicationItem.getApplicationInfo();
+ if (Objects.equals(UserPackage.from(applicationInfo), userPackage)) {
return applicationInfo.uid;
}
}
@@ -436,11 +447,15 @@ public class RequestRoleFragment extends DialogFragment {
}
@Nullable
- private String getHolderPackageName() {
+ private UserPackage getHolderUserPackage() {
if (mAdapter == null) {
return null;
}
- return mAdapter.mHolderPackageName;
+ return mAdapter.mHolderUserPackage;
+ }
+
+ private boolean isRequestingApplication(@Nullable UserPackage userPackage) {
+ return Objects.equals(userPackage, UserPackage.of(Process.myUserHandle(), mPackageName));
}
static void reportRequestResult(int requestingUid, String requestingPackageName,
@@ -466,8 +481,8 @@ public class RequestRoleFragment extends DialogFragment {
private static final String STATE_USER_CHECKED = Adapter.class.getName()
+ ".state.USER_CHECKED";
- private static final String STATE_CHECKED_PACKAGE_NAME = Adapter.class.getName()
- + ".state.CHECKED_PACKAGE_NAME";
+ private static final String STATE_CHECKED_USER_PACKAGE = Adapter.class.getName()
+ + ".state.CHECKED_USER_PACKAGE";
private static final int LAYOUT_TRANSITION_DURATION_MILLIS = 150;
@@ -479,11 +494,10 @@ public class RequestRoleFragment extends DialogFragment {
// We'll use a null to represent the "None" item.
@NonNull
- private final List<Pair<ApplicationInfo, Boolean>> mQualifyingApplications =
- new ArrayList<>();
+ private final List<RoleApplicationItem> mApplicationItems = new ArrayList<>();
@Nullable
- private String mHolderPackageName;
+ private UserPackage mHolderUserPackage;
private boolean mDontAskAgain;
@@ -492,7 +506,7 @@ public class RequestRoleFragment extends DialogFragment {
private boolean mUserChecked;
@Nullable
- private String mCheckedPackageName;
+ private UserPackage mCheckedUserPackage;
Adapter(@NonNull ListView listView, @NonNull Role role) {
mListView = listView;
@@ -502,14 +516,20 @@ public class RequestRoleFragment extends DialogFragment {
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBoolean(STATE_USER_CHECKED, mUserChecked);
if (mUserChecked) {
- outState.putString(STATE_CHECKED_PACKAGE_NAME, mCheckedPackageName);
+ if (mCheckedUserPackage != null) {
+ outState.putParcelable(STATE_CHECKED_USER_PACKAGE, mCheckedUserPackage);
+ } else {
+ outState.remove(STATE_CHECKED_USER_PACKAGE);
+ }
}
}
public void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
mUserChecked = savedInstanceState.getBoolean(STATE_USER_CHECKED);
if (mUserChecked) {
- mCheckedPackageName = savedInstanceState.getString(STATE_CHECKED_PACKAGE_NAME);
+ mCheckedUserPackage =
+ BundleCompat.getParcelable(savedInstanceState, STATE_CHECKED_USER_PACKAGE,
+ UserPackage.class);
}
}
@@ -520,50 +540,52 @@ public class RequestRoleFragment extends DialogFragment {
mDontAskAgain = dontAskAgain;
if (mDontAskAgain) {
mUserChecked = false;
- mCheckedPackageName = mHolderPackageName;
+ mCheckedUserPackage = mHolderUserPackage;
}
notifyDataSetChanged();
}
public void onItemClicked(int position) {
- Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position);
- if (qualifyingApplication == null) {
+ RoleApplicationItem applicationItem = getItem(position);
+ if (applicationItem == null) {
mUserChecked = true;
- mCheckedPackageName = null;
+ mCheckedUserPackage = null;
} else {
- ApplicationInfo applicationInfo = qualifyingApplication.first;
+ ApplicationInfo applicationInfo = applicationItem.getApplicationInfo();
+ UserHandle user = UserHandle.getUserHandleForUid(applicationInfo.uid);
Intent restrictionIntent = mRole.getApplicationRestrictionIntentAsUser(
- applicationInfo, Process.myUserHandle(), mListView.getContext());
+ applicationInfo, user, mListView.getContext());
if (restrictionIntent != null) {
mListView.getContext().startActivity(restrictionIntent);
return;
} else {
mUserChecked = true;
- mCheckedPackageName = applicationInfo.packageName;
+ mCheckedUserPackage = UserPackage.from(applicationInfo);
}
}
notifyDataSetChanged();
}
- public void replace(@NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) {
- mQualifyingApplications.clear();
+ public void replace(@NonNull List<RoleApplicationItem> applicationItems) {
+ mApplicationItems.clear();
if (mRole.shouldShowNone()) {
- mQualifyingApplications.add(0, null);
+ mApplicationItems.add(0, null);
}
- mQualifyingApplications.addAll(qualifyingApplications);
- mHolderPackageName = getHolderPackageName(qualifyingApplications);
+ mApplicationItems.addAll(applicationItems);
+ mHolderUserPackage = getHolderUserPackage(applicationItems);
- if (mUserChecked && mCheckedPackageName != null) {
+ if (mUserChecked && mCheckedUserPackage != null) {
boolean isCheckedPackageNameFound = false;
int count = getCount();
for (int i = 0; i < count; i++) {
- Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(i);
- if (qualifyingApplication == null) {
+ RoleApplicationItem applicationItem = getItem(i);
+ if (applicationItem == null) {
continue;
}
- String packageName = qualifyingApplication.first.packageName;
+ UserPackage userPackage =
+ UserPackage.from(applicationItem.getApplicationInfo());
- if (Objects.equals(packageName, mCheckedPackageName)) {
+ if (Objects.equals(userPackage, mCheckedUserPackage)) {
mUserChecked = true;
isCheckedPackageNameFound = true;
break;
@@ -571,44 +593,40 @@ public class RequestRoleFragment extends DialogFragment {
}
if (!isCheckedPackageNameFound) {
mUserChecked = false;
- mCheckedPackageName = null;
+ mCheckedUserPackage = null;
}
}
if (!mUserChecked) {
- mCheckedPackageName = mHolderPackageName;
+ mCheckedUserPackage = mHolderUserPackage;
}
notifyDataSetChanged();
}
@Nullable
- private static String getHolderPackageName(
- @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) {
- int qualifyingApplicationSize = qualifyingApplications.size();
- for (int i = 0; i < qualifyingApplicationSize; i++) {
- Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get(
- i);
- if (qualifyingApplication == null) {
+ private static UserPackage getHolderUserPackage(
+ @NonNull List<RoleApplicationItem> applicationItems) {
+ int applicationItemSize = applicationItems.size();
+ for (int i = 0; i < applicationItemSize; i++) {
+ RoleApplicationItem applicationItem = applicationItems.get(i);
+ if (applicationItem == null) {
continue;
}
- ApplicationInfo applicationInfo = qualifyingApplication.first;
- boolean isHolderApplication = qualifyingApplication.second;
-
- if (isHolderApplication) {
- return applicationInfo.packageName;
+ if (applicationItem.isHolderApplication()) {
+ return UserPackage.from(applicationItem.getApplicationInfo());
}
}
return null;
}
@Nullable
- public String getCheckedPackageName() {
- return mCheckedPackageName;
+ public UserPackage getCheckedUserPackage() {
+ return mCheckedUserPackage;
}
public boolean isHolderApplicationChecked() {
- return Objects.equals(mCheckedPackageName, mHolderPackageName);
+ return Objects.equals(mCheckedUserPackage, mHolderUserPackage);
}
@Override
@@ -623,13 +641,13 @@ public class RequestRoleFragment extends DialogFragment {
@Override
public int getCount() {
- return mQualifyingApplications.size();
+ return mApplicationItems.size();
}
@Nullable
@Override
- public Pair<ApplicationInfo, Boolean> getItem(int position) {
- return mQualifyingApplications.get(position);
+ public RoleApplicationItem getItem(int position) {
+ return mApplicationItems.get(position);
}
@Override
@@ -638,9 +656,9 @@ public class RequestRoleFragment extends DialogFragment {
// Work around AbsListView.confirmCheckedPositionsById() not respecting our count.
return ListView.INVALID_ROW_ID;
}
- Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position);
- return qualifyingApplication == null ? 0
- : qualifyingApplication.first.packageName.hashCode();
+ RoleApplicationItem applicationItem = getItem(position);
+ return applicationItem == null ? 0
+ : applicationItem.getApplicationInfo().packageName.hashCode();
}
@Override
@@ -648,12 +666,11 @@ public class RequestRoleFragment extends DialogFragment {
if (!mDontAskAgain) {
return true;
}
- Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position);
- if (qualifyingApplication == null) {
- return mHolderPackageName == null;
+ RoleApplicationItem applicationItem = getItem(position);
+ if (applicationItem == null) {
+ return mHolderUserPackage == null;
} else {
- boolean isHolderApplication = qualifyingApplication.second;
- return isHolderApplication;
+ return applicationItem.isHolderApplication();
}
}
@@ -675,45 +692,53 @@ public class RequestRoleFragment extends DialogFragment {
LAYOUT_TRANSITION_DURATION_MILLIS);
}
- Pair<ApplicationInfo, Boolean> qualifyingApplication = getItem(position);
+ RoleApplicationItem applicationItem = getItem(position);
ApplicationInfo applicationInfo;
boolean restricted;
boolean checked;
Drawable icon;
String title;
String subtitle;
- if (qualifyingApplication == null) {
+ String contentDescription;
+ if (applicationItem == null) {
applicationInfo = null;
restricted = false;
- checked = mCheckedPackageName == null;
+ checked = mCheckedUserPackage == null;
icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle);
title = context.getString(R.string.default_app_none);
- subtitle = mHolderPackageName != null ? context.getString(
+ subtitle = mHolderUserPackage == null ? context.getString(
R.string.request_role_current_default) : null;
+ contentDescription = null;
} else {
- applicationInfo = qualifyingApplication.first;
+ applicationInfo = applicationItem.getApplicationInfo();
+ UserPackage userPackage = UserPackage.from(applicationInfo);
restricted = mRole.getApplicationRestrictionIntentAsUser(applicationInfo,
- Process.myUserHandle(), context) != null;
- checked = Objects.equals(applicationInfo.packageName, mCheckedPackageName);
+ userPackage.user, context) != null;
+ checked = Objects.equals(userPackage, mCheckedUserPackage);
icon = Utils.getBadgedIcon(context, applicationInfo);
title = Utils.getAppLabel(applicationInfo, context);
- boolean isHolderApplication = qualifyingApplication.second;
- subtitle = isHolderApplication
+ subtitle = applicationItem.isHolderApplication()
? context.getString(R.string.request_role_current_default)
: checked ? context.getString(mRole.getRequestDescriptionResource()) : null;
+ contentDescription = AppUtils.getAppContentDescription(context,
+ userPackage.packageName, userPackage.user.getIdentifier());
}
boolean enabled = isEnabled(position);
UiUtils.setViewTreeEnabled(view, enabled && !restricted);
view.setEnabled(enabled);
view.setChecked(checked);
+
holder.iconImage.setImageDrawable(icon);
holder.titleText.setText(title);
+ holder.titleText.setContentDescription(contentDescription);
holder.subtitleText.setVisibility(!TextUtils.isEmpty(subtitle) ? View.VISIBLE
: View.GONE);
holder.subtitleText.setText(subtitle);
- RoleUiBehaviorUtils.prepareRequestRoleItemViewAsUser(mRole, holder, applicationInfo,
- Process.myUserHandle(), context);
+ if (applicationInfo != null) {
+ RoleUiBehaviorUtils.prepareRequestRoleItemViewAsUser(mRole, holder, applicationInfo,
+ UserHandle.getUserHandleForUid(applicationInfo.uid), context);
+ }
return view;
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationItem.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationItem.java
new file mode 100644
index 000000000..b6c489ea1
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationItem.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui;
+
+import android.content.pm.ApplicationInfo;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Information about an application to be displayed in a list of applications qualifying for a role.
+ */
+public class RoleApplicationItem {
+
+ /**
+ * The {@link ApplicationInfo} for this application.
+ */
+ @NonNull
+ private final ApplicationInfo mApplicationInfo;
+
+ /**
+ * Whether this application is holding the role.
+ */
+ private final boolean mIsHolderApplication;
+
+ public RoleApplicationItem(@NonNull ApplicationInfo applicationInfo,
+ boolean isHolderApplication) {
+ mApplicationInfo = applicationInfo;
+ mIsHolderApplication = isHolderApplication;
+ }
+
+ @NonNull
+ public ApplicationInfo getApplicationInfo() {
+ return mApplicationInfo;
+ }
+
+ public boolean isHolderApplication() {
+ return mIsHolderApplication;
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationPreference.java
index 1d3e32c9c..3336aad50 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleApplicationPreference.java
@@ -17,6 +17,7 @@
package com.android.permissioncontroller.role.ui;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.TwoStatePreference;
/**
@@ -29,4 +30,9 @@ public interface RoleApplicationPreference extends RestrictionAwarePreference {
*/
@NonNull
TwoStatePreference asTwoStatePreference();
+
+ /**
+ * Sets the preference's content description.
+ */
+ void setContentDescription(@Nullable String contentDescription);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListSortFunction.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListSortFunction.java
index ca059aa32..07b4db1f4 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListSortFunction.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListSortFunction.java
@@ -23,30 +23,23 @@ import androidx.annotation.NonNull;
import kotlin.jvm.functions.Function1;
-import java.util.ArrayList;
import java.util.Comparator;
-import java.util.List;
/**
- * A function for {@link androidx.lifecycle#map(androidx.lifecycle.LiveData, Function1)}
+ * A function for
+ * {@link androidx.lifecycle.Transformations#map(androidx.lifecycle.LiveData, Function1)}
* that sorts a live data for role list.
*/
-public class RoleListSortFunction implements Function1<List<RoleItem>, List<RoleItem>> {
-
- @NonNull
- private final Comparator<RoleItem> mComparator;
+public class RoleListSortFunction extends ListLiveDataSortFunction<RoleItem> {
public RoleListSortFunction(@NonNull Context context) {
- Collator collator = Collator.getInstance(context.getResources().getConfiguration()
- .getLocales().get(0));
- mComparator = Comparator.comparing(roleItem -> context.getString(
- roleItem.getRole().getShortLabelResource()), collator);
+ super(createComparator(context));
}
- @Override
- public List<RoleItem> invoke(List<RoleItem> p1) {
- List<RoleItem> sorted = new ArrayList<>(p1);
- sorted.sort(mComparator);
- return sorted;
+ private static Comparator<RoleItem> createComparator(@NonNull Context context) {
+ Collator collator = Collator.getInstance(context.getResources().getConfiguration()
+ .getLocales().get(0));
+ return Comparator.comparing(item -> context.getString(
+ item.getRole().getShortLabelResource()), collator);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleLiveData.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleLiveData.java
index bb492f76d..1b6d42934 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleLiveData.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleLiveData.java
@@ -22,7 +22,6 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.UserHandle;
import android.util.Log;
-import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
@@ -38,7 +37,7 @@ import java.util.List;
/**
* {@link LiveData} for a role.
*/
-public class RoleLiveData extends AsyncTaskLiveData<List<Pair<ApplicationInfo, Boolean>>>
+public class RoleLiveData extends AsyncTaskLiveData<List<RoleApplicationItem>>
implements OnRoleHoldersChangedListener {
private static final String LOG_TAG = RoleLiveData.class.getSimpleName();
@@ -77,12 +76,12 @@ public class RoleLiveData extends AsyncTaskLiveData<List<Pair<ApplicationInfo, B
@Override
@WorkerThread
- protected List<Pair<ApplicationInfo, Boolean>> loadValueInBackground() {
+ protected List<RoleApplicationItem> loadValueInBackground() {
RoleManager roleManager = mContext.getSystemService(RoleManager.class);
List<String> holderPackageNames = roleManager.getRoleHoldersAsUser(mRole.getName(), mUser);
List<String> qualifyingPackageNames = mRole.getQualifyingPackagesAsUser(mUser, mContext);
- List<Pair<ApplicationInfo, Boolean>> qualifyingApplications = new ArrayList<>();
+ List<RoleApplicationItem> applicationItems = new ArrayList<>();
int qualifyingPackageNamesSize = qualifyingPackageNames.size();
for (int i = 0; i < qualifyingPackageNamesSize; i++) {
String qualifyingPackageName = qualifyingPackageNames.get(i);
@@ -98,9 +97,10 @@ public class RoleLiveData extends AsyncTaskLiveData<List<Pair<ApplicationInfo, B
continue;
}
boolean isHolderApplication = holderPackageNames.contains(qualifyingPackageName);
- qualifyingApplications.add(new Pair<>(qualifyingApplicationInfo, isHolderApplication));
+ applicationItems.add(
+ new RoleApplicationItem(qualifyingApplicationInfo, isHolderApplication));
}
- return qualifyingApplications;
+ return applicationItems;
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RolePreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RolePreference.java
index bbc123cfe..feafb0ae5 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RolePreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RolePreference.java
@@ -17,6 +17,7 @@
package com.android.permissioncontroller.role.ui;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
/**
@@ -28,4 +29,9 @@ public interface RolePreference extends TwoTargetPreference, RestrictionAwarePre
*/
@NonNull
Preference asPreference();
+
+ /**
+ * Sets the preference's summary content description.
+ */
+ void setSummaryContentDescription(@Nullable String summaryContentDescription);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleSortFunction.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleSortFunction.java
index 10db9dbcd..9a06a6b01 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleSortFunction.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleSortFunction.java
@@ -17,10 +17,8 @@
package com.android.permissioncontroller.role.ui;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.icu.text.Collator;
import android.os.UserHandle;
-import android.util.Pair;
import androidx.annotation.NonNull;
@@ -28,34 +26,26 @@ import com.android.permissioncontroller.permission.utils.Utils;
import kotlin.jvm.functions.Function1;
-import java.util.ArrayList;
import java.util.Comparator;
-import java.util.List;
/**
- * A function for {@link androidx.lifecycle#map(androidx.lifecycle.LiveData, Function1)}
+ * A function for
+ * {@link androidx.lifecycle.Transformations#map(androidx.lifecycle.LiveData, Function1)}
* that sorts a live data for role.
*/
-public class RoleSortFunction implements Function1<List<Pair<ApplicationInfo, Boolean>>,
- List<Pair<ApplicationInfo, Boolean>>> {
-
- @NonNull
- private final Comparator<Pair<ApplicationInfo, Boolean>> mComparator;
+public class RoleSortFunction extends ListLiveDataSortFunction<RoleApplicationItem> {
public RoleSortFunction(@NonNull Context context) {
- Collator collator = Collator.getInstance(context.getResources().getConfiguration()
- .getLocales().get(0));
- Comparator<Pair<ApplicationInfo, Boolean>> labelComparator = Comparator.comparing(role ->
- Utils.getAppLabel(role.first, context), collator);
- Comparator<Pair<ApplicationInfo, Boolean>> userIdComparator = Comparator.comparingInt(role
- -> UserHandle.getUserHandleForUid(role.first.uid).getIdentifier());
- mComparator = labelComparator.thenComparing(userIdComparator);
+ super(createComparator(context));
}
- @Override
- public List<Pair<ApplicationInfo, Boolean>> invoke(List<Pair<ApplicationInfo, Boolean>> p1) {
- List<Pair<ApplicationInfo, Boolean>> sorted = new ArrayList<>(p1);
- sorted.sort(mComparator);
- return sorted;
+ private static Comparator<RoleApplicationItem> createComparator(@NonNull Context context) {
+ Collator collator = Collator.getInstance(context.getResources().getConfiguration()
+ .getLocales().get(0));
+ Comparator<RoleApplicationItem> labelComparator = Comparator.comparing(item ->
+ Utils.getAppLabel(item.getApplicationInfo(), context), collator);
+ Comparator<RoleApplicationItem> userIdComparator = Comparator.comparingInt(item
+ -> UserHandle.getUserHandleForUid(item.getApplicationInfo().uid).getIdentifier());
+ return labelComparator.thenComparing(userIdComparator);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRadioPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRadioPreference.java
index 764c07497..f0a27284d 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRadioPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRadioPreference.java
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.role.ui.auto;
import android.content.Context;
import android.content.Intent;
import android.widget.RadioButton;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -30,10 +31,15 @@ import com.android.permissioncontroller.R;
import com.android.permissioncontroller.role.ui.RestrictionAwarePreferenceMixin;
import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
+import java.util.Objects;
+
/** Preference used to represent apps that can be picked as a default app. */
public class AutoRadioPreference extends TwoStatePreference implements
RoleApplicationPreference {
+ @Nullable
+ private String mContentDescription;
+
private final RestrictionAwarePreferenceMixin mRestrictionAwarePreferenceMixin =
new RestrictionAwarePreferenceMixin(this);
@@ -56,10 +62,21 @@ public class AutoRadioPreference extends TwoStatePreference implements
RadioButton radioButton = (RadioButton) holder.findViewById(R.id.radio_button);
radioButton.setChecked(isChecked());
+ TextView titleText = (TextView) holder.findViewById(android.R.id.title);
+ titleText.setContentDescription(mContentDescription);
+
mRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
}
@Override
+ public void setContentDescription(@Nullable String contentDescription) {
+ if (!Objects.equals(mContentDescription, contentDescription)) {
+ mContentDescription = contentDescription;
+ notifyChanged();
+ }
+ }
+
+ @Override
public void setRestrictionIntent(@Nullable Intent restrictionIntent) {
mRestrictionAwarePreferenceMixin.setRestrictionIntent(restrictionIntent);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRolePreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRolePreference.java
index 15fd117d1..28dfc67f9 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRolePreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoRolePreference.java
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.role.ui.auto;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
+import android.widget.TextView;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
@@ -31,12 +32,17 @@ import com.android.permissioncontroller.role.ui.RestrictionAwarePreferenceMixin;
import com.android.permissioncontroller.role.ui.RolePreference;
import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import java.util.Objects;
+
/**
* Preference for use in auto lists. Extends {@link TwoTargetPreference} in order to make sure of
* shared logic between phone and auto settings UI.
*/
public class AutoRolePreference extends Preference implements RolePreference {
+ @Nullable
+ private String mSummaryContentDescription;
+
private RestrictionAwarePreferenceMixin mRestrictionAwarePreferenceMixin =
new RestrictionAwarePreferenceMixin(this);
@@ -62,6 +68,14 @@ public class AutoRolePreference extends Preference implements RolePreference {
public void setOnSecondTargetClickListener(@Nullable OnSecondTargetClickListener listener) {}
@Override
+ public void setSummaryContentDescription(@Nullable String summaryContentDescription) {
+ if (!Objects.equals(mSummaryContentDescription, summaryContentDescription)) {
+ mSummaryContentDescription = summaryContentDescription;
+ notifyChanged();
+ }
+ }
+
+ @Override
public void setRestrictionIntent(@Nullable Intent restrictionIntent) {
mRestrictionAwarePreferenceMixin.setRestrictionIntent(restrictionIntent);
}
@@ -70,6 +84,9 @@ public class AutoRolePreference extends Preference implements RolePreference {
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ TextView summaryText = (TextView) holder.findViewById(android.R.id.summary);
+ summaryText.setContentDescription(mSummaryContentDescription);
+
mRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSwitchPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSwitchPreference.java
index bfb2b5d1d..e7279278c 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSwitchPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoSwitchPreference.java
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.role.ui.auto;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
+import android.widget.TextView;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
@@ -30,12 +31,16 @@ import androidx.preference.SwitchPreference;
import com.android.permissioncontroller.role.ui.RestrictionAwarePreferenceMixin;
import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
+import java.util.Objects;
+
/**
* Role application preference represented as a switch.
*/
public class AutoSwitchPreference extends SwitchPreference
implements RoleApplicationPreference {
+ @Nullable
+ private String mContentDescription;
private RestrictionAwarePreferenceMixin mRestrictionAwarePreferenceMixin =
new RestrictionAwarePreferenceMixin(this);
@@ -58,6 +63,14 @@ public class AutoSwitchPreference extends SwitchPreference
}
@Override
+ public void setContentDescription(@Nullable String contentDescription) {
+ if (!Objects.equals(mContentDescription, contentDescription)) {
+ mContentDescription = contentDescription;
+ notifyChanged();
+ }
+ }
+
+ @Override
public void setRestrictionIntent(@Nullable Intent restrictionIntent) {
mRestrictionAwarePreferenceMixin.setRestrictionIntent(restrictionIntent);
}
@@ -66,6 +79,9 @@ public class AutoSwitchPreference extends SwitchPreference
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ TextView titleText = (TextView) holder.findViewById(android.R.id.title);
+ titleText.setContentDescription(mContentDescription);
+
mRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/AssistantRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/AssistantRoleUiBehavior.java
index 4df3ccf99..c74c3d519 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/AssistantRoleUiBehavior.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/AssistantRoleUiBehavior.java
@@ -25,8 +25,15 @@ import android.provider.Settings;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.permission.flags.Flags;
import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.role.ui.RoleApplicationItem;
import com.android.role.controller.model.Role;
+import com.android.role.controller.util.SignedPackage;
+import com.android.role.controller.util.SignedPackageUtils;
+
+import java.util.List;
+import java.util.function.Predicate;
/***
* Class for UI behavior of Assistant role
@@ -39,14 +46,26 @@ public class AssistantRoleUiBehavior implements RoleUiBehavior {
@NonNull Context context) {
boolean isAutomotive =
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
-
if (isAutomotive) {
return null;
}
-
return new Intent(Settings.ACTION_VOICE_INPUT_SETTINGS);
}
+ @NonNull
+ @Override
+ public Predicate<RoleApplicationItem> getRecommendedApplicationFilter(
+ @NonNull Role role, @NonNull Context context) {
+ if (Flags.defaultAppsRecommendationEnabled()) {
+ List<SignedPackage> signedPackages = SignedPackage.parseList(
+ context.getResources().getString(R.string.config_recommendedAssistants));
+ return applicationItem -> SignedPackageUtils.matchesAny(
+ applicationItem.getApplicationInfo(), signedPackages, context);
+ } else {
+ return RoleUiBehavior.super.getRecommendedApplicationFilter(role, context);
+ }
+ }
+
@Nullable
@Override
public CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java
index ae5c03676..e1bf213a0 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java
@@ -26,10 +26,12 @@ import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.permissioncontroller.role.ui.RequestRoleItemView;
+import com.android.permissioncontroller.role.ui.RoleApplicationItem;
import com.android.permissioncontroller.role.ui.TwoTargetPreference;
import com.android.role.controller.model.Role;
import java.util.List;
+import java.util.function.Predicate;
/***
* Interface for UI behavior for roles
@@ -92,6 +94,20 @@ public interface RoleUiBehavior {
@NonNull UserHandle user, @NonNull Context context) {}
/**
+ * Get the filter for recommended applications of this role.
+ *
+ * @param role the role to get the recommended application filter for
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return the filter for recommended applications
+ */
+ @NonNull
+ default Predicate<RoleApplicationItem> getRecommendedApplicationFilter(
+ @NonNull Role role, @NonNull Context context) {
+ return applicationItem -> false;
+ }
+
+ /**
* Get the confirmation message for adding an application as a holder of this role.
*
* @param role the role to get confirmation message for
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/v35/WalletRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/v35/WalletRoleUiBehavior.java
index f1754dde9..eb1dbb5ef 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/v35/WalletRoleUiBehavior.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/v35/WalletRoleUiBehavior.java
@@ -22,6 +22,8 @@ import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.nfc.cardemulation.ApduServiceInfo;
import android.nfc.cardemulation.CardEmulation;
@@ -39,6 +41,8 @@ import androidx.annotation.RequiresApi;
import androidx.core.util.Pair;
import androidx.preference.Preference;
+import com.android.launcher3.icons.IconFactory;
+import com.android.permissioncontroller.role.ui.RequestRoleItemView;
import com.android.permissioncontroller.role.ui.TwoTargetPreference;
import com.android.permissioncontroller.role.ui.behavior.RoleUiBehavior;
import com.android.role.controller.model.Role;
@@ -70,6 +74,21 @@ public class WalletRoleUiBehavior implements RoleUiBehavior {
}
@Override
+ public void prepareRequestRoleItemViewAsUser(@NonNull Role role,
+ @NonNull RequestRoleItemView itemView, @NonNull ApplicationInfo applicationInfo,
+ @NonNull UserHandle user, @NonNull Context context) {
+ if (isSystemApplication(applicationInfo)) {
+ Pair<Drawable, CharSequence> bannerAndLabel = getLabelAndIconIfItExists(
+ applicationInfo, user, context);
+
+ if (bannerAndLabel != null) {
+ itemView.getIconImageView().setImageDrawable(bannerAndLabel.first);
+ itemView.getTitleTextView().setText(bannerAndLabel.second);
+ }
+ }
+ }
+
+ @Override
public void prepareApplicationPreferenceAsUser(@NonNull Role role,
@NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
@NonNull UserHandle user, @NonNull Context context) {
@@ -81,21 +100,8 @@ public class WalletRoleUiBehavior implements RoleUiBehavior {
@NonNull ApplicationInfo applicationInfo, boolean setTitle, @NonNull UserHandle user,
@NonNull Context context) {
if (isSystemApplication(applicationInfo)) {
- List<ApduServiceInfo> serviceInfos = getNfcServicesForPackage(
- applicationInfo.packageName, user, context);
-
- Pair<Drawable, CharSequence> bannerAndLabel = null;
- // If the flag is enabled , attempt to fetch it from property
- if (Flags.walletRoleIconPropertyEnabled()) {
- bannerAndLabel =
- getBannerAndLabelFromPackageProperty(context, user,
- applicationInfo.packageName);
- }
- // If it's null, indicating that the property is not set, perform a legacy icon lookup.
- if (bannerAndLabel == null) {
- bannerAndLabel =
- getNonPaymentServiceBannerAndLabelIfExists(serviceInfos, user, context);
- }
+ Pair<Drawable, CharSequence> bannerAndLabel = getLabelAndIconIfItExists(
+ applicationInfo, user, context);
if (bannerAndLabel != null) {
preference.setIcon(bannerAndLabel.first);
if (setTitle) {
@@ -107,6 +113,26 @@ public class WalletRoleUiBehavior implements RoleUiBehavior {
}
}
+ @Nullable
+ private Pair<Drawable, CharSequence> getLabelAndIconIfItExists(
+ @NonNull ApplicationInfo applicationInfo,
+ @NonNull UserHandle user,
+ @NonNull Context context) {
+ Pair<Drawable, CharSequence> result = null;
+ // If the flag is enabled , attempt to fetch it from property
+ if (Flags.walletRoleIconPropertyEnabled()) {
+ result = getBannerAndLabelFromPackageProperty(context, user,
+ applicationInfo.packageName);
+ }
+ if (result != null) {
+ return result;
+ }
+ List<ApduServiceInfo> serviceInfos = getNfcServicesForPackage(
+ applicationInfo.packageName, user, context);
+ // If it's null, indicating that the property is not set, perform a legacy icon lookup.
+ return getNonPaymentServiceBannerAndLabelIfExists(serviceInfos, user, context);
+ }
+
@Nullable
private Pair<Drawable, CharSequence> getBannerAndLabelFromPackageProperty(
@@ -122,7 +148,7 @@ public class WalletRoleUiBehavior implements RoleUiBehavior {
PackageManager.Property iconProperty = packageManager.getProperty(
ApduServiceInfo.PROPERTY_WALLET_PREFERRED_BANNER_AND_LABEL, componentName);
if (iconProperty.isBoolean() && iconProperty.getBoolean()) {
- return loadBannerAndLabel(serviceInfo, packageManager);
+ return loadBannerAndLabel(serviceInfo, packageManager, context, user);
}
} catch (PackageManager.NameNotFoundException e) {
continue;
@@ -186,7 +212,8 @@ public class WalletRoleUiBehavior implements RoleUiBehavior {
for (int i = 0; i < apduServiceInfoSize; i++) {
ApduServiceInfo serviceInfo = apduServiceInfos.get(i);
if (serviceInfo.getAids().isEmpty()) {
- bannerAndLabel = loadBannerAndLabel(serviceInfo, userPackageManager);
+ bannerAndLabel = loadBannerAndLabel(serviceInfo, userPackageManager, context,
+ user);
if (bannerAndLabel != null) {
return bannerAndLabel;
}
@@ -197,7 +224,8 @@ public class WalletRoleUiBehavior implements RoleUiBehavior {
String aid = aids.get(j);
String category = serviceInfo.getCategoryForAid(aid);
if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) {
- bannerAndLabel = loadBannerAndLabel(serviceInfo, userPackageManager);
+ bannerAndLabel = loadBannerAndLabel(serviceInfo, userPackageManager,
+ context, user);
if (bannerAndLabel != null) {
return bannerAndLabel;
}
@@ -210,8 +238,20 @@ public class WalletRoleUiBehavior implements RoleUiBehavior {
@Nullable
private Pair<Drawable, CharSequence> loadBannerAndLabel(@NonNull ApduServiceInfo info,
- @NonNull PackageManager userPackageManager) {
+ @NonNull PackageManager userPackageManager, @NonNull Context context,
+ @NonNull UserHandle user) {
Drawable drawable = info.loadBanner(userPackageManager);
+ if (drawable != null) {
+ try (IconFactory factory = IconFactory.obtain(context)) {
+ Bitmap badged =
+ factory.createBadgedIconBitmap(drawable, user,
+ false).icon;
+ if (badged != null) {
+ drawable = new BitmapDrawable(context.getResources(), badged);
+ }
+ }
+ }
+
CharSequence label = info.loadLabel(userPackageManager);
if (drawable != null && !TextUtils.isEmpty(label)) {
return new Pair<>(drawable, label);
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/v36/ReservedForTestingProfileGroupExclusivityRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/v36/ReservedForTestingProfileGroupExclusivityRoleUiBehavior.java
new file mode 100644
index 000000000..00b1ce5b1
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/v36/ReservedForTestingProfileGroupExclusivityRoleUiBehavior.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui.behavior.v36;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Build;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.preference.Preference;
+
+import com.android.permissioncontroller.permission.utils.Utils;
+import com.android.permissioncontroller.role.ui.RequestRoleItemView;
+import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.permissioncontroller.role.ui.behavior.RoleUiBehavior;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.util.UserUtils;
+
+import java.util.List;
+
+@RequiresApi(Build.VERSION_CODES.BAKLAVA)
+public class ReservedForTestingProfileGroupExclusivityRoleUiBehavior implements RoleUiBehavior {
+ @Override
+ public void preparePreferenceAsUser(@NonNull Role role, @NonNull TwoTargetPreference preference,
+ @NonNull List<ApplicationInfo> applicationInfos, @NonNull UserHandle user,
+ @NonNull Context context) {
+ if (!applicationInfos.isEmpty()) {
+ preparePreferenceInternal(preference.asPreference(), applicationInfos.get(0), false,
+ user, context);
+ }
+ }
+
+ @Override
+ public void prepareApplicationPreferenceAsUser(@NonNull Role role,
+ @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
+ @NonNull UserHandle user, @NonNull Context context) {
+ preparePreferenceInternal(preference, applicationInfo, true, user, context);
+ }
+
+ @Override
+ public void prepareRequestRoleItemViewAsUser(@NonNull Role role,
+ @NonNull RequestRoleItemView itemView, @NonNull ApplicationInfo applicationInfo,
+ @NonNull UserHandle user, @NonNull Context context) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ String title = getTitle(applicationInfo, userContext);
+ itemView.getTitleTextView().setText(title);
+ }
+
+ private void preparePreferenceInternal(@NonNull Preference preference,
+ @NonNull ApplicationInfo applicationInfo, boolean setTitle, @NonNull UserHandle user,
+ @NonNull Context context) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ String title = getTitle(applicationInfo, userContext);
+ if (setTitle) {
+ preference.setTitle(title);
+ } else {
+ preference.setSummary(title);
+ }
+ }
+
+ @NonNull
+ private static String getTitle(@NonNull ApplicationInfo applicationInfo,
+ @NonNull Context context) {
+ return Utils.getFullAppLabel(applicationInfo, context) + "@"
+ + UserHandle.getUserHandleForUid(applicationInfo.uid).getIdentifier();
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRadioPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRadioPreference.java
index 67f04051c..2a943d3d7 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRadioPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRadioPreference.java
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.role.ui.handheld;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
+import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -28,12 +29,17 @@ import com.android.permissioncontroller.role.ui.RestrictionAwarePreferenceMixin;
import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
+import java.util.Objects;
+
/**
* Preference used to represent apps that can be picked as a default app.
*/
public class HandheldRadioPreference extends SelectorWithWidgetPreference implements
RoleApplicationPreference {
+ @Nullable
+ private String mContentDescription;
+
private final RestrictionAwarePreferenceMixin mRestrictionAwarePreferenceMixin =
new RestrictionAwarePreferenceMixin(this);
@@ -56,6 +62,14 @@ public class HandheldRadioPreference extends SelectorWithWidgetPreference implem
}
@Override
+ public void setContentDescription(@Nullable String contentDescription) {
+ if (!Objects.equals(mContentDescription, contentDescription)) {
+ mContentDescription = contentDescription;
+ notifyChanged();
+ }
+ }
+
+ @Override
public void setRestrictionIntent(@Nullable Intent restrictionIntent) {
mRestrictionAwarePreferenceMixin.setRestrictionIntent(restrictionIntent);
}
@@ -64,6 +78,9 @@ public class HandheldRadioPreference extends SelectorWithWidgetPreference implem
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ TextView titleText = (TextView) holder.findViewById(android.R.id.title);
+ titleText.setContentDescription(mContentDescription);
+
mRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRolePreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRolePreference.java
index 3d09f0b46..46bf3b173 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRolePreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/handheld/HandheldRolePreference.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.TextView;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
@@ -33,6 +34,8 @@ import com.android.permissioncontroller.role.ui.RestrictionAwarePreferenceMixin;
import com.android.permissioncontroller.role.ui.RolePreference;
import com.android.settingslib.widget.TwoTargetPreference;
+import java.util.Objects;
+
/**
* {@link Preference} with a settings button.
*
@@ -45,6 +48,9 @@ public class HandheldRolePreference extends TwoTargetPreference implements RoleP
new RestrictionAwarePreferenceMixin(this);
@Nullable
+ private String mSummaryContentDescription;
+
+ @Nullable
private OnSecondTargetClickListener mOnSecondTargetClickListener;
public HandheldRolePreference(@NonNull Context context, @Nullable AttributeSet attrs,
@@ -94,6 +100,14 @@ public class HandheldRolePreference extends TwoTargetPreference implements RoleP
}
@Override
+ public void setSummaryContentDescription(@Nullable String summaryContentDescription) {
+ if (!Objects.equals(mSummaryContentDescription, summaryContentDescription)) {
+ mSummaryContentDescription = summaryContentDescription;
+ notifyChanged();
+ }
+ }
+
+ @Override
public void setRestrictionIntent(@Nullable Intent restrictionIntent) {
mRestrictionAwarePreferenceMixin.setRestrictionIntent(restrictionIntent);
}
@@ -114,6 +128,9 @@ public class HandheldRolePreference extends TwoTargetPreference implements RoleP
// Make the settings button enabled even if the preference itself is disabled.
settingsButton.setEnabled(true);
+ TextView summaryText = (TextView) holder.findViewById(android.R.id.summary);
+ summaryText.setContentDescription(mSummaryContentDescription);
+
mRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java
index 0963635e7..7a13eb2b5 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java
@@ -23,7 +23,6 @@ import android.content.pm.ApplicationInfo;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.ArrayMap;
-import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -37,6 +36,7 @@ import androidx.preference.TwoStatePreference;
import com.android.permissioncontroller.permission.utils.Utils;
import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData;
+import com.android.permissioncontroller.role.ui.RoleApplicationItem;
import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
import com.android.role.controller.model.Role;
@@ -107,12 +107,12 @@ public class SpecialAppAccessChildFragment<PF extends PreferenceFragmentCompat
activity.getApplication());
mViewModel = new ViewModelProvider(this, viewModelFactory)
.get(SpecialAppAccessViewModel.class);
- mViewModel.getRoleLiveData().observe(this, this::onRoleChanged);
+ mViewModel.getLiveData().observe(this, this::onApplicationListChanged);
mViewModel.observeManageRoleHolderState(this, this::onManageRoleHolderStateChanged);
}
- private void onRoleChanged(
- @NonNull List<Pair<ApplicationInfo, Boolean>> qualifyingApplications) {
+ private void onApplicationListChanged(
+ @NonNull List<RoleApplicationItem> applicationItems) {
PF preferenceFragment = requirePreferenceFragment();
PreferenceManager preferenceManager = preferenceFragment.getPreferenceManager();
Context context = preferenceManager.getContext();
@@ -138,14 +138,12 @@ public class SpecialAppAccessChildFragment<PF extends PreferenceFragmentCompat
}
}
- int qualifyingApplicationsSize = qualifyingApplications.size();
- for (int i = 0; i < qualifyingApplicationsSize; i++) {
- Pair<ApplicationInfo, Boolean> qualifyingApplication = qualifyingApplications.get(i);
- ApplicationInfo qualifyingApplicationInfo = qualifyingApplication.first;
- boolean isHolderPackage = qualifyingApplication.second;
-
- String key = qualifyingApplicationInfo.packageName + '_'
- + qualifyingApplicationInfo.uid;
+ int applicationItemsSize = applicationItems.size();
+ for (int i = 0; i < applicationItemsSize; i++) {
+ RoleApplicationItem applicationItem = applicationItems.get(i);
+ ApplicationInfo applicationInfo = applicationItem.getApplicationInfo();
+ String key = applicationInfo.packageName + '_'
+ + applicationInfo.uid;
RoleApplicationPreference roleApplicationPreference =
(RoleApplicationPreference) oldPreferences.get(key);
TwoStatePreference preference;
@@ -153,24 +151,23 @@ public class SpecialAppAccessChildFragment<PF extends PreferenceFragmentCompat
roleApplicationPreference = preferenceFragment.createApplicationPreference();
preference = roleApplicationPreference.asTwoStatePreference();
preference.setKey(key);
- preference.setIcon(Utils.getBadgedIcon(context, qualifyingApplicationInfo));
- preference.setTitle(Utils.getFullAppLabel(qualifyingApplicationInfo, context));
+ preference.setIcon(Utils.getBadgedIcon(context, applicationInfo));
+ preference.setTitle(Utils.getFullAppLabel(applicationInfo, context));
preference.setPersistent(false);
preference.setOnPreferenceChangeListener((preference2, newValue) -> false);
preference.setOnPreferenceClickListener(this);
preference.getExtras().putParcelable(PREFERENCE_EXTRA_APPLICATION_INFO,
- qualifyingApplicationInfo);
+ applicationInfo);
} else {
preference = roleApplicationPreference.asTwoStatePreference();
}
- preference.setChecked(isHolderPackage);
- UserHandle user = UserHandle.getUserHandleForUid(qualifyingApplicationInfo.uid);
+ preference.setChecked(applicationItem.isHolderApplication());
+ UserHandle user = UserHandle.getUserHandleForUid(applicationInfo.uid);
roleApplicationPreference.setRestrictionIntent(
- mRole.getApplicationRestrictionIntentAsUser(qualifyingApplicationInfo, user,
- context));
+ mRole.getApplicationRestrictionIntentAsUser(applicationInfo, user, context));
RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, roleApplicationPreference,
- qualifyingApplicationInfo, user, context);
+ applicationInfo, user, context);
preferenceScreen.addPreference(preference);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListViewModel.java
index ee5a0dbbd..e96fb0943 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListViewModel.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListViewModel.java
@@ -26,6 +26,7 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
+import com.android.permissioncontroller.role.ui.MergeRoleListLiveData;
import com.android.permissioncontroller.role.ui.RoleItem;
import com.android.permissioncontroller.role.ui.RoleListLiveData;
import com.android.permissioncontroller.role.ui.RoleListSortFunction;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java
index 0cc00abc1..f4abc0db1 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java
@@ -17,12 +17,10 @@
package com.android.permissioncontroller.role.ui.specialappaccess;
import android.app.Application;
-import android.content.pm.ApplicationInfo;
import android.os.Process;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
@@ -33,6 +31,8 @@ import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData;
+import com.android.permissioncontroller.role.ui.MergeRoleLiveData;
+import com.android.permissioncontroller.role.ui.RoleApplicationItem;
import com.android.permissioncontroller.role.ui.RoleLiveData;
import com.android.permissioncontroller.role.ui.RoleSortFunction;
import com.android.permissioncontroller.role.utils.UserUtils;
@@ -51,7 +51,7 @@ public class SpecialAppAccessViewModel extends AndroidViewModel {
private final Role mRole;
@NonNull
- private final LiveData<List<Pair<ApplicationInfo, Boolean>>> mRoleLiveData;
+ private final LiveData<List<RoleApplicationItem>> mLiveData;
@NonNull
private final ArrayMap<String, ManageRoleHolderStateLiveData> mManageRoleHolderStateLiveDatas =
@@ -67,17 +67,17 @@ public class SpecialAppAccessViewModel extends AndroidViewModel {
UserHandle workProfile = UserUtils.getWorkProfile(application);
RoleSortFunction sortFunction = new RoleSortFunction(application);
if (workProfile == null) {
- mRoleLiveData = Transformations.map(roleLiveData, sortFunction);
+ mLiveData = Transformations.map(roleLiveData, sortFunction);
} else {
RoleLiveData workRoleLiveData = new RoleLiveData(role, workProfile, application);
- mRoleLiveData = Transformations.map(new MergeRoleLiveData(roleLiveData,
+ mLiveData = Transformations.map(new MergeRoleLiveData(roleLiveData,
workRoleLiveData), sortFunction);
}
}
@NonNull
- public LiveData<List<Pair<ApplicationInfo, Boolean>>> getRoleLiveData() {
- return mRoleLiveData;
+ public LiveData<List<RoleApplicationItem>> getLiveData() {
+ return mLiveData;
}
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSwitchPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSwitchPreference.java
index ded6d5cb5..a801c2182 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSwitchPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/handheld/HandheldSwitchPreference.java
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.role.ui.specialappaccess.handheld;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
+import android.widget.TextView;
import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
@@ -30,10 +31,14 @@ import com.android.permissioncontroller.role.ui.RestrictionAwarePreferenceMixin;
import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
import com.android.settingslib.widget.AppSwitchPreference;
+import java.util.Objects;
+
/** {@link AppSwitchPreference} that is a role application preference. */
public class HandheldSwitchPreference extends AppSwitchPreference
implements RoleApplicationPreference {
+ @Nullable
+ private String mContentDescription;
private RestrictionAwarePreferenceMixin mRestrictionAwarePreferenceMixin =
new RestrictionAwarePreferenceMixin(this);
@@ -56,6 +61,14 @@ public class HandheldSwitchPreference extends AppSwitchPreference
}
@Override
+ public void setContentDescription(@Nullable String contentDescription) {
+ if (!Objects.equals(mContentDescription, contentDescription)) {
+ mContentDescription = contentDescription;
+ notifyChanged();
+ }
+ }
+
+ @Override
public void setRestrictionIntent(@Nullable Intent restrictionIntent) {
mRestrictionAwarePreferenceMixin.setRestrictionIntent(restrictionIntent);
}
@@ -64,6 +77,9 @@ public class HandheldSwitchPreference extends AppSwitchPreference
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
+ TextView titleText = (TextView) holder.findViewById(android.R.id.title);
+ titleText.setContentDescription(mContentDescription);
+
mRestrictionAwarePreferenceMixin.onAfterBindViewHolder(holder);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppFragment.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppFragment.kt
index 156656e33..dc8f3bf88 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppFragment.kt
@@ -27,11 +27,11 @@ import androidx.compose.ui.platform.ComposeView
import androidx.core.os.BundleCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
import com.android.permissioncontroller.role.ui.DefaultAppViewModel
import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData
import com.android.permissioncontroller.role.ui.wear.model.DefaultAppConfirmDialogViewModel
import com.android.permissioncontroller.role.ui.wear.model.DefaultAppConfirmDialogViewModelFactory
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionTheme
import com.android.role.controller.model.Role
import com.android.role.controller.model.Roles
@@ -46,14 +46,13 @@ class WearDefaultAppFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
val roleName = arguments?.getString(Intent.EXTRA_ROLE_NAME) ?: ""
val user =
arguments?.let {
BundleCompat.getParcelable(it, Intent.EXTRA_USER, UserHandle::class.java)!!
- }
- ?: UserHandle.SYSTEM
+ } ?: UserHandle.SYSTEM
val activity = requireActivity()
role =
@@ -82,7 +81,7 @@ class WearDefaultAppFragment : Fragment() {
user,
role,
viewModel,
- confirmDialogViewModel
+ confirmDialogViewModel,
)
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppHelper.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppHelper.kt
index a47719cf7..65548d9a1 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppHelper.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppHelper.kt
@@ -17,12 +17,11 @@
package com.android.permissioncontroller.role.ui.wear
import android.content.Context
-import android.content.pm.ApplicationInfo
import android.os.UserHandle
-import android.util.Pair
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.role.ui.DefaultAppViewModel
+import com.android.permissioncontroller.role.ui.RoleApplicationItem
import com.android.permissioncontroller.role.ui.wear.model.ConfirmDialogArgs
import com.android.permissioncontroller.role.ui.wear.model.DefaultAppConfirmDialogViewModel
import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils
@@ -34,19 +33,19 @@ class WearDefaultAppHelper(
val user: UserHandle,
val role: Role,
val viewModel: DefaultAppViewModel,
- val confirmDialogViewModel: DefaultAppConfirmDialogViewModel
+ val confirmDialogViewModel: DefaultAppConfirmDialogViewModel,
) {
fun getTitle() = context.getString(role.labelResource)
fun getNonePreference(
- qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>
+ applicationItems: List<RoleApplicationItem>
): WearRoleApplicationPreference? =
if (role.shouldShowNone()) {
WearRoleApplicationPreference(
context = context,
defaultLabel = context.getString(R.string.default_app_none),
- checked = !hasHolderApplication(qualifyingApplications),
- onDefaultCheckChanged = { _ -> viewModel.setNoneDefaultApp() }
+ checked = !hasHolderApplication(applicationItems),
+ onDefaultCheckChanged = { _ -> viewModel.setNoneDefaultApp() },
)
.apply { icon = context.getDrawable(R.drawable.ic_remove_circle) }
} else {
@@ -54,12 +53,13 @@ class WearDefaultAppHelper(
}
fun getPreferences(
- qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>
+ applicationItems: List<RoleApplicationItem>
): List<WearRoleApplicationPreference> {
- return qualifyingApplications
- .map { pair ->
- val appInfo = pair.first
- val selected = pair.second
+ return applicationItems
+ .map { applicationItem ->
+ val appInfo = applicationItem.applicationInfo
+ val selected = applicationItem.isHolderApplication
+ val user = UserHandle.getUserHandleForUid(appInfo.uid)
WearRoleApplicationPreference(
context = context,
defaultLabel = Utils.getFullAppLabel(appInfo, context),
@@ -71,15 +71,19 @@ class WearDefaultAppHelper(
RoleUiBehaviorUtils.getConfirmationMessage(
role,
packageName,
- context
+ context,
)
if (confirmationMessage != null) {
- showConfirmDialog(packageName, confirmationMessage.toString())
+ showConfirmDialog(
+ packageName,
+ user,
+ confirmationMessage.toString(),
+ )
} else {
- setDefaultApp(packageName)
+ setDefaultApp(packageName, user)
}
}
- }
+ },
)
.apply {
icon = appInfo.loadIcon(context.packageManager)
@@ -91,22 +95,22 @@ class WearDefaultAppHelper(
this,
appInfo,
user,
- context
+ context,
)
}
}
.toList()
}
- private fun showConfirmDialog(packageName: String, message: String) {
+ private fun showConfirmDialog(packageName: String, userHandle: UserHandle, message: String) {
confirmDialogViewModel.confirmDialogArgs =
ConfirmDialogArgs(
message = message,
onOkButtonClick = {
- setDefaultApp(packageName)
+ setDefaultApp(packageName, userHandle)
dismissConfirmDialog()
},
- onCancelButtonClick = { dismissConfirmDialog() }
+ onCancelButtonClick = { dismissConfirmDialog() },
)
confirmDialogViewModel.showConfirmDialogLiveData.value = true
}
@@ -116,13 +120,12 @@ class WearDefaultAppHelper(
confirmDialogViewModel.showConfirmDialogLiveData.value = false
}
- private fun setDefaultApp(packageName: String) {
- viewModel.setDefaultApp(packageName)
+ private fun setDefaultApp(packageName: String, user: UserHandle) {
+ viewModel.setDefaultApp(packageName, user)
}
fun getDescription() = context.getString(role.descriptionResource)
- private fun hasHolderApplication(
- qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>
- ): Boolean = qualifyingApplications.map { it.second }.contains(true)
+ private fun hasHolderApplication(applicationItems: List<RoleApplicationItem>): Boolean =
+ applicationItems.map { it.isHolderApplication }.contains(true)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt
index afee50389..131897fb9 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt
@@ -29,11 +29,11 @@ import androidx.compose.ui.res.stringResource
import androidx.lifecycle.LiveData
import androidx.wear.compose.material.Text
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.chipDefaultColors
-import com.android.permissioncontroller.permission.ui.wear.elements.chipDisabledColors
import com.android.permissioncontroller.role.ui.RoleItem
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButtonStyle
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
@Composable
fun WearDefaultAppListScreen(
@@ -52,20 +52,20 @@ fun WearDefaultAppListScreen(
}
preferences.forEach { pref ->
item {
- Chip(
+ WearPermissionButton(
label = pref.label,
- icon = pref.icon,
- colors =
- if (pref.isEnabled()) {
- chipDefaultColors()
+ iconBuilder = pref.icon?.let { WearPermissionIconBuilder.builder(it) },
+ style =
+ if (pref.isEnabled) {
+ WearPermissionButtonStyle.Secondary
} else {
- chipDisabledColors()
+ WearPermissionButtonStyle.DisabledLike
},
secondaryLabel = pref.summary?.toString(),
onClick = pref.getOnClicked(),
modifier = Modifier.fillMaxWidth(),
labelMaxLines = Int.MAX_VALUE,
- secondaryLabelMaxLines = Integer.MAX_VALUE
+ secondaryLabelMaxLines = Integer.MAX_VALUE,
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt
index 5d4233c6e..c3cbe4670 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt
@@ -16,8 +16,6 @@
package com.android.permissioncontroller.role.ui.wear
-import android.content.pm.ApplicationInfo
-import android.util.Pair
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -25,27 +23,32 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
-import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
-import androidx.wear.compose.material.ToggleChipDefaults
-import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog
-import com.android.permissioncontroller.permission.ui.wear.elements.ListFooter
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
-import com.android.permissioncontroller.permission.ui.wear.elements.toggleChipDisabledColors
+import com.android.permissioncontroller.role.ui.RoleApplicationItem
import com.android.permissioncontroller.role.ui.wear.model.ConfirmDialogArgs
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.DialogButtonContent
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionConfirmationDialog
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionListFooter
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControl
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControlStyle
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControlType
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
@Composable
fun WearDefaultAppScreen(helper: WearDefaultAppHelper) {
- val roleLiveData = helper.viewModel.roleLiveData.observeAsState(emptyList())
+ val roleLiveData = helper.viewModel.liveData.observeAsState(emptyList())
val showConfirmDialog =
helper.confirmDialogViewModel.showConfirmDialogLiveData.observeAsState(false)
var isLoading by remember { mutableStateOf(true) }
+ val materialUIVersion = ResourceHelper.materialUIVersionInSettings
Box {
WearDefaultAppContent(isLoading, roleLiveData.value, helper)
ConfirmDialog(
+ materialUIVersion = materialUIVersion,
showDialog = showConfirmDialog.value,
- args = helper.confirmDialogViewModel.confirmDialogArgs
+ args = helper.confirmDialogViewModel.confirmDialogArgs,
)
}
if (isLoading && roleLiveData.value.isNotEmpty()) {
@@ -56,56 +59,60 @@ fun WearDefaultAppScreen(helper: WearDefaultAppHelper) {
@Composable
private fun WearDefaultAppContent(
isLoading: Boolean,
- qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>,
- helper: WearDefaultAppHelper
+ applicationItems: List<RoleApplicationItem>,
+ helper: WearDefaultAppHelper,
) {
ScrollableScreen(title = helper.getTitle(), isLoading = isLoading) {
- helper.getNonePreference(qualifyingApplications)?.let {
+ helper.getNonePreference(applicationItems)?.let {
item {
- ToggleChip(
+ WearPermissionToggleControl(
label = it.title.toString(),
- icon = it.icon,
+ iconBuilder = it.icon?.let { WearPermissionIconBuilder.builder(it) },
checked = it.checked,
onCheckedChanged = it.onDefaultCheckChanged,
- toggleControl = ToggleChipToggleControl.Radio,
- labelMaxLine = Integer.MAX_VALUE
+ toggleControl = WearPermissionToggleControlType.Radio,
+ labelMaxLines = Integer.MAX_VALUE,
)
}
}
- for (pref in helper.getPreferences(qualifyingApplications)) {
+ for (pref in helper.getPreferences(applicationItems)) {
item {
- ToggleChip(
+ WearPermissionToggleControl(
label = pref.title.toString(),
- icon = pref.icon,
- colors =
+ iconBuilder = pref.icon?.let { WearPermissionIconBuilder.builder(it) },
+ style =
if (pref.isEnabled) {
- ToggleChipDefaults.toggleChipColors()
+ WearPermissionToggleControlStyle.Default
} else {
- toggleChipDisabledColors()
+ WearPermissionToggleControlStyle.DisabledLike
},
secondaryLabel = pref.summary?.toString(),
checked = pref.checked,
onCheckedChanged = pref.getOnCheckChanged(),
- toggleControl = ToggleChipToggleControl.Radio,
- labelMaxLine = Integer.MAX_VALUE,
- secondaryLabelMaxLine = Integer.MAX_VALUE
+ toggleControl = WearPermissionToggleControlType.Radio,
+ labelMaxLines = Integer.MAX_VALUE,
+ secondaryLabelMaxLines = Integer.MAX_VALUE,
)
}
}
- item { ListFooter(description = helper.getDescription()) }
+ item { WearPermissionListFooter(label = helper.getDescription()) }
}
}
@Composable
-private fun ConfirmDialog(showDialog: Boolean, args: ConfirmDialogArgs?) {
- args?.let {
- AlertDialog(
- showDialog = showDialog,
- message = it.message,
- onOKButtonClick = it.onOkButtonClick,
- onCancelButtonClick = it.onCancelButtonClick,
- scalingLazyListState = rememberScalingLazyListState()
+private fun ConfirmDialog(
+ materialUIVersion: WearPermissionMaterialUIVersion,
+ showDialog: Boolean,
+ args: ConfirmDialogArgs?,
+) {
+ args?.run {
+ WearPermissionConfirmationDialog(
+ materialUIVersion = materialUIVersion,
+ show = showDialog,
+ message = message,
+ positiveButtonContent = DialogButtonContent(onClick = onOkButtonClick),
+ negativeButtonContent = DialogButtonContent(onClick = onCancelButtonClick),
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleFragment.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleFragment.kt
index 728ea8e99..99f2fe36f 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleFragment.kt
@@ -32,12 +32,14 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.android.permissioncontroller.PermissionControllerStatsLog
import com.android.permissioncontroller.permission.utils.PackageRemovalMonitor
+import com.android.permissioncontroller.role.UserPackage
import com.android.permissioncontroller.role.model.UserDeniedManager
import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData
import com.android.permissioncontroller.role.ui.RequestRoleViewModel
import com.android.permissioncontroller.role.ui.wear.model.WearRequestRoleViewModel
import com.android.permissioncontroller.role.ui.wear.model.WearRequestRoleViewModelFactory
import com.android.permissioncontroller.role.utils.PackageUtils
+import com.android.permissioncontroller.role.utils.UserUtils
import com.android.role.controller.model.Role
import com.android.role.controller.model.Roles
import java.util.Objects
@@ -56,7 +58,7 @@ class WearRequestRoleFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
- savedInstanceState: Bundle?
+ savedInstanceState: Bundle?,
): View? {
roleName = arguments?.getString(Intent.EXTRA_ROLE_NAME) ?: ""
packageName = arguments?.getString(Intent.EXTRA_PACKAGE_NAME) ?: ""
@@ -74,12 +76,12 @@ class WearRequestRoleFragment : Fragment() {
if (currentPackageNames.contains(packageName)) {
Log.i(
TAG,
- "Application is already a role holder, role: $roleName, package: $packageName"
+ "Application is already a role holder, role: $roleName, package: $packageName",
)
reportRequestResult(
PermissionControllerStatsLog
.ROLE_REQUEST_RESULT_REPORTED__RESULT__IGNORED_ALREADY_GRANTED,
- null
+ null,
)
clearDeniedSetResultOkAndFinish()
return null
@@ -89,7 +91,7 @@ class WearRequestRoleFragment : Fragment() {
Log.w(TAG, "Unknown application: $packageName")
reportRequestResult(
PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED__RESULT__IGNORED,
- null
+ null,
)
finish()
return null
@@ -98,7 +100,7 @@ class WearRequestRoleFragment : Fragment() {
viewModel =
ViewModelProvider(
this,
- RequestRoleViewModel.Factory(role, requireActivity().application)
+ RequestRoleViewModel.Factory(role, requireActivity().application),
)
.get(RequestRoleViewModel::class.java)
viewModel.manageRoleHolderStateLiveData.observe(this, this::onManageRoleHolderStateChanged)
@@ -117,25 +119,25 @@ class WearRequestRoleFragment : Fragment() {
roleName,
packageName,
viewModel,
- wearViewModel
+ wearViewModel,
)
- val onSetAsDefault: (Boolean, String?) -> Unit = { dontAskAgain, selectedPackageName ->
+ val onSetAsDefault: (Boolean, UserPackage?) -> Unit = { dontAskAgain, selectedUserPackage ->
run {
if (dontAskAgain) {
Log.i(
TAG,
"Request denied with don't ask again, role: $roleName" +
- ", package: $packageName"
+ ", package: $packageName",
)
reportRequestResult(
PermissionControllerStatsLog
.ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_ALWAYS,
- null
+ null,
)
setDeniedAlwaysAndFinish()
} else {
- setRoleHolder(selectedPackageName)
+ setRoleHolder(selectedUserPackage)
}
}
}
@@ -143,7 +145,7 @@ class WearRequestRoleFragment : Fragment() {
Log.i(TAG, "Dialog cancelled, role: $roleName , package: $packageName")
reportRequestResult(
PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED,
- null
+ null,
)
setDeniedOnceAndFinish()
}
@@ -158,25 +160,28 @@ class WearRequestRoleFragment : Fragment() {
when (state) {
ManageRoleHolderStateLiveData.STATE_SUCCESS -> {
val lastPackageName = liveData.lastPackageName
+ val lastUser = liveData.lastUser
+ val lastUserPackage: UserPackage? =
+ if (lastPackageName == null || lastUser == null) {
+ null
+ } else {
+ UserPackage.of(lastUser, lastPackageName)
+ }
if (lastPackageName != null) {
- role.onHolderSelectedAsUser(
- lastPackageName,
- liveData.lastUser,
- requireContext()
- )
+ role.onHolderSelectedAsUser(lastPackageName, lastUser, requireContext())
}
- if (lastPackageName == packageName) {
+ if (isRequestingApplication(lastUserPackage)) {
Log.i(
TAG,
"Application added as a role holder, role: $roleName, package: " +
- packageName
+ packageName,
)
clearDeniedSetResultOkAndFinish()
} else {
Log.i(
TAG,
"Request denied with another application added as a role holder, " +
- "role: $roleName, package: $packageName"
+ "role: $roleName, package: $packageName",
)
setDeniedOnceAndFinish()
}
@@ -196,23 +201,24 @@ class WearRequestRoleFragment : Fragment() {
finish()
}
- private fun reportRequestResult(result: Int, grantedAnotherPackageName: String?) {
- val holderPackageName: String? = getHolderPackageName()
+ private fun reportRequestResult(result: Int, grantedUserPackage: UserPackage?) {
+ val holderUserPackage: UserPackage? = getHolderUserPackage()
reportRequestResult(
- getApplicationUid(packageName),
+ getRequestingApplicationUid(),
packageName,
roleName,
getQualifyingApplicationCount(),
- getQualifyingApplicationUid(holderPackageName),
- holderPackageName,
- getQualifyingApplicationUid(grantedAnotherPackageName),
- grantedAnotherPackageName,
- result
+ getQualifyingApplicationUid(holderUserPackage),
+ holderUserPackage?.packageName,
+ getQualifyingApplicationUid(grantedUserPackage),
+ grantedUserPackage?.packageName,
+ result,
)
}
- private fun getApplicationUid(packageName: String): Int {
- val uid: Int = getQualifyingApplicationUid(packageName)
+ private fun getRequestingApplicationUid(): Int {
+ val uid: Int =
+ getQualifyingApplicationUid(UserPackage.of(Process.myUserHandle(), packageName))
if (uid != -1) {
return uid
}
@@ -221,14 +227,15 @@ class WearRequestRoleFragment : Fragment() {
return applicationInfo.uid
}
- private fun getQualifyingApplicationUid(packageName: String?): Int {
- if (packageName == null) {
+ private fun getQualifyingApplicationUid(userPackage: UserPackage?): Int {
+ if (userPackage == null) {
return -1
}
- viewModel.roleLiveData.value?.let { qualifyingApplications ->
- for (qualifyingApplication in qualifyingApplications) {
- val qualifyingApplicationInfo = qualifyingApplication.first
- if (Objects.equals(qualifyingApplicationInfo.packageName, packageName)) {
+ viewModel.liveData.value?.let { applicationItems ->
+ for (applicationItem in applicationItems) {
+ val qualifyingApplicationInfo = applicationItem.applicationInfo
+ val qualifyingUserPackage = UserPackage.from(qualifyingApplicationInfo)
+ if (Objects.equals(qualifyingUserPackage, userPackage)) {
return qualifyingApplicationInfo.uid
}
}
@@ -236,12 +243,11 @@ class WearRequestRoleFragment : Fragment() {
return -1
}
- private fun getHolderPackageName(): String? {
- viewModel.roleLiveData.value?.let { qualifyingApplications ->
+ private fun getHolderUserPackage(): UserPackage? {
+ viewModel.liveData.value?.let { qualifyingApplications ->
for (qualifyingApplication in qualifyingApplications) {
- val isHolderApplication = qualifyingApplication.second
- if (isHolderApplication) {
- return qualifyingApplication.first.packageName
+ if (qualifyingApplication.isHolderApplication) {
+ return UserPackage.from(qualifyingApplication.applicationInfo)
}
}
}
@@ -249,7 +255,7 @@ class WearRequestRoleFragment : Fragment() {
}
private fun getQualifyingApplicationCount(): Int {
- return viewModel.roleLiveData.value?.size ?: -1
+ return viewModel.liveData.value?.size ?: -1
}
private fun setDeniedAlwaysAndFinish() {
@@ -261,49 +267,58 @@ class WearRequestRoleFragment : Fragment() {
requireActivity().finish()
}
- private fun setRoleHolder(selectedPackageName: String?) {
+ private fun setRoleHolder(selectedUserPackage: UserPackage?) {
val context: Context = requireContext()
- val user: UserHandle = Process.myUserHandle()
- if (selectedPackageName == null) {
+ if (selectedUserPackage == null) {
reportRequestResult(
PermissionControllerStatsLog
.ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_GRANTED_ANOTHER,
- null
+ null,
)
+ val user: UserHandle =
+ if (role.exclusivity == Role.EXCLUSIVITY_PROFILE_GROUP) {
+ UserUtils.getProfileParentOrSelf(Process.myUserHandle(), context)
+ } else {
+ Process.myUserHandle()
+ }
role.onNoneHolderSelectedAsUser(user, context)
viewModel.manageRoleHolderStateLiveData.clearRoleHoldersAsUser(
roleName,
0,
user,
- context
+ context,
)
} else {
- val isRequestingApplication = selectedPackageName == packageName
+ val isRequestingApplication = isRequestingApplication(selectedUserPackage)
if (isRequestingApplication) {
reportRequestResult(
PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED,
- null
+ null,
)
} else {
reportRequestResult(
PermissionControllerStatsLog
.ROLE_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_GRANTED_ANOTHER,
- selectedPackageName
+ selectedUserPackage,
)
}
val flags =
if (isRequestingApplication) RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP else 0
viewModel.manageRoleHolderStateLiveData.setRoleHolderAsUser(
roleName,
- selectedPackageName,
+ selectedUserPackage.packageName,
true,
flags,
- user,
- context
+ selectedUserPackage.user,
+ context,
)
}
}
+ private fun isRequestingApplication(userPackage: UserPackage?): Boolean {
+ return Objects.equals(userPackage, UserPackage.of(Process.myUserHandle(), packageName))
+ }
+
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
wearViewModel.onSaveInstanceState(outState)
@@ -318,12 +333,12 @@ class WearRequestRoleFragment : Fragment() {
TAG,
"Application is uninstalled, role: $roleName" +
", package: " +
- packageName
+ packageName,
)
reportRequestResult(
PermissionControllerStatsLog
.ROLE_REQUEST_RESULT_REPORTED__RESULT__IGNORED,
- null
+ null,
)
finish()
}
@@ -364,7 +379,7 @@ class WearRequestRoleFragment : Fragment() {
currentPackageName: String?,
grantedAnotherUid: Int,
grantedAnotherPackageName: String?,
- result: Int
+ result: Int,
) {
Log.i(
TAG,
@@ -376,7 +391,7 @@ class WearRequestRoleFragment : Fragment() {
" currentPackageName=$currentPackageName" +
" grantedAnotherUid=$grantedAnotherUid" +
" grantedAnotherPackageName=$grantedAnotherPackageName" +
- " result=$result"
+ " result=$result",
)
PermissionControllerStatsLog.write(
PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED,
@@ -388,7 +403,7 @@ class WearRequestRoleFragment : Fragment() {
currentPackageName,
grantedAnotherUid,
grantedAnotherPackageName,
- result
+ result,
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleHelper.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleHelper.kt
index c7f14b862..f95f1ee90 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleHelper.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleHelper.kt
@@ -19,13 +19,16 @@ package com.android.permissioncontroller.role.ui.wear
import android.content.Context
import android.content.pm.ApplicationInfo
import android.graphics.drawable.Drawable
-import android.util.Pair
+import android.os.Process
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.utils.Utils
+import com.android.permissioncontroller.role.UserPackage
import com.android.permissioncontroller.role.model.UserDeniedManager
import com.android.permissioncontroller.role.ui.RequestRoleViewModel
+import com.android.permissioncontroller.role.ui.RoleApplicationItem
import com.android.permissioncontroller.role.ui.wear.model.WearRequestRoleViewModel
import com.android.role.controller.model.Role
+import java.util.Objects
/** A helper class for [WearRequestRoleScreen]. */
class WearRequestRoleHelper(
@@ -35,7 +38,7 @@ class WearRequestRoleHelper(
val roleName: String,
val packageName: String,
val viewModel: RequestRoleViewModel,
- val wearViewModel: WearRequestRoleViewModel
+ val wearViewModel: WearRequestRoleViewModel,
) {
fun getIcon() = Utils.getBadgedIcon(context, applicationInfo)
@@ -47,13 +50,13 @@ class WearRequestRoleHelper(
UserDeniedManager.getInstance(context).isDeniedOnce(roleName, packageName)
fun getNonePreference(
- qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>,
- selectedPackage: String?
+ applicationItems: List<RoleApplicationItem>,
+ selectedPackage: UserPackage?,
): RequestRolePreference? =
if (role.shouldShowNone()) {
- val hasHolderApplication = hasHolderApplication(qualifyingApplications)
+ val hasHolderApplication = hasHolderApplication(applicationItems)
RequestRolePreference(
- packageName = null,
+ userPackage = null,
label = context.getString(R.string.default_app_none),
subTitle =
if (!hasHolderApplication) {
@@ -62,66 +65,70 @@ class WearRequestRoleHelper(
null
},
icon = context.getDrawable(R.drawable.ic_remove_circle),
- checked = selectedPackage.isNullOrEmpty(),
+ checked = selectedPackage == null,
enabled =
if (!wearViewModel.dontAskAgain()) {
true
} else {
!hasHolderApplication
},
- isHolder = !hasHolderApplication
+ isHolder = !hasHolderApplication,
)
} else {
null
}
fun getPreferences(
- qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>,
- selectedPackage: String?
+ applicationItems: List<RoleApplicationItem>,
+ selectedPackage: UserPackage?,
): List<RequestRolePreference> {
- return qualifyingApplications
- .map { qualifyingApplication ->
+ return applicationItems
+ .map { applicationItem ->
+ val userPackage = UserPackage.from(applicationItem.applicationInfo)
RequestRolePreference(
- packageName = qualifyingApplication.first.packageName,
- label = Utils.getAppLabel(qualifyingApplication.first, context),
+ userPackage = userPackage,
+ label = Utils.getAppLabel(applicationItem.applicationInfo, context),
subTitle =
- if (qualifyingApplication.second) {
+ if (applicationItem.isHolderApplication) {
context.getString(R.string.request_role_current_default)
} else {
context.getString(role.requestDescriptionResource)
},
- icon = Utils.getBadgedIcon(context, qualifyingApplication.first),
- checked = qualifyingApplication.first.packageName.equals(selectedPackage),
+ icon = Utils.getBadgedIcon(context, applicationItem.applicationInfo),
+ checked = Objects.equals(userPackage, selectedPackage),
enabled =
if (!wearViewModel.dontAskAgain()) {
true
} else {
- qualifyingApplication.second
+ applicationItem.isHolderApplication
},
- isHolder = qualifyingApplication.second
+ isHolder = applicationItem.isHolderApplication,
)
}
.toList()
}
- private fun hasHolderApplication(
- qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>
- ): Boolean = qualifyingApplications.map { it.second }.contains(true)
+ private fun hasHolderApplication(applicationItems: List<RoleApplicationItem>): Boolean =
+ applicationItems.map { it.isHolderApplication }.contains(true)
fun shouldSetAsDefaultEnabled(enabled: Boolean): Boolean {
return enabled && (wearViewModel.dontAskAgain() || !wearViewModel.isHolderChecked)
}
- fun initializeHolderPackageName(qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>) {
- wearViewModel.holderPackageName =
- qualifyingApplications.find { it.second }?.first?.packageName
+ fun initializeHolderPackage(applicationItems: List<RoleApplicationItem>) {
+ wearViewModel.holderPackage =
+ applicationItems
+ .find { it.isHolderApplication }
+ ?.applicationInfo
+ ?.let { appInfo -> UserPackage.from(appInfo) }
}
- fun initializeSelectedPackageName() {
- if (wearViewModel.holderPackageName == null) {
- wearViewModel.selectedPackageName.value = null
+ fun initializeSelectedPackage() {
+ if (wearViewModel.holderPackage == null) {
+ wearViewModel.selectedPackage.value = null
} else {
- wearViewModel.selectedPackageName.value = packageName
+ wearViewModel.selectedPackage.value =
+ UserPackage.of(Process.myUserHandle(), packageName)
}
}
@@ -131,7 +138,7 @@ class WearRequestRoleHelper(
val icon: Drawable?,
val checked: Boolean,
val enabled: Boolean,
- val packageName: String?,
- val isHolder: Boolean
+ val userPackage: UserPackage?,
+ val isHolder: Boolean,
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt
index 13a9cb6d6..1367f4c3c 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt
@@ -16,8 +16,6 @@
package com.android.permissioncontroller.role.ui.wear
-import android.content.pm.ApplicationInfo
-import android.util.Pair
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.runtime.Composable
@@ -30,62 +28,67 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
-import androidx.wear.compose.material.ChipDefaults
-import androidx.wear.compose.material.MaterialTheme
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.ui.wear.elements.Chip
-import com.android.permissioncontroller.permission.ui.wear.elements.ListFooter
-import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
-import com.android.permissioncontroller.permission.ui.wear.elements.toggleChipBackgroundColors
+import com.android.permissioncontroller.role.UserPackage
import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData
+import com.android.permissioncontroller.role.ui.RoleApplicationItem
+import com.android.permissioncontroller.wear.permission.components.ScrollableScreen
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButton
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButtonStyle
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionListFooter
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControl
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControlStyle
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControlType
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
@Composable
fun WearRequestRoleScreen(
helper: WearRequestRoleHelper,
- onSetAsDefault: (Boolean, String?) -> Unit,
- onCanceled: () -> Unit
+ onSetAsDefault: (Boolean, UserPackage?) -> Unit,
+ onCanceled: () -> Unit,
) {
- val roleLiveData = helper.viewModel.roleLiveData.observeAsState(emptyList())
+ val roleLiveData = helper.viewModel.liveData.observeAsState(emptyList())
val manageRoleHolderState =
helper.viewModel.manageRoleHolderStateLiveData.observeAsState(
ManageRoleHolderStateLiveData.STATE_WORKING
)
val dontAskAgain = helper.wearViewModel.dontAskAgain.observeAsState(false)
- val selectedPackageName = helper.wearViewModel.selectedPackageName.observeAsState(null)
+ val selectedPackage = helper.wearViewModel.selectedPackage.observeAsState(null)
var isLoading by remember { mutableStateOf(true) }
if (isLoading && roleLiveData.value.isNotEmpty()) {
- helper.initializeHolderPackageName(roleLiveData.value)
- helper.initializeSelectedPackageName()
+ helper.initializeHolderPackage(roleLiveData.value)
+ helper.initializeSelectedPackage()
}
- val onCheckedChanged: (Boolean, String?, Boolean) -> Unit = { checked, packageName, isHolder ->
- if (checked) {
- helper.wearViewModel.selectedPackageName.value = packageName
- helper.wearViewModel.isHolderChecked = isHolder
+ val onCheckedChanged: (Boolean, UserPackage?, Boolean) -> Unit =
+ { checked, userPackage, isHolder ->
+ if (checked) {
+ helper.wearViewModel.selectedPackage.value = userPackage
+ helper.wearViewModel.isHolderChecked = isHolder
+ }
}
- }
val onDontAskAgainCheckedChanged: (Boolean) -> Unit = { checked ->
helper.wearViewModel.dontAskAgain.value = checked
if (checked) {
- helper.initializeSelectedPackageName()
+ helper.initializeSelectedPackage()
}
}
-
WearRequestRoleContent(
+ ResourceHelper.materialUIVersionInApp,
isLoading,
helper,
roleLiveData.value,
manageRoleHolderState.value == ManageRoleHolderStateLiveData.STATE_IDLE,
dontAskAgain.value,
- selectedPackageName.value,
+ selectedPackage.value,
onCheckedChanged,
onDontAskAgainCheckedChanged,
onSetAsDefault,
- onCanceled
+ onCanceled,
)
if (isLoading && roleLiveData.value.isNotEmpty()) {
@@ -95,65 +98,84 @@ fun WearRequestRoleScreen(
@Composable
internal fun WearRequestRoleContent(
+ materialUIVersion: WearPermissionMaterialUIVersion,
isLoading: Boolean,
helper: WearRequestRoleHelper,
- qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>,
+ applicationItems: List<RoleApplicationItem>,
enabled: Boolean,
dontAskAgain: Boolean,
- selectedPackageName: String?,
- onCheckedChanged: (Boolean, String?, Boolean) -> Unit,
+ selectedPackage: UserPackage?,
+ onCheckedChanged: (Boolean, UserPackage?, Boolean) -> Unit,
onDontAskAgainCheckedChanged: (Boolean) -> Unit,
- onSetAsDefault: (Boolean, String?) -> Unit,
- onCanceled: () -> Unit
+ onSetAsDefault: (Boolean, UserPackage?) -> Unit,
+ onCanceled: () -> Unit,
) {
ScrollableScreen(
+ materialUIVersion = materialUIVersion,
image = helper.getIcon(),
title = helper.getTitle(),
showTimeText = false,
- isLoading = isLoading
+ isLoading = isLoading,
) {
- helper.getNonePreference(qualifyingApplications, selectedPackageName)?.let {
+ helper.getNonePreference(applicationItems, selectedPackage)?.let { pref ->
item {
- ToggleChip(
- label = it.label,
- icon = it.icon,
- enabled = enabled && it.enabled,
- checked = it.checked,
+ WearPermissionToggleControl(
+ materialUIVersion = materialUIVersion,
+ label = pref.label,
+ iconBuilder = pref.icon?.let { WearPermissionIconBuilder.builder(it) },
+ enabled = enabled && pref.enabled,
+ checked = pref.checked,
onCheckedChanged = { checked ->
- run { onCheckedChanged(checked, it.packageName, it.isHolder) }
+ onCheckedChanged(checked, pref.userPackage, pref.isHolder)
},
- toggleControl = ToggleChipToggleControl.Radio,
- labelMaxLine = Integer.MAX_VALUE
+ toggleControl = WearPermissionToggleControlType.Radio,
+ labelMaxLines = Integer.MAX_VALUE,
)
}
- it.subTitle?.let { subTitle -> item { ListFooter(description = subTitle) } }
+ pref.subTitle?.let { subTitle ->
+ item {
+ WearPermissionListFooter(
+ materialUIVersion = materialUIVersion,
+ label = subTitle,
+ )
+ }
+ }
}
- for (pref in helper.getPreferences(qualifyingApplications, selectedPackageName)) {
+ for (pref in helper.getPreferences(applicationItems, selectedPackage)) {
item {
- ToggleChip(
+ WearPermissionToggleControl(
+ materialUIVersion = materialUIVersion,
label = pref.label,
- icon = pref.icon,
+ iconBuilder = pref.icon?.let { WearPermissionIconBuilder.builder(it) },
enabled = enabled && pref.enabled,
checked = pref.checked,
onCheckedChanged = { checked ->
- run { onCheckedChanged(checked, pref.packageName, pref.isHolder) }
+ onCheckedChanged(checked, pref.userPackage, pref.isHolder)
},
- toggleControl = ToggleChipToggleControl.Radio,
+ toggleControl = WearPermissionToggleControlType.Radio,
)
}
- pref.subTitle?.let { subTitle -> item { ListFooter(description = subTitle) } }
+ pref.subTitle?.let { subTitle ->
+ item {
+ WearPermissionListFooter(
+ materialUIVersion = materialUIVersion,
+ label = subTitle,
+ )
+ }
+ }
}
if (helper.showDontAskButton()) {
item {
- ToggleChip(
+ WearPermissionToggleControl(
+ materialUIVersion = materialUIVersion,
checked = dontAskAgain,
enabled = enabled,
onCheckedChanged = { checked -> run { onDontAskAgainCheckedChanged(checked) } },
label = stringResource(R.string.request_role_dont_ask_again),
- toggleControl = ToggleChipToggleControl.Checkbox,
- colors = toggleChipBackgroundColors(),
+ toggleControl = WearPermissionToggleControlType.Checkbox,
+ style = WearPermissionToggleControlStyle.Transparent,
modifier =
Modifier.testTag("com.android.permissioncontroller:id/dont_ask_again"),
)
@@ -163,17 +185,18 @@ internal fun WearRequestRoleContent(
item { Spacer(modifier = Modifier.height(14.dp)) }
item {
- Chip(
+ WearPermissionButton(
+ materialUIVersion = materialUIVersion,
label = stringResource(R.string.request_role_set_as_default),
- textColor = MaterialTheme.colors.background,
- colors = ChipDefaults.primaryChipColors(),
+ style = WearPermissionButtonStyle.Primary,
enabled = helper.shouldSetAsDefaultEnabled(enabled),
- onClick = { onSetAsDefault(dontAskAgain, selectedPackageName) },
+ onClick = { onSetAsDefault(dontAskAgain, selectedPackage) },
modifier = Modifier.testTag("android:id/button1"),
)
}
item {
- Chip(
+ WearPermissionButton(
+ materialUIVersion = materialUIVersion,
label = stringResource(R.string.cancel),
enabled = enabled,
onClick = { onCanceled() },
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRoleApplicationPreference.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRoleApplicationPreference.kt
index 6cd52f576..cb12bf70a 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRoleApplicationPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRoleApplicationPreference.kt
@@ -18,6 +18,7 @@ package com.android.permissioncontroller.role.ui.wear
import android.content.Context
import android.content.Intent
+import androidx.preference.PreferenceViewHolder
import androidx.preference.TwoStatePreference
import com.android.permissioncontroller.role.ui.RoleApplicationPreference
@@ -30,7 +31,8 @@ class WearRoleApplicationPreference(
defaultLabel: String,
val checked: Boolean,
val onDefaultCheckChanged: (Boolean) -> Unit = {},
- private var restrictionIntent: Intent? = null
+ private var restrictionIntent: Intent? = null,
+ private var contentDescription: String? = null,
) : TwoStatePreference(context), RoleApplicationPreference {
init {
title = defaultLabel
@@ -39,10 +41,22 @@ class WearRoleApplicationPreference(
fun getOnCheckChanged(): (Boolean) -> Unit =
restrictionIntent?.let { { _ -> context.startActivity(it) } } ?: onDefaultCheckChanged
+ override fun setContentDescription(contentDescription: String?) {
+ if (this.contentDescription != contentDescription) {
+ this.contentDescription = contentDescription
+ notifyChanged()
+ }
+ }
+
override fun setRestrictionIntent(restrictionIntent: Intent?) {
this.restrictionIntent = restrictionIntent
isEnabled = restrictionIntent == null
}
override fun asTwoStatePreference(): TwoStatePreference = this
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ holder.findViewById(android.R.id.title)?.let { it.contentDescription = contentDescription }
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRolePreference.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRolePreference.kt
index 43acf4293..670c136ea 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRolePreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRolePreference.kt
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.role.ui.wear
import android.content.Context
import android.content.Intent
import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.role.ui.RolePreference
import com.android.permissioncontroller.role.ui.TwoTargetPreference.OnSecondTargetClickListener
import com.android.settingslib.widget.TwoTargetPreference
@@ -28,13 +29,21 @@ class WearRolePreference(
context: Context,
val label: String,
val onDefaultClicked: () -> Unit = {},
- private var restrictionIntent: Intent? = null
+ private var restrictionIntent: Intent? = null,
+ private var summaryContentDescription: String? = null,
) : TwoTargetPreference(context), RolePreference {
override fun setOnSecondTargetClickListener(listener: OnSecondTargetClickListener?) {
// no-op
}
+ override fun setSummaryContentDescription(summaryContentDescription: String?) {
+ if (this.summaryContentDescription != summaryContentDescription) {
+ this.summaryContentDescription = summaryContentDescription
+ notifyChanged()
+ }
+ }
+
override fun setRestrictionIntent(restrictionIntent: Intent?) {
this.restrictionIntent = restrictionIntent
setEnabled(restrictionIntent == null)
@@ -42,6 +51,14 @@ class WearRolePreference(
override fun asPreference(): Preference = this
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ holder.findViewById(android.R.id.summary)?.let {
+ it.contentDescription = summaryContentDescription
+ }
+ }
+
fun getOnClicked(): () -> Unit =
restrictionIntent?.let { { context.startActivity(it) } } ?: onDefaultClicked
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/model/WearRequestRoleViewModel.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/model/WearRequestRoleViewModel.kt
index 692533916..7cc0b2b52 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/model/WearRequestRoleViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/model/WearRequestRoleViewModel.kt
@@ -17,16 +17,18 @@
package com.android.permissioncontroller.role.ui.wear.model
import android.os.Bundle
+import androidx.core.os.BundleCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
+import com.android.permissioncontroller.role.UserPackage
/** ViewModel for WearRequestRoleScreen. */
class WearRequestRoleViewModel : ViewModel() {
val dontAskAgain = MutableLiveData<Boolean>(false)
- val selectedPackageName = MutableLiveData<String?>(null)
+ val selectedPackage = MutableLiveData<UserPackage?>(null)
var isHolderChecked: Boolean = false
- var holderPackageName: String? = null
+ var holderPackage: UserPackage? = null
fun dontAskAgain(): Boolean {
return dontAskAgain.value ?: false
@@ -34,20 +36,25 @@ class WearRequestRoleViewModel : ViewModel() {
fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(STATE_DONT_ASK_AGAIN, dontAskAgain())
- outState.putString(STATE_SELECTED_PACKAGE_NAME, selectedPackageName.value)
+ outState.putParcelable(STATE_SELECTED_USER_PACKAGE, selectedPackage.value)
outState.putBoolean(STATE_IS_HOLDER_CHECKED, isHolderChecked)
}
fun onRestoreInstanceState(savedInstanceState: Bundle) {
dontAskAgain.value = savedInstanceState.getBoolean(STATE_DONT_ASK_AGAIN)
- selectedPackageName.value = savedInstanceState.getString(STATE_SELECTED_PACKAGE_NAME)
isHolderChecked = savedInstanceState.getBoolean(STATE_IS_HOLDER_CHECKED)
+ selectedPackage.value =
+ BundleCompat.getParcelable(
+ savedInstanceState,
+ STATE_SELECTED_USER_PACKAGE,
+ UserPackage::class.java,
+ )
}
companion object {
const val STATE_DONT_ASK_AGAIN = "WearRequestRoleViewModel.state.DONT_ASK_AGAIN"
- const val STATE_SELECTED_PACKAGE_NAME =
- "WearRequestRoleViewModel.state.SELECTED_PACKAGE_NAME"
+ const val STATE_SELECTED_USER_PACKAGE =
+ "WearRequestRoleViewModel.state.SELECTED_USER_PACKAGE"
const val STATE_IS_HOLDER_CHECKED = "WearRequestRoleViewModel.state.IS_HOLDER_CHECKED"
}
}
@@ -55,6 +62,7 @@ class WearRequestRoleViewModel : ViewModel() {
/** Factory for a WearRequestRoleViewModel */
class WearRequestRoleViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST") return WearRequestRoleViewModel() as T
+ @Suppress("UNCHECKED_CAST")
+ return WearRequestRoleViewModel() as T
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java b/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java
index c11a74259..255d88ff0 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java
@@ -26,12 +26,14 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.permissioncontroller.role.ui.RequestRoleItemView;
+import com.android.permissioncontroller.role.ui.RoleApplicationItem;
import com.android.permissioncontroller.role.ui.RoleApplicationPreference;
import com.android.permissioncontroller.role.ui.RolePreference;
import com.android.permissioncontroller.role.ui.behavior.RoleUiBehavior;
import com.android.role.controller.model.Role;
import java.util.List;
+import java.util.function.Predicate;
/**
* Utility methods for Role UI behavior
@@ -117,6 +119,19 @@ public final class RoleUiBehaviorUtils {
}
/**
+ * @see RoleUiBehavior#getRecommendedApplicationFilter
+ */
+ @NonNull
+ public static Predicate<RoleApplicationItem> getRecommendedApplicationFilter(
+ @NonNull Role role, @NonNull Context context) {
+ RoleUiBehavior uiBehavior = getUiBehavior(role);
+ if (uiBehavior == null) {
+ return applicationItem -> false;
+ }
+ return uiBehavior.getRecommendedApplicationFilter(role, context);
+ }
+
+ /**
* @see RoleUiBehavior#getConfirmationMessage
*/
@Nullable
diff --git a/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java b/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java
index 339b2a12a..4d778befb 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java
@@ -37,11 +37,24 @@ public class UserUtils {
private UserUtils() {}
/**
+ * Returns the parent of a given user, or user if it has no parent (e.g. it is the primary
+ * user)
+ */
+ @NonNull
+ public static UserHandle getProfileParentOrSelf(@NonNull UserHandle user,
+ @NonNull Context context) {
+ return com.android.role.controller.util.UserUtils.getProfileParentOrSelf(user, context);
+ }
+
+ /**
* Get the work profile of current user, if any.
+ * <p>
+ * If {@link Process#myUserHandle()} is the work profile, then {@code null} is returned
*
* @param context the {@code Context} to retrieve system services
*
- * @return the work profile of current user, or {@code null} if none
+ * @return the work profile of current user, or {@code null} if none or if work profile is the
+ * current process user
*/
@Nullable
public static UserHandle getWorkProfile(@NonNull Context context) {
@@ -65,6 +78,30 @@ public class UserUtils {
}
/**
+ * Get the work profile of current profile-group, if any.
+ * <p>
+ * {@link Process#myUserHandle()} may be the work profile, and is a valid returned value
+ *
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return the work profile in the current profile-group, or {@code null} if none
+ */
+ @Nullable
+ public static UserHandle getWorkProfileOrSelf(@NonNull Context context) {
+ UserManager userManager = context.getSystemService(UserManager.class);
+ List<UserHandle> profiles = userManager.getUserProfiles();
+ int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; i++) {
+ UserHandle profile = profiles.get(i);
+ if (!userManager.isManagedProfile(profile.getIdentifier())) {
+ continue;
+ }
+ return profile;
+ }
+ return null;
+ }
+
+ /**
* Get the private profile of current user, if any.
*
* @param context the {@code Context} to retrieve system services
@@ -94,6 +131,12 @@ public class UserUtils {
return null;
}
+ /**
+ * Returns whether the user is a private profile or not.
+ *
+ * @param userHandle the {@code UserHandle} to check is private profile
+ * @param context the {@code Context} to retrieve system services
+ */
private static boolean isPrivateProfile(@NonNull UserHandle userHandle,
@NonNull Context context) {
if (!SdkLevel.isAtLeastV() || !android.os.Flags.allowPrivateProfile()) {
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterSearchIndexablesProvider.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterSearchIndexablesProvider.kt
index c5e59d3df..8d7d96f99 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterSearchIndexablesProvider.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterSearchIndexablesProvider.kt
@@ -81,7 +81,7 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
cursor.addSafetySourcesGroupRow(
safetySourcesGroup,
safetyCenterResourcesApk,
- screenTitle
+ screenTitle,
)
}
safetySourcesGroup.safetySources
@@ -93,7 +93,7 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
safetySource,
safetyCenterResourcesApk,
safetyCenterManager,
- screenTitle
+ screenTitle,
)
}
}
@@ -123,14 +123,14 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
collectAllRemovableKeys(
safetyCenterManager,
keysToRemove,
- staticEntryGroupsAreRemovable = SdkLevel.isAtLeastU()
+ staticEntryGroupsAreRemovable = SdkLevel.isAtLeastU(),
)
keepActiveEntriesFromRemoval(safetyCenterManager, context, keysToRemove)
} else {
collectAllRemovableKeys(
safetyCenterManager,
keysToRemove,
- staticEntryGroupsAreRemovable = true
+ staticEntryGroupsAreRemovable = true,
)
}
@@ -169,8 +169,6 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
) {
val searchTerms =
safetyCenterResourcesApk.getNotEmptyStringOrNull(safetySource.searchTermsResId)
- var isPersonalEntryAdded = false
- var isWorkEntryAdded = false
fun MatrixCursor.addIndexableRow(title: CharSequence, profileType: ProfileType) =
newRow()
@@ -181,40 +179,41 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
.add(COLUMN_INTENT_ACTION, Intent.ACTION_SAFETY_CENTER)
.add(COLUMN_SCREEN_TITLE, screenTitle)
- if (safetySource.id == BIOMETRIC_SOURCE_ID) {
- // Correct Biometric Unlock title is only available when Biometric SafetySource have
- // sent the data to SafetyCenter. Only the main user and the work profile send data for
- // the Biometric Safety Source.
+ // If we prefer live title instead of config title for this entry, check for live source
+ // data first. Then later, add all missing entries that weren't found in live source data.
+ // Even if we index hidden entries, those will be filtered out via queryNonIndexableKeys.
+ var isSourceDataPersonalEntryAdded = false
+ var isSourceDataWorkEntryAdded = false
+ if (biometricsSources.contains(safetySource.id)) {
context.getSystemService(UserManager::class.java)?.let { userManager ->
safetyCenterManager.safetyEntries
- .associateBy { it.entryId }
- .filter { it.key.safetySourceId == BIOMETRIC_SOURCE_ID }
+ .filter { it.entryId.safetySourceId == safetySource.id }
.forEach {
- val isWorkProfile = userManager.isManagedProfile(it.key.userId)
+ val isWorkProfile = userManager.isManagedProfile(it.entryId.userId)
if (isWorkProfile) {
- isWorkEntryAdded = true
- addIndexableRow(it.value.title, ProfileType.MANAGED)
+ addIndexableRow(it.title, ProfileType.MANAGED)
+ isSourceDataWorkEntryAdded = true
} else {
- addIndexableRow(it.value.title, ProfileType.PRIMARY)
- isPersonalEntryAdded = true
+ addIndexableRow(it.title, ProfileType.PRIMARY)
+ isSourceDataPersonalEntryAdded = true
}
}
}
}
- if (!isPersonalEntryAdded) {
+ if (!isSourceDataPersonalEntryAdded) {
safetyCenterResourcesApk.getNotEmptyStringOrNull(safetySource.titleResId)?.let {
addIndexableRow(title = it, ProfileType.PRIMARY)
}
}
if (safetySource.profile == SafetySource.PROFILE_ALL) {
- if (!isWorkEntryAdded) {
+ if (!isSourceDataWorkEntryAdded) {
safetyCenterResourcesApk
.getNotEmptyStringOrNull(safetySource.titleForWorkResId)
?.let { addIndexableRow(title = it, ProfileType.MANAGED) }
}
- if (safetySource.id != BIOMETRIC_SOURCE_ID && isPrivateProfileSupported()) {
+ if (!biometricsSources.contains(safetySource.id) && isPrivateProfileSupported()) {
safetyCenterResourcesApk
.getNotEmptyStringOrNull(safetySource.titleForPrivateProfileResId)
?.let { addIndexableRow(title = it, ProfileType.PRIVATE) }
@@ -242,13 +241,12 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
get() =
safetyCenterConfig?.safetySourcesGroups?.asSequence()?.filter {
it.type != SAFETY_SOURCES_GROUP_TYPE_HIDDEN
- }
- ?: emptySequence()
+ } ?: emptySequence()
private fun collectAllRemovableKeys(
safetyCenterManager: SafetyCenterManager,
keysToRemove: MutableSet<String>,
- staticEntryGroupsAreRemovable: Boolean
+ staticEntryGroupsAreRemovable: Boolean,
) {
safetyCenterManager.safetySourcesGroupsWithEntries
.filter {
@@ -279,7 +277,7 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
private fun keepActiveEntriesFromRemoval(
safetyCenterManager: SafetyCenterManager,
context: Context,
- keysToRemove: MutableSet<String>
+ keysToRemove: MutableSet<String>,
) {
val safetyCenterData = safetyCenterManager.safetyCenterData
safetyCenterData.entriesOrGroups.forEach { entryOrGroup ->
@@ -306,7 +304,7 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
private fun keepEntryFromRemoval(
entryId: SafetyCenterEntryId,
context: Context,
- keysToRemove: MutableSet<String>
+ keysToRemove: MutableSet<String>,
) {
val userContext = context.createContextAsUser(UserHandle.of(entryId.userId), /* flags= */ 0)
val userUserManager = userContext.getSystemService(UserManager::class.java) ?: return
@@ -340,6 +338,12 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
companion object {
private const val BIOMETRIC_SOURCE_ID = "AndroidBiometrics"
+ private const val FACE_SOURCE_ID = "AndroidFaceUnlock"
+ private const val FINGERPRINT_SOURCE_ID = "AndroidFingerprintUnlock"
+ private const val WEAR_SOURCE_ID = "AndroidWearUnlock"
+
+ private val biometricsSources =
+ listOf(BIOMETRIC_SOURCE_ID, FACE_SOURCE_ID, FINGERPRINT_SOURCE_ID, WEAR_SOURCE_ID)
private val privacyControlKeys: List<String>
get() = Pref.values().map { it.key }
@@ -373,6 +377,6 @@ class SafetyCenterSearchIndexablesProvider : BaseSearchIndexablesProvider() {
enum class ProfileType {
PRIMARY,
MANAGED,
- PRIVATE
+ PRIVATE,
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java
index 3ab4faa66..54cb86ff3 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/ClickableDisabledSwitchPreference.java
@@ -26,7 +26,7 @@ import android.view.ViewGroup;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceViewHolder;
-import androidx.preference.SwitchPreference;
+import androidx.preference.SwitchPreferenceCompat;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel;
@@ -38,7 +38,7 @@ import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsVie
* method will not register any changes while it appears disabled.
*/
@RequiresApi(TIRAMISU)
-public class ClickableDisabledSwitchPreference extends SwitchPreference {
+public class ClickableDisabledSwitchPreference extends SwitchPreferenceCompat {
private boolean mAppearDisabled;
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/HighlightablePreferenceGroupAdapter.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/HighlightablePreferenceGroupAdapter.java
index c6cfa3a9a..a079405e6 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/HighlightablePreferenceGroupAdapter.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/HighlightablePreferenceGroupAdapter.java
@@ -21,17 +21,23 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
+import androidx.annotation.DrawableRes;
+import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceGroupAdapter;
import androidx.preference.PreferenceViewHolder;
import androidx.recyclerview.widget.RecyclerView;
import com.android.permissioncontroller.R;
+import com.android.settingslib.widget.SettingsPreferenceGroupAdapter;
+import com.android.settingslib.widget.SettingsThemeHelper;
import com.google.android.material.appbar.AppBarLayout;
@@ -41,7 +47,7 @@ import com.google.android.material.appbar.AppBarLayout;
*
* @see com.android.settings.widget.HighlightablePreferenceGroupAdapter
*/
-public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter {
+public class HighlightablePreferenceGroupAdapter extends SettingsPreferenceGroupAdapter {
private static final String TAG = "HighlightableAdapter";
private static final long DELAY_COLLAPSE_DURATION_MILLIS = 300L;
@@ -50,10 +56,12 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
private static final long HIGHLIGHT_FADE_OUT_DURATION = 500L;
private static final long HIGHLIGHT_FADE_IN_DURATION = 200L;
+ private final int mInitialBackgroundColor;
private final int mHighlightColor;
- private boolean mFadeInAnimated;
+ boolean mFadeInAnimated;
- private final int mNormalBackgroundRes;
+ private final Context mContext;
+ private final @DrawableRes int mNormalBackgroundRes;
private final String mHighlightKey;
private boolean mHighlightRequested;
private int mHighlightPosition = RecyclerView.NO_POSITION;
@@ -63,13 +71,27 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
super(preferenceGroup);
mHighlightKey = key;
mHighlightRequested = highlightRequested;
- final Context context = preferenceGroup.getContext();
- final TypedValue outValue = new TypedValue();
- context.getTheme()
+ mContext = preferenceGroup.getContext();
+
+ final TypedValue backgroundResOutValue = new TypedValue();
+ mContext.getTheme()
.resolveAttribute(
- android.R.attr.selectableItemBackground, outValue, true /* resolveRefs */);
- mNormalBackgroundRes = outValue.resourceId;
- mHighlightColor = context.getColor(R.color.preference_highlight_color);
+ android.R.attr.selectableItemBackground,
+ backgroundResOutValue,
+ true /* resolveRefs */);
+ mNormalBackgroundRes = backgroundResOutValue.resourceId;
+
+ if (SettingsThemeHelper.isExpressiveTheme(mContext)) {
+ final TypedValue backgroundColorOutValue = new TypedValue();
+ mContext.getTheme()
+ .resolveAttribute(
+ R.attr.colorSurface, backgroundColorOutValue, true /* resolveRefs */);
+ mInitialBackgroundColor = backgroundColorOutValue.resourceId;
+ } else {
+ mInitialBackgroundColor = mContext.getColor(android.R.color.transparent);
+ }
+
+ mHighlightColor = mContext.getColor(R.color.preference_highlight_color);
}
@Override
@@ -78,11 +100,13 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
updateBackground(holder, position);
}
- private void updateBackground(PreferenceViewHolder holder, int position) {
+ void updateBackground(PreferenceViewHolder holder, int position) {
View v = holder.itemView;
- if (position == mHighlightPosition
- && (mHighlightKey != null
- && TextUtils.equals(mHighlightKey, getItem(position).getKey()))) {
+ Preference preference = getItem(position);
+ if (preference != null
+ && position == mHighlightPosition
+ && (mHighlightKey != null && TextUtils.equals(mHighlightKey, preference.getKey()))
+ && v.isShown()) {
// This position should be highlighted. If it's highlighted before - skip animation.
addHighlightBackground(holder, !mFadeInAnimated);
} else if (Boolean.TRUE.equals(v.getTag(R.id.preference_highlighted))) {
@@ -109,31 +133,46 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
// Collapse app bar after 300 milliseconds.
if (appBarLayout != null) {
root.postDelayed(
- () -> {
- appBarLayout.setExpanded(false, true);
- },
- DELAY_COLLAPSE_DURATION_MILLIS);
+ () -> appBarLayout.setExpanded(false, true), DELAY_COLLAPSE_DURATION_MILLIS);
}
// Remove the animator as early as possible to avoid a RecyclerView crash.
recyclerView.setItemAnimator(null);
- // Scroll to correct position after 600 milliseconds.
+ // Scroll to correct position after a short delay.
root.postDelayed(
() -> {
if (ensureHighlightPosition()) {
recyclerView.smoothScrollToPosition(mHighlightPosition);
+ highlightAndFocusTargetItem(recyclerView, mHighlightPosition);
}
},
DELAY_HIGHLIGHT_DURATION_MILLIS);
+ }
- // Highlight preference after 900 milliseconds.
- root.postDelayed(
- () -> {
- if (ensureHighlightPosition()) {
- notifyItemChanged(mHighlightPosition);
- }
- },
- DELAY_COLLAPSE_DURATION_MILLIS + DELAY_HIGHLIGHT_DURATION_MILLIS);
+ private void highlightAndFocusTargetItem(RecyclerView recyclerView, int highlightPosition) {
+ RecyclerView.ViewHolder target =
+ recyclerView.findViewHolderForAdapterPosition(highlightPosition);
+ if (target != null) { // view already visible
+ notifyItemChanged(mHighlightPosition);
+ target.itemView.requestFocus();
+ } else { // otherwise we're about to scroll to that view (but we might not be scrolling yet)
+ recyclerView.addOnScrollListener(
+ new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ notifyItemChanged(mHighlightPosition);
+ RecyclerView.ViewHolder target =
+ recyclerView.findViewHolderForAdapterPosition(
+ highlightPosition);
+ if (target != null) {
+ target.itemView.requestFocus();
+ }
+ recyclerView.removeOnScrollListener(this);
+ }
+ }
+ });
+ }
}
/**
@@ -159,7 +198,7 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
}
/** Remove the highlighted background with a delay */
- public void requestRemoveHighlightDelayed(PreferenceViewHolder holder) {
+ private void requestRemoveHighlightDelayed(PreferenceViewHolder holder) {
final View v = holder.itemView;
v.postDelayed(
() -> {
@@ -172,33 +211,36 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
private void addHighlightBackground(PreferenceViewHolder holder, boolean animate) {
final View v = holder.itemView;
v.setTag(R.id.preference_highlighted, true);
+
if (!animate) {
- v.setBackgroundColor(mHighlightColor);
+ setBackgroundColor(v, mHighlightColor);
Log.d(TAG, "AddHighlight: Not animation requested - setting highlight background");
requestRemoveHighlightDelayed(holder);
return;
}
mFadeInAnimated = true;
- final int colorFrom = mNormalBackgroundRes;
- final int colorTo = mHighlightColor;
+
final ValueAnimator fadeInLoop =
- ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
+ ValueAnimator.ofObject(
+ new ArgbEvaluator(), mInitialBackgroundColor, mHighlightColor);
fadeInLoop.setDuration(HIGHLIGHT_FADE_IN_DURATION);
fadeInLoop.addUpdateListener(
- animator -> v.setBackgroundColor((int) animator.getAnimatedValue()));
+ animator -> setBackgroundColor(v, (int) animator.getAnimatedValue()));
fadeInLoop.setRepeatMode(ValueAnimator.REVERSE);
fadeInLoop.setRepeatCount(4);
fadeInLoop.start();
Log.d(TAG, "AddHighlight: starting fade in animation");
+
holder.setIsRecyclable(false);
requestRemoveHighlightDelayed(holder);
}
private void removeHighlightBackground(PreferenceViewHolder holder, boolean animate) {
final View v = holder.itemView;
+
if (!animate) {
v.setTag(R.id.preference_highlighted, false);
- v.setBackgroundResource(mNormalBackgroundRes);
+ clearBackgroundColor(v);
Log.d(TAG, "RemoveHighlight: No animation requested - setting normal background");
return;
}
@@ -208,27 +250,44 @@ public class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter
Log.d(TAG, "RemoveHighlight: Not highlighted - skipping");
return;
}
- int colorFrom = mHighlightColor;
- int colorTo = mNormalBackgroundRes;
v.setTag(R.id.preference_highlighted, false);
final ValueAnimator colorAnimation =
- ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
+ ValueAnimator.ofObject(
+ new ArgbEvaluator(), mHighlightColor, mInitialBackgroundColor);
colorAnimation.setDuration(HIGHLIGHT_FADE_OUT_DURATION);
colorAnimation.addUpdateListener(
- animator -> v.setBackgroundColor((int) animator.getAnimatedValue()));
+ animator -> setBackgroundColor(v, (int) animator.getAnimatedValue()));
colorAnimation.addListener(
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- // Animation complete - the background is now white. Change to
- // mNormalBackgroundRes
- // so it is white and has ripple on touch.
- v.setBackgroundResource(mNormalBackgroundRes);
+ // Animation complete - the background needs to be the target background.
+ clearBackgroundColor(v);
holder.setIsRecyclable(true);
}
});
colorAnimation.start();
Log.d(TAG, "Starting fade out animation");
}
+
+ private void setBackgroundColor(View v, int color) {
+ if (SettingsThemeHelper.isExpressiveTheme(mContext)) {
+ v.getBackground()
+ .setColorFilter(
+ new PorterDuffColorFilter(
+ color,
+ PorterDuff.Mode.SRC_ATOP));
+ } else {
+ v.setBackgroundColor(color);
+ }
+ }
+
+ private void clearBackgroundColor(View v) {
+ if (SettingsThemeHelper.isExpressiveTheme(mContext)) {
+ v.getBackground().clearColorFilter();
+ } else {
+ v.setBackgroundResource(mNormalBackgroundRes);
+ }
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
index 7622270b9..88759797e 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
@@ -52,6 +52,7 @@ import androidx.preference.PreferenceViewHolder;
import com.android.modules.utils.build.SdkLevel;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel;
+import com.android.settingslib.widget.GroupSectionDividerMixin;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.shape.AbsoluteCornerSize;
@@ -62,7 +63,8 @@ import java.util.Objects;
/** A preference that displays a card representing a {@link SafetyCenterIssue}. */
@RequiresApi(TIRAMISU)
-public class IssueCardPreference extends Preference implements ComparablePreference {
+public class IssueCardPreference extends Preference
+ implements ComparablePreference, GroupSectionDividerMixin {
public static final String TAG = IssueCardPreference.class.getSimpleName();
@@ -101,12 +103,13 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- holder.itemView.setBackgroundResource(mPositionInCardList.getBackgroundDrawableResId());
+ View issueCardView = holder.itemView.requireViewById(R.id.issue_card);
+ issueCardView.setBackgroundResource(mPositionInCardList.getBackgroundDrawableResId());
int topMargin = getTopMargin(mPositionInCardList, getContext());
- MarginLayoutParams layoutParams = (MarginLayoutParams) holder.itemView.getLayoutParams();
+ MarginLayoutParams layoutParams = (MarginLayoutParams) issueCardView.getLayoutParams();
if (layoutParams.topMargin != topMargin) {
layoutParams.topMargin = topMargin;
- holder.itemView.setLayoutParams(layoutParams);
+ issueCardView.setLayoutParams(layoutParams);
}
// Set default group visibility in case view is being reused
@@ -202,19 +205,20 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
private void configureSafetyProtectionView(PreferenceViewHolder holder) {
View safetyProtectionSectionView =
holder.findViewById(R.id.issue_card_protected_by_android);
+ View issueCard = holder.findViewById(R.id.issue_card);
if (safetyProtectionSectionView.getVisibility() == View.GONE) {
- holder.itemView.setPaddingRelative(
- holder.itemView.getPaddingStart(),
- holder.itemView.getPaddingTop(),
- holder.itemView.getPaddingEnd(),
+ issueCard.setPaddingRelative(
+ issueCard.getPaddingStart(),
+ issueCard.getPaddingTop(),
+ issueCard.getPaddingEnd(),
/* bottom= */ getContext()
.getResources()
.getDimensionPixelSize(R.dimen.sc_card_margin_bottom));
} else {
- holder.itemView.setPaddingRelative(
- holder.itemView.getPaddingStart(),
- holder.itemView.getPaddingTop(),
- holder.itemView.getPaddingEnd(),
+ issueCard.setPaddingRelative(
+ issueCard.getPaddingStart(),
+ issueCard.getPaddingTop(),
+ issueCard.getPaddingEnd(),
/* bottom= */ 0);
}
}
@@ -427,9 +431,7 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
TypedValue buttonThemeValue = new TypedValue();
mContext.getTheme()
.resolveAttribute(
- R.attr.scActionButtonTheme,
- buttonThemeValue,
- /* resolveRefs= */ false);
+ R.attr.scActionButtonTheme, buttonThemeValue, /* resolveRefs= */ false);
mContextThemeWrapper = new ContextThemeWrapper(context, buttonThemeValue.data);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt
index a63e19984..5c86b4515 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/MoreIssuesCardPreference.kt
@@ -24,6 +24,7 @@ import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.R
import com.android.permissioncontroller.safetycenter.ui.view.MoreIssuesHeaderView
+import com.android.settingslib.widget.GroupSectionDividerMixin
/** A preference that displays a card linking to a list of more {@link SafetyCenterIssue}. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@@ -34,8 +35,8 @@ internal class MoreIssuesCardPreference(
private var newMoreIssuesCardData: MoreIssuesCardData,
private val dismissedOnly: Boolean,
val isStaticHeader: Boolean,
- private val onClickListener: () -> Unit
-) : Preference(context), ComparablePreference {
+ private val onClickListener: () -> Unit,
+) : Preference(context), ComparablePreference, GroupSectionDividerMixin {
init {
layoutResource = R.layout.preference_more_issues_card
@@ -44,11 +45,12 @@ internal class MoreIssuesCardPreference(
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
- val issueHeaderView = holder.itemView as MoreIssuesHeaderView
+ val issueHeaderView =
+ holder.itemView.requireViewById<MoreIssuesHeaderView>(R.id.more_issues_card)
if (isStaticHeader) {
issueHeaderView.showStaticHeader(
context.getString(R.string.safety_center_dismissed_issues_card_title),
- newMoreIssuesCardData.severityLevel
+ newMoreIssuesCardData.severityLevel,
)
} else {
issueHeaderView.showExpandableHeader(
@@ -62,7 +64,7 @@ internal class MoreIssuesCardPreference(
}
),
overrideChevronIconResId,
- onClickListener
+ onClickListener,
)
}
}
@@ -94,5 +96,5 @@ internal class MoreIssuesCardPreference(
internal data class MoreIssuesCardData(
val severityLevel: Int,
val hiddenIssueCount: Int,
- val isExpanded: Boolean
+ val isExpanded: Boolean,
)
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt
index 57e4175ca..c5287af53 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyBrandChipPreference.kt
@@ -28,11 +28,12 @@ import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
import com.android.permissioncontroller.R
import com.android.permissioncontroller.safetycenter.SafetyCenterConstants
+import com.android.settingslib.widget.GroupSectionDividerMixin
/** A preference that displays the Security and Privacy brand name on a Safety Center subpage. */
@RequiresApi(UPSIDE_DOWN_CAKE)
internal class SafetyBrandChipPreference(context: Context, attrs: AttributeSet) :
- Preference(context, attrs) {
+ Preference(context, attrs), GroupSectionDividerMixin {
init {
setLayoutResource(R.layout.preference_brand_chip)
@@ -67,7 +68,7 @@ internal class SafetyBrandChipPreference(context: Context, attrs: AttributeSet)
fun closeSubpage(
fragmentActivity: FragmentActivity,
fragmentContext: Context,
- sessionId: Long
+ sessionId: Long,
) {
val openedFromHomepage =
fragmentActivity
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
index c6f2d146f..04206479f 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
@@ -29,6 +29,8 @@ import static com.android.permissioncontroller.safetycenter.SafetyCenterConstant
import static com.android.permissioncontroller.safetycenter.SafetyCenterConstants.PRIVATE_PROFILE_SUFFIX;
import static com.android.permissioncontroller.safetycenter.SafetyCenterConstants.WORK_PROFILE_SUFFIX;
+import static java.util.Objects.requireNonNull;
+
import android.app.ActionBar;
import android.content.Intent;
import android.content.res.Configuration;
@@ -60,6 +62,7 @@ import com.android.permissioncontroller.permission.utils.Utils;
import com.android.permissioncontroller.safetycenter.ui.model.PrivacyControlsViewModel.Pref;
import com.android.settingslib.activityembedding.ActivityEmbeddingUtils;
import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
+import com.android.settingslib.widget.SettingsThemeHelper;
import java.util.List;
import java.util.Objects;
@@ -78,10 +81,10 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity {
private static final String EXTRA_PREVENT_TRAMPOLINE_TO_SETTINGS =
"com.android.permissioncontroller.safetycenter.extra.PREVENT_TRAMPOLINE_TO_SETTINGS";
- private SafetyCenterManager mSafetyCenterManager;
+ @Nullable private SafetyCenterManager mSafetyCenterManager;
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSafetyCenterManager = getSystemService(SafetyCenterManager.class);
@@ -93,18 +96,25 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity {
return;
}
+ if (SettingsThemeHelper.isExpressiveTheme(this)) {
+ // Setting a theme programmatically causes standard preferences to display weirdly.
+ // See b/377519324.
+ setTheme(R.style.Theme_SafetyCenterExpressive);
+ }
+
Fragment frag;
+ Intent intent = getIntent();
final boolean maybeOpenSubpage =
SafetyCenterUiFlags.getShowSubpages()
- && getIntent().getAction().equals(ACTION_SAFETY_CENTER);
- if (maybeOpenSubpage && getIntent().hasExtra(EXTRA_SAFETY_SOURCES_GROUP_ID)) {
- String groupId = getIntent().getStringExtra(EXTRA_SAFETY_SOURCES_GROUP_ID);
+ && Objects.equals(intent.getAction(), ACTION_SAFETY_CENTER);
+ if (maybeOpenSubpage && intent.hasExtra(EXTRA_SAFETY_SOURCES_GROUP_ID)) {
+ String groupId = intent.getStringExtra(EXTRA_SAFETY_SOURCES_GROUP_ID);
frag = openRelevantSubpage(groupId);
- } else if (maybeOpenSubpage && getIntent().hasExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY)) {
- String preferenceKey = getIntent().getStringExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY);
+ } else if (maybeOpenSubpage && intent.hasExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY)) {
+ String preferenceKey = intent.getStringExtra(EXTRA_SETTINGS_FRAGMENT_ARGS_KEY);
String groupId = getParentGroupId(preferenceKey);
frag = openRelevantSubpage(groupId);
- } else if (getIntent().getAction().equals(PRIVACY_CONTROLS_ACTION)) {
+ } else if (Objects.equals(intent.getAction(), PRIVACY_CONTROLS_ACTION)) {
setTitle(R.string.privacy_controls_title);
frag = PrivacyControlsFragment.newInstance();
} else {
@@ -306,7 +316,8 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity {
return PRIVACY_SOURCES_GROUP_ID;
}
- SafetyCenterConfig safetyCenterConfig = mSafetyCenterManager.getSafetyCenterConfig();
+ SafetyCenterConfig safetyCenterConfig =
+ requireNonNull(mSafetyCenterManager).getSafetyCenterConfig();
String[] splitKey;
if (preferenceKey.endsWith(PERSONAL_PROFILE_SUFFIX)) {
splitKey = preferenceKey.split("_" + PERSONAL_PROFILE_SUFFIX);
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
index efbd57080..ed6bc382c 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
@@ -56,6 +56,7 @@ import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData;
import com.android.safetycenter.internaldata.SafetyCenterBundles;
import com.android.safetycenter.resources.SafetyCenterResourcesApk;
+import com.android.settingslib.widget.SettingsThemeHelper;
import kotlin.Unit;
@@ -121,12 +122,16 @@ public final class SafetyCenterDashboardFragment extends SafetyCenterFragment {
mEntriesGroup = getPreferenceScreen().findPreference(ENTRIES_GROUP_KEY);
mStaticEntriesGroup = getPreferenceScreen().findPreference(STATIC_ENTRIES_GROUP_KEY);
+ Preference spacerPreference = getPreferenceScreen().findPreference(SPACER_KEY);
+ if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
+ getPreferenceScreen().removePreference(spacerPreference);
+ }
+
if (mIsQuickSettingsFragment) {
getPreferenceScreen().removePreference(mEntriesGroup);
mEntriesGroup = null;
getPreferenceScreen().removePreference(mStaticEntriesGroup);
mStaticEntriesGroup = null;
- Preference spacerPreference = getPreferenceScreen().findPreference(SPACER_KEY);
getPreferenceScreen().removePreference(spacerPreference);
}
getSafetyCenterViewModel().getStatusUiLiveData().observe(this, this::updateStatus);
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
index 9feecf5d4..d29b0aa3e 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
@@ -22,7 +22,6 @@ import android.safetycenter.SafetyCenterErrorDetails
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.lifecycle.ViewModelProvider
-import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceScreen
import androidx.recyclerview.widget.RecyclerView
import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
@@ -33,10 +32,11 @@ import com.android.permissioncontroller.safetycenter.ui.model.LiveSafetyCenterVi
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel
import com.android.safetycenter.resources.SafetyCenterResourcesApk
+import com.android.settingslib.widget.SettingsBasePreferenceFragment
/** A base fragment that represents a page in Safety Center. */
@RequiresApi(TIRAMISU)
-abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
+abstract class SafetyCenterFragment : SettingsBasePreferenceFragment() {
lateinit var safetyCenterViewModel: SafetyCenterViewModel
lateinit var sameTaskSourceIds: List<String>
@@ -51,17 +51,16 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
override fun onCreateAdapter(
preferenceScreen: PreferenceScreen
- ): RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ ): RecyclerView.Adapter<out RecyclerView.ViewHolder> {
/* The scroll-to-result functionality for settings search is currently implemented only for
* subpages i.e. non expand-and-collapse type entries. Hence, we check that the flag is
* enabled before using an adapter that does the highlighting and scrolling. */
- val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
+ val adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder> =
if (SafetyCenterUiFlags.getShowSubpages()) {
highlightManager.createAdapter(preferenceScreen)
} else {
super.onCreateAdapter(preferenceScreen)
}
-
/* By default, the PreferenceGroupAdapter does setHasStableIds(true). Since each Preference
* is internally allocated with an auto-incremented ID, it does not allow us to gracefully
* update only changed preferences based on SafetyPreferenceComparisonCallback. In order to
@@ -77,10 +76,15 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
.split(",")
safetyCenterSessionId = requireArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID)
+ val activity = requireActivity()
safetyCenterViewModel =
ViewModelProvider(
- requireActivity(),
- LiveSafetyCenterViewModelFactory(requireActivity().getApplication())
+ activity,
+ LiveSafetyCenterViewModelFactory(
+ activity.application,
+ activity.taskId,
+ sameTaskSourceIds,
+ ),
)
.get(SafetyCenterViewModel::class.java)
safetyCenterViewModel.safetyCenterUiLiveData.observe(this) { uiData: SafetyCenterUiData? ->
@@ -91,8 +95,7 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
displayErrorDetails(errorDetails)
}
- val safetyCenterIntent: ParsedSafetyCenterIntent =
- requireActivity().intent.toSafetyCenterIntent()
+ val safetyCenterIntent: ParsedSafetyCenterIntent = activity.intent.toSafetyCenterIntent()
val isQsFragment =
getArguments()?.getBoolean(QUICK_SETTINGS_SAFETY_CENTER_FRAGMENT, false) ?: false
collapsableIssuesCardHelper =
@@ -177,7 +180,7 @@ abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
safetyCenterViewModel.interactionLogger.recordForIssue(
Action.SAFETY_CENTER_VIEWED,
maybeIssue,
- isDismissed = false
+ isDismissed = false,
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java
index 2ad282449..d9f45cc08 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsActivity.java
@@ -24,7 +24,9 @@ import android.permission.PermissionManager;
import androidx.fragment.app.FragmentActivity;
import com.android.modules.utils.build.SdkLevel;
+import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.Utils;
+import com.android.settingslib.widget.SettingsThemeHelper;
/** Activity for the Safety Center Quick Settings Activity */
public class SafetyCenterQsActivity extends FragmentActivity {
@@ -39,6 +41,12 @@ public class SafetyCenterQsActivity extends FragmentActivity {
return;
}
+ if (SettingsThemeHelper.isExpressiveTheme(this)) {
+ // Safe to set expressive theme here since QS doesn't display vanilla preferences.
+ // See b/377519324.
+ setTheme(R.style.Theme_SafetyCenterQsExpressive);
+ }
+
configureFragment();
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
index fdade2189..2e5ba49ae 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
@@ -28,6 +28,7 @@ import com.android.permissioncontroller.safetycenter.ui.SafetyBrandChipPreferenc
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.settingslib.widget.FooterPreference
+import com.android.settingslib.widget.SettingsThemeHelper
/** A fragment that represents a generic subpage in Safety Center. */
@RequiresApi(UPSIDE_DOWN_CAKE)
@@ -45,15 +46,16 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
setPreferencesFromResource(R.xml.safety_center_subpage, rootKey)
sourceGroupId = requireArguments().getString(SOURCE_GROUP_ID_KEY)!!
- subpageBrandChip = getPreferenceScreen().findPreference(BRAND_CHIP_KEY)!!
- subpageIllustration = getPreferenceScreen().findPreference(ILLUSTRATION_KEY)!!
- subpageIssueGroup = getPreferenceScreen().findPreference(ISSUE_GROUP_KEY)!!
- subpageEntryGroup = getPreferenceScreen().findPreference(ENTRY_GROUP_KEY)!!
- subpageFooter = getPreferenceScreen().findPreference(FOOTER_KEY)!!
+ subpageBrandChip = preferenceScreen.findPreference(BRAND_CHIP_KEY)!!
+ subpageIllustration = preferenceScreen.findPreference(ILLUSTRATION_KEY)!!
+ subpageIssueGroup = preferenceScreen.findPreference(ISSUE_GROUP_KEY)!!
+ subpageEntryGroup = preferenceScreen.findPreference(ENTRY_GROUP_KEY)!!
+ subpageFooter = preferenceScreen.findPreference(FOOTER_KEY)!!
subpageBrandChip.setupListener(requireActivity(), safetyCenterSessionId)
setupIllustration()
setupFooter()
+ maybeRemoveSpacer()
prerenderCurrentSafetyCenterData()
}
@@ -80,7 +82,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
return
}
- requireActivity().setTitle(entryGroup.title)
+ requireActivity().title = entryGroup.title
updateSafetyCenterIssues(uiData)
updateSafetyCenterEntries(entryGroup)
}
@@ -91,7 +93,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
val drawable = SafetyCenterResourcesApk(context).getDrawableByName(resName, context.theme)
if (drawable == null) {
Log.w(TAG, "$sourceGroupId doesn't have any matching illustration")
- subpageIllustration.setVisible(false)
+ subpageIllustration.isVisible = false
}
subpageIllustration.illustrationDrawable = drawable
@@ -102,12 +104,19 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
val footerText = SafetyCenterResourcesApk(requireContext()).getStringByName(resName)
if (footerText.isEmpty()) {
Log.w(TAG, "$sourceGroupId doesn't have any matching footer")
- subpageFooter.setVisible(false)
+ subpageFooter.isVisible = false
}
// footer is ordered last by default
// in order to keep a spacer after the footer, footer needs to be the second from last
- subpageFooter.setOrder(Int.MAX_VALUE - 2)
- subpageFooter.setSummary(footerText)
+ subpageFooter.order = Int.MAX_VALUE - 2
+ subpageFooter.summary = footerText
+ }
+
+ private fun maybeRemoveSpacer() {
+ if (SettingsThemeHelper.isExpressiveTheme(requireContext())) {
+ val spacerPreference = preferenceScreen.findPreference<SpacerPreference>(SPACER_KEY)!!
+ preferenceScreen.removePreference(spacerPreference)
+ }
}
private fun updateSafetyCenterIssues(uiData: SafetyCenterUiData?) {
@@ -131,7 +140,7 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
subpageIssues,
subpageDismissedIssues,
uiData.resolvedIssues,
- requireActivity().getTaskId()
+ requireActivity().taskId,
)
}
@@ -145,23 +154,24 @@ class SafetyCenterSubpageFragment : SafetyCenterFragment() {
PendingIntentSender.getTaskIdForEntry(
entry.id,
sameTaskSourceIds,
- requireActivity()
+ requireActivity(),
),
entry,
- safetyCenterViewModel
+ safetyCenterViewModel,
)
)
}
}
companion object {
- private val TAG: String = SafetyCenterSubpageFragment::class.java.simpleName
- private const val BRAND_CHIP_KEY: String = "subpage_brand_chip"
- private const val ILLUSTRATION_KEY: String = "subpage_illustration"
- private const val ISSUE_GROUP_KEY: String = "subpage_issue_group"
- private const val ENTRY_GROUP_KEY: String = "subpage_entry_group"
- private const val FOOTER_KEY: String = "subpage_footer"
- private const val SOURCE_GROUP_ID_KEY: String = "source_group_id"
+ private val TAG = SafetyCenterSubpageFragment::class.java.simpleName
+ private const val BRAND_CHIP_KEY = "subpage_brand_chip"
+ private const val ILLUSTRATION_KEY = "subpage_illustration"
+ private const val ISSUE_GROUP_KEY = "subpage_issue_group"
+ private const val ENTRY_GROUP_KEY = "subpage_entry_group"
+ private const val FOOTER_KEY = "subpage_footer"
+ private const val SPACER_KEY = "subpage_spacer"
+ private const val SOURCE_GROUP_ID_KEY = "source_group_id"
/** Creates an instance of SafetyCenterSubpageFragment with the arguments set */
@JvmStatic
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt
index 5acb27131..3976fa7e3 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyIllustrationPreference.kt
@@ -25,11 +25,12 @@ import androidx.annotation.RequiresApi
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.android.permissioncontroller.R
+import com.android.settingslib.widget.GroupSectionDividerMixin
/** A preference that displays the illustration on a Safety Center subpage. */
@RequiresApi(UPSIDE_DOWN_CAKE)
internal class SafetyIllustrationPreference(context: Context, attrs: AttributeSet) :
- Preference(context, attrs) {
+ Preference(context, attrs), GroupSectionDividerMixin {
init {
layoutResource = R.layout.preference_illustration
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
index 811841845..0bef71b3e 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
@@ -29,7 +29,6 @@ import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
-import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@@ -40,37 +39,27 @@ import com.android.permissioncontroller.R;
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel;
import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData;
import com.android.permissioncontroller.safetycenter.ui.view.StatusCardView;
+import com.android.settingslib.widget.GroupSectionDividerMixin;
-import kotlin.Pair;
-
-import java.util.List;
import java.util.Objects;
/** Preference which displays a visual representation of {@link SafetyCenterStatus}. */
@RequiresApi(TIRAMISU)
-public class SafetyStatusPreference extends Preference implements ComparablePreference {
+public class SafetyStatusPreference extends Preference
+ implements ComparablePreference, GroupSectionDividerMixin {
private static final String TAG = "SafetyStatusPreference";
+ private final SafetyStatusAnimationSequencer mSequencer = new SafetyStatusAnimationSequencer();
+
@Nullable private StatusUiData mStatus;
@Nullable private SafetyCenterViewModel mViewModel;
- private final TextFadeAnimator mTitleTextAnimator = new TextFadeAnimator(R.id.status_title);
-
- private final TextFadeAnimator mSummaryTextAnimator = new TextFadeAnimator(R.id.status_summary);
-
- private final TextFadeAnimator mAllTextAnimator =
- new TextFadeAnimator(List.of(R.id.status_title, R.id.status_summary));
-
- private boolean mFirstBind = true;
-
public SafetyStatusPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.preference_safety_status);
}
- private boolean mIsTextChangeAnimationRunning;
- private final SafetyStatusAnimationSequencer mSequencer = new SafetyStatusAnimationSequencer();
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
@@ -82,7 +71,8 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
}
Context context = getContext();
- StatusCardView statusCardView = (StatusCardView) holder.itemView;
+ StatusCardView statusCardView = holder.itemView.requireViewById(R.id.status_card);
+
configureButtons(context, statusCardView);
statusCardView
.getTitleAndSummaryContainerView()
@@ -90,9 +80,7 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
updateStatusIcon(statusCardView);
- updateStatusText(statusCardView.getTitleView(), statusCardView.getSummaryView());
-
- mFirstBind = false;
+ statusCardView.showText(mStatus);
}
private void configureButtons(Context context, StatusCardView statusCardView) {
@@ -122,14 +110,6 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
statusCardView.showButtons(mStatus);
}
- private void updateStatusText(TextView title, TextView summary) {
- if (mFirstBind) {
- title.setText(mStatus.getTitle());
- summary.setText(mStatus.getSummary(getContext()));
- }
- runTextAnimationIfNeeded(title, summary);
- }
-
private void updateStatusIcon(StatusCardView statusCardView) {
int severityLevel = mStatus.getSeverityLevel();
boolean isRefreshing = mStatus.isRefreshInProgress();
@@ -140,33 +120,6 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
/* scanningAnimation= */ null);
}
- private void runTextAnimationIfNeeded(TextView titleView, TextView summaryView) {
- if (mIsTextChangeAnimationRunning) {
- return;
- }
- Log.v(TAG, "Starting status text animation");
- String titleText = mStatus.getTitle().toString();
- String summaryText = mStatus.getSummary(getContext()).toString();
- boolean titleEquals = titleView.getText().toString().equals(titleText);
- boolean summaryEquals = summaryView.getText().toString().equals(summaryText);
- Runnable onFinish =
- () -> {
- Log.v(TAG, "Finishing status text animation");
- mIsTextChangeAnimationRunning = false;
- runTextAnimationIfNeeded(titleView, summaryView);
- };
- mIsTextChangeAnimationRunning = !titleEquals || !summaryEquals;
- if (!titleEquals && !summaryEquals) {
- Pair<TextView, String> titleChange = new Pair<>(titleView, titleText);
- Pair<TextView, String> summaryChange = new Pair<>(summaryView, summaryText);
- mAllTextAnimator.animateChangeText(List.of(titleChange, summaryChange), onFinish);
- } else if (!titleEquals) {
- mTitleTextAnimator.animateChangeText(titleView, titleText, onFinish);
- } else if (!summaryEquals) {
- mSummaryTextAnimator.animateChangeText(summaryView, summaryText, onFinish);
- }
- }
-
private void startScanningAnimation(StatusCardView statusCardView) {
mSequencer.onStartScanningAnimationStart();
ImageView statusImage = statusCardView.getStatusImageView();
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt
index 030b67be9..7f619d1ca 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SpacerPreference.kt
@@ -55,6 +55,7 @@ internal class SpacerPreference(context: Context, attrs: AttributeSet) :
}
private var maxKnownToolbarHeight = 0
+
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val spacer = holder.itemView
@@ -74,7 +75,7 @@ internal class SpacerPreference(context: Context, attrs: AttributeSet) :
oldLeft: Int,
oldTop: Int,
oldRight: Int,
- oldBottom: Int
+ oldBottom: Int,
) {
adjustHeight(spacer)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/IssueUiData.kt
index a4ce4e764..e260bb917 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/IssueUiData.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.safetycenter.ui.model
-public enum class ToggleChipToggleControl {
- Switch,
- Radio,
- Checkbox
-}
+import android.safetycenter.SafetyCenterIssue
+
+/** UI model representation of [SafetyCenterIssue] */
+data class IssueUiData(
+ val issue: SafetyCenterIssue,
+ val isDismissed: Boolean,
+ val resolvedIssueActionId: String? = null,
+ val launchTaskId: Int? = null,
+)
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
index 4ddcf1c3d..0b976f49d 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
@@ -43,11 +43,16 @@ import com.android.safetycenter.internaldata.SafetyCenterIds
/* A SafetyCenterViewModel that talks to the real backing service for Safety Center. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
-class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
+class LiveSafetyCenterViewModel(
+ app: Application,
+ private val taskId: Int,
+ private val sameTaskSourceIds: List<String>,
+) : SafetyCenterViewModel(app) {
private val TAG: String = LiveSafetyCenterViewModel::class.java.simpleName
override val statusUiLiveData: LiveData<StatusUiData>
get() = safetyCenterUiLiveData.map { StatusUiData(it.safetyCenterData) }
+
override val safetyCenterUiLiveData: LiveData<SafetyCenterUiData> by this::_safetyCenterLiveData
override val errorLiveData: LiveData<SafetyCenterErrorDetails> by this::_errorLiveData
@@ -65,7 +70,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
private val safetyCenterManager = app.getSystemService(SafetyCenterManager::class.java)!!
override fun getCurrentSafetyCenterDataAsUiData(): SafetyCenterUiData =
- SafetyCenterUiData(safetyCenterManager.safetyCenterData)
+ uiData(safetyCenterManager.safetyCenterData)
override fun dismissIssue(issue: SafetyCenterIssue) {
safetyCenterManager.dismissSafetyCenterIssue(issue.id)
@@ -74,7 +79,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
override fun executeIssueAction(
issue: SafetyCenterIssue,
action: SafetyCenterIssue.Action,
- launchTaskId: Int?
+ launchTaskId: Int?,
) {
val issueId =
if (launchTaskId != null) {
@@ -107,9 +112,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
override fun navigateToSafetyCenter(context: Context, navigationSource: NavigationSource?) {
val intent = Intent(ACTION_SAFETY_CENTER)
- if (navigationSource != null) {
- navigationSource.addToIntent(intent)
- }
+ navigationSource?.addToIntent(intent)
context.startActivity(intent)
}
@@ -132,7 +135,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
} else {
safetyCenterManager.refreshSafetySources(
SafetyCenterManager.REFRESH_REASON_PAGE_OPEN,
- safetySourceIds
+ safetySourceIds,
)
}
}
@@ -174,7 +177,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
override fun onActive() {
safetyCenterManager.addOnSafetyCenterDataChangedListener(
getMainExecutor(app.applicationContext),
- this
+ this,
)
super.onActive()
}
@@ -209,7 +212,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
Log.d(
TAG,
"Received SafetyCenterData while issue resolution animations" +
- " occurring. Will update UI with new data soon."
+ " occurring. Will update UI with new data soon.",
)
return
}
@@ -254,7 +257,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
private fun isCurrentlyScanning(): Boolean = value?.safetyCenterData?.isScanning() ?: false
private fun sendNextData() {
- value = SafetyCenterUiData(safetyCenterDataQueue.removeFirst())
+ value = uiData(safetyCenterDataQueue.removeFirst())
}
private fun skipNextData() = safetyCenterDataQueue.removeFirst()
@@ -270,7 +273,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
// The current SafetyCenterData still contains the resolved SafetyCenterIssue objects.
// Send it with the resolved IDs so the UI can generate the correct preferences and
// trigger the right animations for issue resolution.
- value = SafetyCenterUiData(currentData, currentResolvedIssues)
+ value = uiData(currentData, currentResolvedIssues)
}
@MainThread
@@ -279,6 +282,11 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
maybeProcessDataToNextResolvedIssues()
}
}
+
+ private fun uiData(
+ safetyCenterData: SafetyCenterData,
+ resolvedIssues: Map<IssueId, ActionId> = emptyMap(),
+ ) = SafetyCenterUiData(safetyCenterData, taskId, sameTaskSourceIds, resolvedIssues)
}
/** Returns inflight issues pending resolution */
@@ -309,8 +317,15 @@ private val SafetyCenterData.allResolvableIssues: Sequence<SafetyCenterIssue>
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
-class LiveSafetyCenterViewModelFactory(private val app: Application) : ViewModelProvider.Factory {
+class LiveSafetyCenterViewModelFactory
+@JvmOverloads
+constructor(
+ private val app: Application,
+ private val taskId: Int = 0,
+ private val sameTaskSourceIds: List<String> = emptyList(),
+) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
- @Suppress("UNCHECKED_CAST") return LiveSafetyCenterViewModel(app) as T
+ @Suppress("UNCHECKED_CAST")
+ return LiveSafetyCenterViewModel(app, taskId, sameTaskSourceIds) as T
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt
index 69a315f08..d8aadae2f 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt
@@ -29,11 +29,21 @@ import com.android.safetycenter.internaldata.SafetyCenterIds
import com.android.safetycenter.internaldata.SafetyCenterIssueKey
/** UI model representation of Safety Center Data */
+@RequiresApi(TIRAMISU)
data class SafetyCenterUiData(
val safetyCenterData: SafetyCenterData,
- val resolvedIssues: Map<IssueId, ActionId> = emptyMap()
+ private val taskId: Int,
+ private val sameTaskSourceIds: List<String>,
+ val resolvedIssues: Map<IssueId, ActionId> = emptyMap(),
) {
- @RequiresApi(TIRAMISU)
+
+ val issueUiDatas: List<IssueUiData> by
+ lazy(LazyThreadSafetyMode.NONE) {
+ safetyCenterData.issues.map {
+ IssueUiData(it, false, resolvedIssues[it.id], getLaunchTaskIdForIssue(it))
+ }
+ }
+
fun getMatchingIssue(issueKey: SafetyCenterIssueKey): SafetyCenterIssue? {
return safetyCenterData.issues.find {
SafetyCenterIds.issueIdFromString(it.id).safetyCenterIssueKey == issueKey
@@ -67,7 +77,7 @@ data class SafetyCenterUiData(
@RequiresApi(UPSIDE_DOWN_CAKE)
private fun selectMatchingIssuesForGroup(
groupId: String,
- issues: List<SafetyCenterIssue>
+ issues: List<SafetyCenterIssue>,
): List<SafetyCenterIssue> {
val issuesToGroups = safetyCenterData.extras.getBundle(ISSUES_TO_GROUPS_BUNDLE_KEY)
return issues.filter {
@@ -84,4 +94,12 @@ data class SafetyCenterUiData(
@RequiresApi(UPSIDE_DOWN_CAKE)
fun SafetyCenterData.visibleDismissedIssues() =
dismissedIssues.filter { it.severityLevel > ISSUE_SEVERITY_LEVEL_OK }
+
+ private fun getLaunchTaskIdForIssue(issue: SafetyCenterIssue): Int? {
+ val sourceId: String =
+ SafetyCenterIds.issueIdFromString(issue.id)
+ .getSafetyCenterIssueKey()
+ .getSafetySourceId()
+ return if (sameTaskSourceIds.contains(sourceId)) taskId else null
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/MoreIssuesHeaderView.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/MoreIssuesHeaderView.kt
index ebfbef714..3eb2e7904 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/MoreIssuesHeaderView.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/MoreIssuesHeaderView.kt
@@ -35,7 +35,7 @@ constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
- defStyleRes: Int = 0
+ defStyleRes: Int = 0,
) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
init {
@@ -55,32 +55,36 @@ constructor(
nextData: MoreIssuesCardData,
title: String,
@DrawableRes overrideChevronIconResId: Int?,
- onClick: () -> Unit
+ onClick: () -> Unit,
) {
titleView.text = title
updateStatusIcon(previousData?.severityLevel, nextData.severityLevel)
updateExpandCollapseButton(
previousData?.isExpanded,
nextData.isExpanded,
- overrideChevronIconResId
+ overrideChevronIconResId,
)
updateIssueCount(previousData?.hiddenIssueCount, nextData.hiddenIssueCount)
updateBackground(previousData?.isExpanded, nextData.isExpanded)
setOnClickListener { onClick() }
- val expansionString =
- StringUtils.getIcuPluralsString(
- context,
- R.string.safety_center_more_issues_card_expand_action,
- nextData.hiddenIssueCount
- )
+ val actionString =
+ if (nextData.isExpanded) {
+ context.getString(R.string.safety_center_more_issues_card_collapse_action)
+ } else {
+ StringUtils.getIcuPluralsString(
+ context,
+ R.string.safety_center_more_issues_card_expand_action,
+ nextData.hiddenIssueCount,
+ )
+ }
// Replacing the on-click label to indicate the number of hidden issues. The on-click
// command is set to null so that it uses the existing expansion behaviour.
ViewCompat.replaceAccessibilityAction(
this,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
- expansionString,
- null
+ actionString,
+ null,
)
}
@@ -96,7 +100,7 @@ constructor(
private fun updateExpandCollapseButton(
wasExpanded: Boolean?,
isExpanded: Boolean,
- @DrawableRes overrideChevronIconResId: Int?
+ @DrawableRes overrideChevronIconResId: Int?,
) {
expandCollapseLayout.isVisible = true
if (overrideChevronIconResId != null) {
@@ -105,12 +109,12 @@ constructor(
if (isExpanded) {
expandCollapseIcon.animate(
R.drawable.more_issues_expand_anim,
- R.drawable.ic_collapse_issues
+ R.drawable.ic_collapse_issues,
)
} else {
expandCollapseIcon.animate(
R.drawable.more_issues_collapse_anim,
- R.drawable.ic_expand_issues
+ R.drawable.ic_expand_issues,
)
}
} else {
@@ -132,7 +136,7 @@ constructor(
statusIconView,
previousSeverityLevel,
endSeverityLevel,
- selectIconResId(endSeverityLevel)
+ selectIconResId(endSeverityLevel),
)
} else {
statusIconView.setImageResource(selectIconResId(endSeverityLevel))
@@ -198,7 +202,7 @@ constructor(
bottomRadiusStart,
bottomRadiusStart,
bottomRadiusStart,
- bottomRadiusStart
+ bottomRadiusStart,
)
setCornerRadii(ripple, cornerRadii)
if (bottomRadiusEnd != bottomRadiusStart) {
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt
index 6a415c563..bb417104d 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt
@@ -21,6 +21,7 @@ import android.os.Build
import android.util.AttributeSet
import android.widget.ImageView
import android.widget.LinearLayout
+import android.widget.TextSwitcher
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.constraintlayout.widget.ConstraintLayout
@@ -35,7 +36,7 @@ constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
- defStyleRes: Int = 0
+ defStyleRes: Int = 0,
) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) {
init {
@@ -44,11 +45,29 @@ constructor(
val statusImageView: ImageView by lazyView(R.id.status_image)
val titleAndSummaryContainerView: LinearLayout by lazyView(R.id.status_title_and_summary)
- val titleView: TextView by lazyView(R.id.status_title)
- val summaryView: TextView by lazyView(R.id.status_summary)
+ private val titleView: TextSwitcher by lazyView(R.id.status_title)
+ private val summaryView: TextSwitcher by lazyView(R.id.status_summary)
val reviewSettingsButton: MaterialButton by lazyView(R.id.review_settings_button)
val rescanButton: MaterialButton by lazyView(R.id.rescan_button)
+ fun showText(statusUiData: StatusUiData) {
+ titleView.updateText(statusUiData.title)
+ summaryView.updateText(statusUiData.getSummary(context))
+ }
+
+ private fun TextSwitcher.updateText(newText: CharSequence) {
+ val currentText: CharSequence? = (currentView as TextView).text
+ if (currentText == newText) {
+ return
+ }
+
+ if (currentText.isNullOrBlank()) {
+ setCurrentText(newText)
+ } else {
+ setText(newText)
+ }
+ }
+
fun showButtons(statusUiData: StatusUiData) {
rescanButton.isEnabled = !statusUiData.isRefreshInProgress
diff --git a/PermissionController/tests/inprocess/Android.bp b/PermissionController/tests/inprocess/Android.bp
index 60b35e80f..49e4e9474 100644
--- a/PermissionController/tests/inprocess/Android.bp
+++ b/PermissionController/tests/inprocess/Android.bp
@@ -50,6 +50,10 @@ android_test {
"compatibility-device-util-axt",
"kotlin-test",
"permission-test-util-lib",
+ // This may result in two flag libs being included. This should only be used for Flag
+ //string referencing for test annotations.
+ "com.android.permission.flags-aconfig-java-export",
+ "android.permission.flags-aconfig-java-export",
],
data: [
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/GetPermissionGroupInfoTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/GetPermissionGroupInfoTest.kt
index b20e99c38..d06de169b 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/GetPermissionGroupInfoTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/GetPermissionGroupInfoTest.kt
@@ -18,12 +18,14 @@ package com.android.permissioncontroller.permission
import android.content.Context
import android.os.Build
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
+import org.junit.Rule
import org.junit.Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
@@ -32,6 +34,8 @@ class GetPermissionGroupInfoTest {
private val packageManager = context.packageManager
private val timeoutMs: Long = 10000
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+
@Test
fun assertAllPlatformPermGroupPermListsMatch() {
val groups = PermissionMapping.getPlatformPermissionGroups()
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/data/AttributionLabelLiveDataTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/data/AttributionLabelLiveDataTest.kt
index bc9e5d6ff..7c735a451 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/data/AttributionLabelLiveDataTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/data/AttributionLabelLiveDataTest.kt
@@ -22,10 +22,12 @@ import android.os.Process.myUserHandle
import android.os.UserHandle
import android.permission.cts.PermissionUtils.install
import android.permission.cts.PermissionUtils.uninstallApp
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
private const val APK = "/data/local/tmp/pc-inprocess/AppThatUsesCameraPermission.apk"
@@ -34,6 +36,8 @@ private const val PKG = "com.android.permissioncontroller.tests.appthatrequestpe
class AttributionLabelLiveDataTest {
private val context = InstrumentationRegistry.getInstrumentation().context as Context
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+
@Before
fun installAttributingApp() {
install(APK)
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/ui/model/PermissionUsageDetailsViewModelTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/ui/model/PermissionUsageDetailsViewModelTest.kt
index eb5fdefdb..644bdfd15 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/ui/model/PermissionUsageDetailsViewModelTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/ui/model/PermissionUsageDetailsViewModelTest.kt
@@ -16,16 +16,14 @@
package com.android.permissioncontroller.permission.ui.model
-import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.SavedStateHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.modules.utils.build.SdkLevel
-import com.android.permission.flags.Flags
import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelV2
import com.google.common.truth.Truth
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -42,15 +40,14 @@ class PermissionUsageDetailsViewModelTest {
@JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
- @RequiresFlagsEnabled(Flags.FLAG_LIVEDATA_REFACTOR_PERMISSION_TIMELINE_ENABLED)
fun verifyUiStateIsGeneratedSuccessfully() {
Assume.assumeTrue(SdkLevel.isAtLeastS())
lateinit var uiState: PermissionUsageDetailsUiState.Success
val viewModel =
- PermissionUsageDetailsViewModelV2.create(
+ PermissionUsageDetailsViewModel.create(
PermissionControllerApplication.get(),
SavedStateHandle(mapOf("show7Days" to true, "showSystem" to true)),
- LOCATION_PERMISSION_GROUP
+ LOCATION_PERMISSION_GROUP,
)
val countDownLatch = CountDownLatch(1)
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt
deleted file mode 100644
index aa7d7da60..000000000
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.permission.util
-
-import android.Manifest
-import android.app.AppOpsManager
-import android.health.connect.HealthPermissions
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.permissioncontroller.permission.utils.PermissionMapping
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class PermissionMappingTest {
- @Test
- fun testGetPlatformPermissionGroupForOp_healthPermissionGroup() {
- assertThat(
- PermissionMapping.getPlatformPermissionGroupForOp(
- AppOpsManager.OPSTR_READ_WRITE_HEALTH_DATA
- )
- )
- .isEqualTo(HealthPermissions.HEALTH_PERMISSION_GROUP)
- }
-
- @Test
- fun testGetPlatformPermissionGroupForOp_microphone() {
- assertThat(
- PermissionMapping.getPlatformPermissionGroupForOp(
- AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE
- )
- )
- .isEqualTo(Manifest.permission_group.MICROPHONE)
- }
-
- @Test
- fun testGetPlatformPermissionGroupForOp_camera() {
- assertThat(
- PermissionMapping.getPlatformPermissionGroupForOp(
- AppOpsManager.OPSTR_PHONE_CALL_CAMERA
- )
- )
- .isEqualTo(Manifest.permission_group.CAMERA)
- }
-
- @Test
- fun testGetPlatformPermissionGroupForOp_InvalidOpName() {
- try {
- assertThat(PermissionMapping.getPlatformPermissionGroupForOp("invalid_opName"))
- .isEqualTo(null)
- } catch (e: IllegalArgumentException) {
- // ignore wtf may throw in some configuration.
- }
- }
-
- @Test
- fun testGetPlatformPermissionGroupForOp_readContacts() {
- assertThat(
- PermissionMapping.getPlatformPermissionGroupForOp(AppOpsManager.OPSTR_READ_CONTACTS)
- )
- .isEqualTo(
- PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.READ_CONTACTS)
- )
- }
-}
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/ArrayUtilsTest.kt
index 708d4222f..6590a4516 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/ArrayUtilsTest.kt
@@ -14,13 +14,16 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.util
+package com.android.permissioncontroller.permission.utils
-import com.android.permissioncontroller.permission.utils.ArrayUtils
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
import org.junit.Test
class ArrayUtilsTest {
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+
@Test
fun appendString_appendToNull_returnsArrayWithString() {
assertThat(ArrayUtils.appendString(null, TEST_STRING)).isEqualTo(arrayOf(TEST_STRING))
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/CollectionUtilsTest.kt
index 3d4bd28ff..771f4ab6e 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/CollectionUtilsTest.kt
@@ -14,13 +14,16 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.util
+package com.android.permissioncontroller.permission.utils
-import com.android.permissioncontroller.permission.utils.CollectionUtils
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
import org.junit.Test
class CollectionUtilsTest {
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+
@Test
fun testContains_true() {
val byteArrays = setOf(TEST_BYTE_ARRAY_1, TEST_BYTE_ARRAY_2, TEST_BYTE_ARRAY_3)
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/KotlinUtilsTest.kt
index 37aa8d988..2c8eefcd3 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/KotlinUtilsTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.util
+package com.android.permissioncontroller.permission.utils
import android.Manifest.permission.READ_MEDIA_IMAGES
import android.Manifest.permission.READ_MEDIA_VIDEO
@@ -30,11 +30,12 @@ import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
@@ -48,6 +49,8 @@ import org.mockito.Mockito.`when` as whenever
class KotlinUtilsTest {
private val targetContext = InstrumentationRegistry.getInstrumentation().targetContext
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+
@Test
fun convertToBitmap_argb888BitmapDrawable_returnsSameBitmap() {
val bitmap = Bitmap.createBitmap(/* width= */ 5, /* height= */ 10, Bitmap.Config.ARGB_8888)
@@ -64,11 +67,15 @@ class KotlinUtilsTest {
class FakeDrawable(private val intrinsicSize: Int) : Drawable() {
override fun getIntrinsicWidth() = intrinsicSize
+
override fun getIntrinsicHeight() = intrinsicSize
override fun draw(canvas: Canvas) = Unit // no-op
+
override fun getOpacity() = throw UnsupportedOperationException()
+
override fun setAlpha(alpha: Int) = throw UnsupportedOperationException()
+
override fun setColorFilter(colorFilter: ColorFilter?) =
throw UnsupportedOperationException()
}
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/PermissionMappingTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/PermissionMappingTest.kt
new file mode 100644
index 000000000..cf349b8d4
--- /dev/null
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/PermissionMappingTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.utils
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.health.connect.HealthPermissions
+import android.os.Build
+import android.permission.flags.Flags
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Assume.assumeTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class PermissionMappingTest {
+
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+ @JvmField @Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Test
+ fun testGetPlatformPermissionGroupForOp_healthPermissionGroup() {
+ assertThat(
+ PermissionMapping.getPlatformPermissionGroupForOp(
+ AppOpsManager.OPSTR_READ_WRITE_HEALTH_DATA
+ )
+ )
+ .isEqualTo(HealthPermissions.HEALTH_PERMISSION_GROUP)
+ }
+
+ @Test
+ fun testGetPlatformPermissionGroupForOp_microphone() {
+ assertThat(
+ PermissionMapping.getPlatformPermissionGroupForOp(
+ AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE
+ )
+ )
+ .isEqualTo(Manifest.permission_group.MICROPHONE)
+ }
+
+ @Test
+ fun testGetPlatformPermissionGroupForOp_camera() {
+ assertThat(
+ PermissionMapping.getPlatformPermissionGroupForOp(
+ AppOpsManager.OPSTR_PHONE_CALL_CAMERA
+ )
+ )
+ .isEqualTo(Manifest.permission_group.CAMERA)
+ }
+
+ @Test
+ fun testGetPlatformPermissionGroupForOp_InvalidOpName() {
+ try {
+ assertThat(PermissionMapping.getPlatformPermissionGroupForOp("invalid_opName"))
+ .isEqualTo(null)
+ } catch (e: IllegalArgumentException) {
+ // ignore wtf may throw in some configuration.
+ }
+ }
+
+ @Test
+ fun testGetPlatformPermissionGroupForOp_readContacts() {
+ assertThat(
+ PermissionMapping.getPlatformPermissionGroupForOp(AppOpsManager.OPSTR_READ_CONTACTS)
+ )
+ .isEqualTo(
+ PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.READ_CONTACTS)
+ )
+ }
+
+ @Test
+ fun testHealthPermissionIsRuntime_healthPermissionUiEnabled_isRuntime() {
+ assumeTrue(Utils.isHealthPermissionUiEnabled())
+
+ assertThat(PermissionMapping.isRuntimePlatformPermission(
+ HealthPermissions.READ_HEART_RATE)).isTrue()
+ }
+
+ @Test
+ fun testHealthPermissionGroupIsPlatform_healthPermissionUiEnabled_isPlatform() {
+ assumeTrue(Utils.isHealthPermissionUiEnabled())
+
+ assertThat(PermissionMapping.isPlatformPermissionGroup(
+ HealthPermissions.HEALTH_PERMISSION_GROUP)).isTrue()
+ }
+
+ @Test
+ fun testGetGroupForHealthPermission_healthPermissionUiEnabled_isHealthPermissionGroup() {
+ assumeTrue(Utils.isHealthPermissionUiEnabled())
+
+ assertThat(PermissionMapping.getGroupOfPlatformPermission(
+ HealthPermissions.READ_HEART_RATE)).isEqualTo(
+ HealthPermissions.HEALTH_PERMISSION_GROUP)
+ }
+
+ @Test
+ fun testGetPermNameForHealthPermissionGroup_healthPermissionUiEnabled_isHealthPermission() {
+ assumeTrue(Utils.isHealthPermissionUiEnabled())
+
+ assertThat(PermissionMapping.getPlatformPermissionNamesOfGroup(
+ HealthPermissions.HEALTH_PERMISSION_GROUP)).contains(
+ HealthPermissions.READ_HEART_RATE)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ @RequiresFlagsEnabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun getGroupOfPlatformPermission_replaceBodySensorFlagEnabled_notHaveSensorsGroup() {
+ assertNull(PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.BODY_SENSORS))
+ assertNull(
+ PermissionMapping.getGroupOfPlatformPermission(
+ Manifest.permission.BODY_SENSORS_BACKGROUND
+ )
+ )
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun getGroupOfPlatformPermission_replaceBodySensorFlagDisabled_haveSensorsGroup() {
+ assertNotNull(
+ PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.BODY_SENSORS)
+ )
+ assertNotNull(
+ PermissionMapping.getGroupOfPlatformPermission(
+ Manifest.permission.BODY_SENSORS_BACKGROUND
+ )
+ )
+ }
+
+
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ )
+ @Test
+ fun getGroupOfPlatformPermission_preV_haveSensorsGroup() {
+ assertNotNull(
+ PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.BODY_SENSORS)
+ )
+ assertNotNull(
+ PermissionMapping.getGroupOfPlatformPermission(
+ Manifest.permission.BODY_SENSORS_BACKGROUND
+ )
+ )
+ }
+}
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/UtilsTest.kt
index 11bcca356..8cc6b952c 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/UtilsTest.kt
@@ -14,8 +14,10 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.util
+package com.android.permissioncontroller.permission.utils
+import android.Manifest.permission.BODY_SENSORS
+import android.Manifest.permission.BODY_SENSORS_BACKGROUND
import android.Manifest.permission.READ_CONTACTS
import android.Manifest.permission_group.ACTIVITY_RECOGNITION
import android.Manifest.permission_group.CALENDAR
@@ -38,20 +40,35 @@ import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Resources
+import android.os.Build
+import android.permission.flags.Flags
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
import com.android.permissioncontroller.Constants.INVALID_SESSION_ID
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.privacysources.WorkPolicyInfo
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Ignore
+import org.junit.Rule
import org.junit.Test
class UtilsTest {
+
+ @JvmField @Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
private val context = InstrumentationRegistry.getInstrumentation().targetContext as Context
+ @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule()
+
@Test
fun getAbsoluteTimeString_zero_returnsNull() {
assertThat(Utils.getAbsoluteTimeString(context, 0)).isNull()
@@ -96,6 +113,7 @@ class UtilsTest {
fun getBlockedTitle_invalidGroupName_returnsMinusOne() {
assertThat(Utils.getBlockedTitle(INVALID_GROUP_NAME)).isEqualTo(-1)
}
+
@Test
fun getBlockedTitle_validGroupName() {
assertThat(Utils.getBlockedTitle(CAMERA)).isEqualTo(R.string.blocked_camera_title)
@@ -295,6 +313,59 @@ class UtilsTest {
assertThat(permissionInfos[0].name).isEqualTo(READ_CONTACTS)
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ @RequiresFlagsEnabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun getInstalledRuntimePermissionInfosForGroup_bodySensorFlagEnabled_bodySensorPermissionsNotIncluded() {
+ val permissionNamesInUndefinedGroup =
+ Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, UNDEFINED)
+ .map { it.name }
+ val permissionNamesInSensorsGroup =
+ Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, SENSORS)
+ .map { it.name }
+
+ assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS))
+ assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS_BACKGROUND))
+ assertFalse(permissionNamesInSensorsGroup.contains(BODY_SENSORS))
+ assertFalse(permissionNamesInSensorsGroup.contains(BODY_SENSORS_BACKGROUND))
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun getInstalledRuntimePermissionInfosForGroup_bodySensorFlagDisabled_bodySensorPermissionsIncluded() {
+ val permissionNamesInUndefinedGroup =
+ Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, UNDEFINED)
+ .map { it.name }
+ val permissionNamesInSensorsGroup =
+ Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, SENSORS)
+ .map { it.name }
+
+ assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS))
+ assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS_BACKGROUND))
+ assertTrue(permissionNamesInSensorsGroup.contains(BODY_SENSORS))
+ assertTrue(permissionNamesInSensorsGroup.contains(BODY_SENSORS_BACKGROUND))
+ }
+
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ )
+ @Test
+ fun getInstalledRuntimePermissionInfosForGroup_preV_bodySensorPermissionsIncluded() {
+ val permissionNamesInUndefinedGroup =
+ Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, UNDEFINED)
+ .map { it.name }
+ val permissionNamesInSensorsGroup =
+ Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, SENSORS)
+ .map { it.name }
+
+ assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS))
+ assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS_BACKGROUND))
+ assertTrue(permissionNamesInSensorsGroup.contains(BODY_SENSORS))
+ assertTrue(permissionNamesInSensorsGroup.contains(BODY_SENSORS_BACKGROUND))
+ }
+
@Test
fun getColorResId_validId_returnsNonZero() {
assertThat(Utils.getColorResId(context, android.R.attr.colorPrimary))
diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtilsTest.kt
new file mode 100644
index 000000000..b1ac6095d
--- /dev/null
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtilsTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.utils.v31
+
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.health.connect.HealthConnectManager
+import android.health.connect.HealthPermissions
+import android.os.Build
+import android.permission.flags.Flags
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@RunWith(AndroidJUnit4::class)
+class AdminRestrictedPermissionsUtilsTest {
+
+ @JvmField @Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+ private val dpm: DevicePolicyManager = mock(DevicePolicyManager::class.java)
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ @RequiresFlagsEnabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun mayAdminGrantPermission_healthPermissions_restricted() {
+ val permissions: Set<String> = HealthConnectManager.getHealthPermissions(context)
+ for (permission in permissions) {
+ val canGrant =
+ AdminRestrictedPermissionsUtils.mayAdminGrantPermission(
+ permission,
+ HealthPermissions.HEALTH_PERMISSION_GROUP,
+ /* canAdminGrantSensorsPermissions= */ false,
+ /* isManagedProfile= */ false,
+ dpm,
+ )
+ assertEquals(false, canGrant)
+ }
+ }
+}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationPolicyTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationPolicyTest.kt
index 99aa4baa9..15a37532f 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationPolicyTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationPolicyTest.kt
@@ -16,19 +16,27 @@
package com.android.permissioncontroller.tests.mocking.hibernation
+import android.Manifest
import android.app.job.JobScheduler
+import android.content.ComponentName
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.database.ContentObserver
import android.net.Uri
+import android.os.Binder
import android.os.Build
import android.os.SystemClock
+import android.os.UserHandle
import android.os.UserManager
import android.preference.PreferenceManager
import android.provider.DeviceConfig
import android.provider.Settings
+import android.telecom.PhoneAccountHandle
+import android.telecom.TelecomManager
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
@@ -37,14 +45,19 @@ import com.android.permissioncontroller.Constants
import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.hibernation.HibernationBroadcastReceiver
import com.android.permissioncontroller.hibernation.ONE_DAY_MS
-import com.android.permissioncontroller.hibernation.PREF_KEY_SYSTEM_TIME_SNAPSHOT
import com.android.permissioncontroller.hibernation.PREF_KEY_ELAPSED_REALTIME_SNAPSHOT
import com.android.permissioncontroller.hibernation.PREF_KEY_START_TIME_OF_UNUSED_APP_TRACKING
+import com.android.permissioncontroller.hibernation.PREF_KEY_SYSTEM_TIME_SNAPSHOT
import com.android.permissioncontroller.hibernation.SNAPSHOT_UNINITIALIZED
import com.android.permissioncontroller.hibernation.getStartTimeOfUnusedAppTracking
+import com.android.permissioncontroller.hibernation.isPackageHibernationExemptBySystem
+import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
+import com.android.permissioncontroller.permission.utils.ContextCompat
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
@@ -71,11 +84,14 @@ class HibernationPolicyTest {
private val application = Mockito.mock(PermissionControllerApplication::class.java)
private const val USER_SETUP_INCOMPLETE = 0
private const val USER_SETUP_COMPLETE = 1
+ private const val TEST_PKG_NAME = "test.package"
}
@Mock lateinit var jobScheduler: JobScheduler
@Mock lateinit var context: Context
@Mock lateinit var userManager: UserManager
+ @Mock lateinit var packageManager: PackageManager
+ @Mock lateinit var telecomManager: TelecomManager
@Mock lateinit var contentResolver: ContentResolver
private lateinit var realContext: Context
@@ -83,6 +99,7 @@ class HibernationPolicyTest {
private lateinit var sharedPreferences: SharedPreferences
private lateinit var mockitoSession: MockitoSession
private lateinit var filesDir: File
+ private lateinit var userHandle: UserHandle
@Before
fun setup() {
@@ -100,11 +117,15 @@ class HibernationPolicyTest {
`when`(Settings.Secure.getUriFor(any())).thenReturn(Mockito.mock(Uri::class.java))
realContext = ApplicationProvider.getApplicationContext()
+ userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid())
sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(realContext.applicationContext)
`when`(context.getSharedPreferences(anyString(), anyInt())).thenReturn(sharedPreferences)
`when`(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
+ `when`(application.getSystemService(TelecomManager::class.java)).thenReturn(telecomManager)
+ `when`(application.packageManager).thenReturn(packageManager)
+ `when`(application.applicationContext).thenReturn(context)
filesDir = realContext.cacheDir
`when`(application.filesDir).thenReturn(filesDir)
@@ -207,6 +228,30 @@ class HibernationPolicyTest {
.isNotEqualTo(systemTimeSnapshot)
}
+ @Test
+ @Ignore("b/371061181")
+ // This method under test initializes several SmartAsyncMediatorLiveData classes which run code
+ // on GlobalScope which the unit test has no control over. This can lead to the code running
+ // during other tests which may not have the right static mocks.
+ // Until this is fixed, this test should be ignored to prevent flaky test faliures.
+ fun isPackageExemptBySystem_isCallingApp_returnsTrue() = runBlocking<Unit> {
+ val pkgInfo = makePackageInfo(TEST_PKG_NAME)
+
+ `when`(context.checkPermission(
+ eq(Manifest.permission.MANAGE_OWN_CALLS), anyInt(), eq(pkgInfo.uid)))
+ .thenReturn(PERMISSION_GRANTED)
+ `when`(context.checkPermission(
+ eq(Manifest.permission.RECORD_AUDIO), anyInt(), eq(pkgInfo.uid)))
+ .thenReturn(PERMISSION_GRANTED)
+ `when`(context.checkPermission(
+ eq(Manifest.permission.WRITE_CALL_LOG), anyInt(), eq(pkgInfo.uid)))
+ .thenReturn(PERMISSION_GRANTED)
+ `when`(telecomManager.selfManagedPhoneAccounts).thenReturn(
+ listOf(PhoneAccountHandle(ComponentName(TEST_PKG_NAME, "Service"), "id")))
+
+ assertThat(isPackageHibernationExemptBySystem(pkgInfo, userHandle)).isTrue()
+ }
+
private fun assertAdjustedTime(systemTimeSnapshot: Long, realtimeSnapshot: Long) {
val newStartTimeOfUnusedAppTracking =
sharedPreferences.getLong(
@@ -223,4 +268,23 @@ class HibernationPolicyTest {
assertThat(newRealtimeSnapshot).isNotEqualTo(SNAPSHOT_UNINITIALIZED)
assertThat(newRealtimeSnapshot).isAtLeast(realtimeSnapshot)
}
+
+ private fun makePackageInfo(packageName: String): LightPackageInfo {
+ return LightPackageInfo(
+ packageName,
+ emptyList(),
+ emptyList(),
+ emptyList(),
+ 0 /* uid */,
+ Build.VERSION_CODES.CUR_DEVELOPMENT,
+ false /* isInstantApp */,
+ true /* enabled */,
+ 0 /* appFlags */,
+ 0 /* firstInstallTime */,
+ 0 /* lastUpdateTime */,
+ false /* areAttributionsUserVisible */,
+ emptyMap() /* attributionTagsToLabels */,
+ ContextCompat.DEVICE_ID_DEFAULT
+ )
+ }
}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/domain/usecase/GetPermissionGroupUsageDetailsUseCaseTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/domain/usecase/GetPermissionGroupUsageDetailsUseCaseTest.kt
index da2d05f63..cb36de2b1 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/domain/usecase/GetPermissionGroupUsageDetailsUseCaseTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/domain/usecase/GetPermissionGroupUsageDetailsUseCaseTest.kt
@@ -34,6 +34,7 @@ import com.android.permissioncontroller.permission.domain.model.v31.PermissionTi
import com.android.permissioncontroller.permission.domain.model.v31.PermissionTimelineUsageModelWrapper
import com.android.permissioncontroller.permission.domain.usecase.v31.GetPermissionGroupUsageDetailsUseCase
import com.android.permissioncontroller.permission.domain.usecase.v31.TELECOM_PACKAGE
+import com.android.permissioncontroller.permission.utils.LocationUtils
import com.android.permissioncontroller.pm.data.model.v31.PackageAttributionModel
import com.android.permissioncontroller.pm.data.model.v31.PackageInfoModel
import com.android.permissioncontroller.pm.data.repository.v31.PackageRepository
@@ -56,9 +57,11 @@ import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Assume
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
@@ -72,7 +75,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
private var mockitoSession: MockitoSession? = null
private lateinit var packageInfos: MutableMap<String, PackageInfoModel>
- private val currentUser = android.os.Process.myUserHandle()
+ private val currentUser = UserHandle.of(0)
private val privateProfile = UserHandle.of(10)
private val guestUser = UserHandle.of(20)
@@ -88,11 +91,13 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
mockitoSession =
ExtendedMockito.mockitoSession()
.mockStatic(PermissionControllerApplication::class.java)
+ .mockStatic(LocationUtils::class.java)
.strictness(Strictness.LENIENT)
.startMocking()
whenever(PermissionControllerApplication.get()).thenReturn(application)
whenever(application.applicationContext).thenReturn(context)
+ whenever(LocationUtils.isLocationProvider(Mockito.any(), Mockito.any())).thenReturn(false)
packageInfos =
mapOf(
@@ -102,7 +107,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
systemPackageName to
getPackageInfoModel(
systemPackageName,
- applicationFlags = ApplicationInfo.FLAG_SYSTEM
+ applicationFlags = ApplicationInfo.FLAG_SYSTEM,
),
)
.toMutableMap()
@@ -124,7 +129,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
emit(
listOf(
DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
- DiscretePackageOpsModel(guestUserPkgName, guestUser.identifier, appOpEvents)
+ DiscretePackageOpsModel(guestUserPkgName, guestUser.identifier, appOpEvents),
)
)
}
@@ -150,11 +155,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
emit(
listOf(
DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
- DiscretePackageOpsModel(
- testPackageName,
- privateProfile.identifier,
- appOpEvents
- ),
+ DiscretePackageOpsModel(testPackageName, privateProfile.identifier, appOpEvents),
)
)
}
@@ -168,8 +169,8 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
currentUserProfiles =
listOf(currentUser.identifier, privateProfile.identifier),
quietUserProfiles = listOf(privateProfile.identifier),
- showInQuiteModeProfiles = listOf(privateProfile.identifier)
- )
+ showInQuiteModeProfiles = listOf(privateProfile.identifier),
+ ),
)
val permissionTimelineUsages = getResult(underTest, this)
@@ -190,11 +191,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
emit(
listOf(
DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
- DiscretePackageOpsModel(
- testPackageName,
- privateProfile.identifier,
- appOpEvents
- ),
+ DiscretePackageOpsModel(testPackageName, privateProfile.identifier, appOpEvents),
)
)
}
@@ -208,7 +205,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
currentUserProfiles =
listOf(currentUser.identifier, privateProfile.identifier),
quietUserProfiles = listOf(privateProfile.identifier),
- )
+ ),
)
val permissionTimelineUsages = getResult(underTest, this)
@@ -254,7 +251,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -280,7 +277,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -303,23 +300,23 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(1),
- MINUTES.toMillis(1)
+ MINUTES.toMillis(1),
),
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(2),
- MINUTES.toMillis(1)
+ MINUTES.toMillis(1),
),
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(3),
- MINUTES.toMillis(2)
+ MINUTES.toMillis(2),
),
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -344,23 +341,19 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
DiscreteOpModel(
AppOpsManager.OPSTR_FINE_LOCATION,
hours3 + MINUTES.toMillis(59),
- -1
+ -1,
),
DiscreteOpModel(
AppOpsManager.OPSTR_FINE_LOCATION,
hours4 + MINUTES.toMillis(0),
- -1
- ),
- DiscreteOpModel(
- AppOpsManager.OPSTR_FINE_LOCATION,
- hours4 + MINUTES.toMillis(1),
- -1
+ -1,
),
+ DiscreteOpModel(AppOpsManager.OPSTR_FINE_LOCATION, hours4 + MINUTES.toMillis(1), -1),
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -383,18 +376,18 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(1),
- MINUTES.toMillis(3)
+ MINUTES.toMillis(3),
),
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(3),
- MINUTES.toMillis(2)
+ MINUTES.toMillis(2),
),
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -420,7 +413,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -448,18 +441,18 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(1),
- MINUTES.toMillis(3)
+ MINUTES.toMillis(3),
),
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(5),
- MINUTES.toMillis(5)
+ MINUTES.toMillis(5),
),
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -483,13 +476,11 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
@Test
fun singleDiscreteAccess() = runTest {
val appOpEvents =
- listOf(
- DiscreteOpModel(AppOpsManager.OPSTR_FINE_LOCATION, MINUTES.toMillis(1), -1),
- )
+ listOf(DiscreteOpModel(AppOpsManager.OPSTR_FINE_LOCATION, MINUTES.toMillis(1), -1))
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -512,13 +503,13 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(1),
- MINUTES.toMillis(3)
- ),
+ MINUTES.toMillis(3),
+ )
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -541,29 +532,29 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(1),
- MINUTES.toMillis(2)
+ MINUTES.toMillis(2),
),
// This entry says the camera was accessed for 15 minutes starting at minute 3
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(3),
- MINUTES.toMillis(15)
+ MINUTES.toMillis(15),
),
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(4),
- MINUTES.toMillis(1)
+ MINUTES.toMillis(1),
),
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
MINUTES.toMillis(6),
- MINUTES.toMillis(1)
+ MINUTES.toMillis(1),
),
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -582,9 +573,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
@Test
fun verifyUserSensitiveFlags() = runTest {
val appOpEvents =
- listOf(
- DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1),
- )
+ listOf(DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1))
val discretePackageOps = flow {
emit(
listOf(
@@ -620,13 +609,11 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
@Test
fun verifyNotUserSensitiveFlagsForSystemPackage() = runTest {
val appOpEvents =
- listOf(
- DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1),
- )
+ listOf(DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1))
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(systemPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(systemPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -651,14 +638,12 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
@Test
fun verifyCameraUserSensitiveFlagsForTelecomPackage() = runTest {
val appOpEvents =
- listOf(
- DiscreteOpModel(AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(1), -1),
- )
+ listOf(DiscreteOpModel(AppOpsManager.OPSTR_CAMERA, MINUTES.toMillis(1), -1))
packageInfos[TELECOM_PACKAGE] = getPackageInfoModel(TELECOM_PACKAGE)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(TELECOM_PACKAGE, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(TELECOM_PACKAGE, currentUser.identifier, appOpEvents)
)
)
}
@@ -683,14 +668,12 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
@Test
fun verifyLocationUserSensitiveFlagsForTelecomPackage() = runTest {
val appOpEvents =
- listOf(
- DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1),
- )
+ listOf(DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, MINUTES.toMillis(1), -1))
packageInfos[TELECOM_PACKAGE] = getPackageInfoModel(TELECOM_PACKAGE)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(TELECOM_PACKAGE, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(TELECOM_PACKAGE, currentUser.identifier, appOpEvents)
)
)
}
@@ -714,7 +697,75 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
}
@Test
- fun verifyAttributionTagsAreGroupedAndClustered() = runTest {
+ @RequiresFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_PERMISSION_TIMELINE_ATTRIBUTION_LABEL_FIX
+ )
+ @Ignore("b/365004787")
+ fun verifyAccessIsNotGroupedByAttributionLabelAndClustered() = runTest {
+ // The package is not a location provider.
+ val appOpEvents =
+ listOf(
+ // These entries should be clustered even though they have
+ // different attribution labels.
+ DiscreteOpModel(
+ AppOpsManager.OPSTR_COARSE_LOCATION,
+ MINUTES.toMillis(1),
+ -1,
+ "tag1",
+ ),
+ DiscreteOpModel(
+ AppOpsManager.OPSTR_COARSE_LOCATION,
+ MINUTES.toMillis(2),
+ -1,
+ "tag1",
+ ),
+ DiscreteOpModel(
+ AppOpsManager.OPSTR_COARSE_LOCATION,
+ MINUTES.toMillis(3),
+ -1,
+ "tag3",
+ ),
+ DiscreteOpModel(
+ AppOpsManager.OPSTR_COARSE_LOCATION,
+ MINUTES.toMillis(4),
+ -1,
+ "tag2",
+ ),
+ )
+ val discretePackageOps = flow {
+ emit(
+ listOf(
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
+ )
+ )
+ }
+ val packageAttributions = mutableMapOf<String, PackageAttributionModel>()
+ // tag1 and tag3 refers to same attribution label.
+ val attributionTagToLabelRes = mapOf("tag1" to 100, "tag2" to 200, "tag3" to 100)
+ val attributionsMap = mapOf(100 to "Tag1 Label", 200 to "Tag2 Label")
+ packageAttributions[testPackageName] =
+ PackageAttributionModel(
+ testPackageName,
+ true,
+ attributionTagToLabelRes,
+ attributionsMap,
+ )
+
+ val underTest =
+ getPermissionGroupUsageDetailsUseCase(
+ LOCATION_PERMISSION_GROUP,
+ discretePackageOps,
+ packageRepository = FakePackageRepository(packageInfos, packageAttributions),
+ attributionLabelFix = true,
+ )
+ val permissionTimelineUsages = getResult(underTest, this)
+
+ Truth.assertThat(permissionTimelineUsages.size).isEqualTo(1)
+ }
+
+ @Test
+ fun verifyAccessIsGroupedByAttributionLabelAndClustered() = runTest {
+ whenever(LocationUtils.isLocationProvider(Mockito.any(), Mockito.any())).thenReturn(true)
val appOpEvents =
listOf(
// These 3 entries should be grouped and clustered.
@@ -722,38 +773,38 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
AppOpsManager.OPSTR_COARSE_LOCATION,
MINUTES.toMillis(1),
-1,
- "tag1"
+ "tag1",
),
DiscreteOpModel(
AppOpsManager.OPSTR_COARSE_LOCATION,
MINUTES.toMillis(2),
-1,
- "tag1"
+ "tag1",
),
DiscreteOpModel(
AppOpsManager.OPSTR_COARSE_LOCATION,
MINUTES.toMillis(3),
-1,
- "tag3"
+ "tag3",
),
// The access at minute 4 should not be grouped or clustered.
DiscreteOpModel(
AppOpsManager.OPSTR_COARSE_LOCATION,
MINUTES.toMillis(4),
-1,
- "tag2"
+ "tag2",
),
DiscreteOpModel(
AppOpsManager.OPSTR_COARSE_LOCATION,
MINUTES.toMillis(8),
-1,
- "tag2"
+ "tag2",
),
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -766,14 +817,14 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
testPackageName,
true,
attributionTagToLabelRes,
- attributionsMap
+ attributionsMap,
)
val underTest =
getPermissionGroupUsageDetailsUseCase(
LOCATION_PERMISSION_GROUP,
discretePackageOps,
- packageRepository = FakePackageRepository(packageInfos, packageAttributions)
+ packageRepository = FakePackageRepository(packageInfos, packageAttributions),
)
val permissionTimelineUsages = getResult(underTest, this)
@@ -794,6 +845,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
}
@Test
+ @Ignore("b/365004787")
@RequiresFlagsEnabled(Flags.FLAG_LOCATION_BYPASS_PRIVACY_DASHBOARD_ENABLED)
fun emergencyAccessesAreNotClusteredWithRegularAccesses() = runTest {
Assume.assumeTrue(SdkLevel.isAtLeastV())
@@ -805,7 +857,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -829,7 +881,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -843,7 +895,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
private fun TestScope.getResult(
useCase: GetPermissionGroupUsageDetailsUseCase,
- coroutineScope: CoroutineScope
+ coroutineScope: CoroutineScope,
): List<PermissionTimelineUsageModel> {
val usages by collectLastValue(useCase(coroutineScope))
return (usages as PermissionTimelineUsageModelWrapper.Success).timelineUsageModels
@@ -854,7 +906,8 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
discreteUsageFlow: Flow<List<DiscretePackageOpsModel>>,
permissionFlags: Map<String, Int> = emptyMap(),
userRepository: UserRepository = FakeUserRepository(listOf(currentUser.identifier)),
- packageRepository: PackageRepository = FakePackageRepository(packageInfos)
+ packageRepository: PackageRepository = FakePackageRepository(packageInfos),
+ attributionLabelFix: Boolean = false,
): GetPermissionGroupUsageDetailsUseCase {
val permissionRepository = FakePermissionRepository(permissionFlags)
val appOpUsageRepository = FakeAppOpRepository(emptyFlow(), discreteUsageFlow)
@@ -865,7 +918,8 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
permissionRepository,
appOpUsageRepository,
roleRepository,
- userRepository
+ userRepository,
+ attributionLabelFix,
)
}
@@ -877,7 +931,7 @@ class GetPermissionGroupUsageDetailsUseCaseTest {
listOf(
PackageInfo.REQUESTED_PERMISSION_GRANTED,
PackageInfo.REQUESTED_PERMISSION_GRANTED,
- PackageInfo.REQUESTED_PERMISSION_GRANTED
+ PackageInfo.REQUESTED_PERMISSION_GRANTED,
),
applicationFlags: Int = 0,
) = PackageInfoModel(packageName, requestedPermissions, permissionsFlags, applicationFlags)
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/PermissionUsageDetailsViewModelTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/PermissionUsageDetailsViewModelTest.kt
index 31e81e588..4334026c3 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/PermissionUsageDetailsViewModelTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/PermissionUsageDetailsViewModelTest.kt
@@ -30,8 +30,9 @@ import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.appops.data.model.v31.DiscretePackageOpsModel
import com.android.permissioncontroller.appops.data.model.v31.DiscretePackageOpsModel.DiscreteOpModel
import com.android.permissioncontroller.permission.domain.usecase.v31.GetPermissionGroupUsageDetailsUseCase
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelV2
+import com.android.permissioncontroller.permission.utils.LocationUtils
import com.android.permissioncontroller.permission.utils.StringUtils
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.pm.data.model.v31.PackageInfoModel
@@ -66,7 +67,7 @@ import org.mockito.quality.Strictness
/**
* These unit tests are for new permission timeline implementation, the new view model class is
- * [PermissionUsageDetailsViewModelV2]
+ * [PermissionUsageDetailsViewModel]
*/
@RunWith(AndroidJUnit4::class)
class PermissionUsageDetailsViewModelTest {
@@ -93,6 +94,7 @@ class PermissionUsageDetailsViewModelTest {
.mockStatic(DeviceUtils::class.java)
.mockStatic(StringUtils::class.java)
.mockStatic(Flags::class.java)
+ .mockStatic(LocationUtils::class.java)
.strictness(Strictness.LENIENT)
.startMocking()
@@ -105,10 +107,11 @@ class PermissionUsageDetailsViewModelTest {
any(),
anyInt(),
anyInt(),
- any(Array<String>::class.java)
+ any(Array<String>::class.java),
)
)
.thenReturn("Duration Summary")
+ whenever(LocationUtils.isLocationProvider(any(), any())).thenReturn(false)
packageInfos =
mapOf(
@@ -116,7 +119,7 @@ class PermissionUsageDetailsViewModelTest {
systemPackageName to
getPackageInfoModel(
systemPackageName,
- applicationFlags = ApplicationInfo.FLAG_SYSTEM
+ applicationFlags = ApplicationInfo.FLAG_SYSTEM,
),
)
.toMutableMap()
@@ -124,7 +127,7 @@ class PermissionUsageDetailsViewModelTest {
packageRepository =
FakePackageRepository(
packageInfos,
- packagesAndLabels = mapOf(testPackageName to testPackageLabel)
+ packagesAndLabels = mapOf(testPackageName to testPackageLabel),
)
}
@@ -137,9 +140,7 @@ class PermissionUsageDetailsViewModelTest {
fun verifyOnlyNonSystemAppsAreShown() = runTest {
val accessTimeMillis = (getCurrentTime() - TimeUnit.HOURS.toMillis(5))
val appOpEvents =
- listOf(
- DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, accessTimeMillis, -1),
- )
+ listOf(DiscreteOpModel(AppOpsManager.OPSTR_COARSE_LOCATION, accessTimeMillis, -1))
val discretePackageOps = flow {
emit(
listOf(
@@ -173,8 +174,8 @@ class PermissionUsageDetailsViewModelTest {
DiscreteOpModel(
AppOpsManager.OPSTR_COARSE_LOCATION,
accessTimeMillis,
- TimeUnit.MINUTES.toMillis(1)
- ),
+ TimeUnit.MINUTES.toMillis(1),
+ )
)
val discretePackageOps = flow {
emit(
@@ -189,7 +190,7 @@ class PermissionUsageDetailsViewModelTest {
getViewModel(
LOCATION_PERMISSION_GROUP,
discretePackageOps,
- savedStateMap = mapOf("show7Days" to false, "showSystem" to true)
+ savedStateMap = mapOf("show7Days" to false, "showSystem" to true),
)
val uiState = getPermissionUsageDetailsUiState(underTest)
@@ -207,13 +208,13 @@ class PermissionUsageDetailsViewModelTest {
DiscreteOpModel(
AppOpsManager.OPSTR_COARSE_LOCATION,
accessTimeMillis,
- TimeUnit.MINUTES.toMillis(1)
- ),
+ TimeUnit.MINUTES.toMillis(1),
+ )
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -222,7 +223,7 @@ class PermissionUsageDetailsViewModelTest {
getViewModel(
LOCATION_PERMISSION_GROUP,
discretePackageOps,
- savedStateMap = mapOf("show7Days" to false, "showSystem" to false)
+ savedStateMap = mapOf("show7Days" to false, "showSystem" to false),
)
val uiState = getPermissionUsageDetailsUiState(underTest)
@@ -237,13 +238,13 @@ class PermissionUsageDetailsViewModelTest {
DiscreteOpModel(
AppOpsManager.OPSTR_COARSE_LOCATION,
accessTimeMillis,
- TimeUnit.MINUTES.toMillis(1)
- ),
+ TimeUnit.MINUTES.toMillis(1),
+ )
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(systemPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(systemPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -252,7 +253,7 @@ class PermissionUsageDetailsViewModelTest {
getViewModel(
LOCATION_PERMISSION_GROUP,
discretePackageOps,
- savedStateMap = mapOf("show7Days" to false, "showSystem" to false)
+ savedStateMap = mapOf("show7Days" to false, "showSystem" to false),
)
val uiState = getPermissionUsageDetailsUiState(underTest)
@@ -268,18 +269,18 @@ class PermissionUsageDetailsViewModelTest {
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
accessStartWithIn24Hours,
- TimeUnit.MINUTES.toMillis(5)
+ TimeUnit.MINUTES.toMillis(5),
),
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
accessStartBefore24Hours,
- TimeUnit.MINUTES.toMillis(7)
+ TimeUnit.MINUTES.toMillis(7),
),
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -301,18 +302,18 @@ class PermissionUsageDetailsViewModelTest {
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
accessTimeWithIn24Hours,
- TimeUnit.MINUTES.toMillis(5)
+ TimeUnit.MINUTES.toMillis(5),
),
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
accessTimeBefore24Hours,
- TimeUnit.MINUTES.toMillis(7)
+ TimeUnit.MINUTES.toMillis(7),
),
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -341,13 +342,13 @@ class PermissionUsageDetailsViewModelTest {
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
accessTimeMillis,
- TimeUnit.MINUTES.toMillis(5)
- ),
+ TimeUnit.MINUTES.toMillis(5),
+ )
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -368,13 +369,13 @@ class PermissionUsageDetailsViewModelTest {
DiscreteOpModel(
AppOpsManager.OPSTR_CAMERA,
accessTimeMillis,
- TimeUnit.MINUTES.toMillis(1)
- ),
+ TimeUnit.MINUTES.toMillis(1),
+ )
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
@@ -394,22 +395,16 @@ class PermissionUsageDetailsViewModelTest {
whenever(application.getString(anyInt())).thenReturn("emergency attr label")
val accessTimeMillis = (getCurrentTime() - TimeUnit.HOURS.toMillis(5))
val appOpEvents =
- listOf(
- DiscreteOpModel(AppOpsManager.OPSTR_EMERGENCY_LOCATION, accessTimeMillis, -1),
- )
+ listOf(DiscreteOpModel(AppOpsManager.OPSTR_EMERGENCY_LOCATION, accessTimeMillis, -1))
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
- val underTest =
- getViewModel(
- LOCATION_PERMISSION_GROUP,
- discretePackageOps,
- )
+ val underTest = getViewModel(LOCATION_PERMISSION_GROUP, discretePackageOps)
val uiState = getPermissionUsageDetailsUiState(underTest)
assertThat(uiState.appPermissionAccessUiInfoList.size).isEqualTo(1)
val timelineRow = uiState.appPermissionAccessUiInfoList.first()
@@ -426,21 +421,15 @@ class PermissionUsageDetailsViewModelTest {
DiscreteOpModel(
AppOpsManager.OPSTR_COARSE_LOCATION,
accessTimeMillis,
- TimeUnit.MINUTES.toMillis(1)
- ),
+ TimeUnit.MINUTES.toMillis(1),
+ )
)
val actualData =
- listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
- )
+ listOf(DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents))
val discretePackageOps = MutableStateFlow(emptyList<DiscretePackageOpsModel>())
discretePackageOps.emit(emptyList())
- val underTest =
- getViewModel(
- LOCATION_PERMISSION_GROUP,
- discretePackageOps,
- )
+ val underTest = getViewModel(LOCATION_PERMISSION_GROUP, discretePackageOps)
val result by collectLastValue(underTest.permissionUsageDetailsUiStateFlow)
var uiState = result as PermissionUsageDetailsUiState.Success
assertThat(uiState.appPermissionAccessUiInfoList).isEmpty()
@@ -460,22 +449,18 @@ class PermissionUsageDetailsViewModelTest {
DiscreteOpModel(
AppOpsManager.OPSTR_COARSE_LOCATION,
accessTimeMillis,
- TimeUnit.MINUTES.toMillis(1)
- ),
+ TimeUnit.MINUTES.toMillis(1),
+ )
)
val discretePackageOps = flow {
emit(
listOf(
- DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents),
+ DiscretePackageOpsModel(testPackageName, currentUser.identifier, appOpEvents)
)
)
}
- val underTest =
- getViewModel(
- LOCATION_PERMISSION_GROUP,
- discretePackageOps,
- )
+ val underTest = getViewModel(LOCATION_PERMISSION_GROUP, discretePackageOps)
val uiState = getPermissionUsageDetailsUiState(underTest)
assertThat(uiState.show7Days).isFalse()
@@ -500,7 +485,8 @@ class PermissionUsageDetailsViewModelTest {
permissionRepository,
appOpUsageRepository,
roleRepository,
- userRepository
+ userRepository,
+ false,
)
}
@@ -513,20 +499,20 @@ class PermissionUsageDetailsViewModelTest {
permissionGroup: String,
discretePackageOps: Flow<List<DiscretePackageOpsModel>>,
savedStateMap: Map<String, Boolean> = mapOf("show7Days" to false, "showSystem" to false),
- pkgRepository: PackageRepository = packageRepository
+ pkgRepository: PackageRepository = packageRepository,
) =
- PermissionUsageDetailsViewModelV2(
+ PermissionUsageDetailsViewModel(
application,
getPermissionGroupUsageDetailsUseCase(permissionGroup, discretePackageOps),
SavedStateHandle(savedStateMap),
permissionGroup,
scope = backgroundScope,
StandardTestDispatcher(testScheduler),
- packageRepository = pkgRepository
+ packageRepository = pkgRepository,
)
private fun TestScope.getPermissionUsageDetailsUiState(
- viewModel: PermissionUsageDetailsViewModelV2
+ viewModel: PermissionUsageDetailsViewModel
): PermissionUsageDetailsUiState.Success {
val result by collectLastValue(viewModel.permissionUsageDetailsUiStateFlow)
return result as PermissionUsageDetailsUiState.Success
@@ -538,7 +524,7 @@ class PermissionUsageDetailsViewModelTest {
permissionsFlags: List<Int> =
listOf(
PackageInfo.REQUESTED_PERMISSION_GRANTED,
- PackageInfo.REQUESTED_PERMISSION_GRANTED
+ PackageInfo.REQUESTED_PERMISSION_GRANTED,
),
applicationFlags: Int = 0,
) = PackageInfoModel(packageName, requestedPermissions, permissionsFlags, applicationFlags)
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/AdminRestrictedPermissionsUtilsTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/AdminRestrictedPermissionsUtilsTest.kt
index 0c864da4a..35543b6f1 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/AdminRestrictedPermissionsUtilsTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/AdminRestrictedPermissionsUtilsTest.kt
@@ -17,75 +17,126 @@
package com.android.permissioncontroller.tests.mocking.permission.utils
import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.health.connect.HealthPermissions
+import android.os.Build
+import android.permission.flags.Flags
import android.platform.test.annotations.AsbSecurityTest
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils
import org.junit.Assert.assertEquals
import org.junit.Assume
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
+import org.junit.experimental.runners.Enclosed
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mockito.mock
-@RunWith(Parameterized::class)
-class AdminRestrictedPermissionsUtilsTest(
- private val permission: String,
- private val group: String?,
- private val canAdminGrantSensorsPermissions: Boolean,
- private val expected: Boolean
-) {
+@RunWith(Enclosed::class)
+object AdminRestrictedPermissionsUtilsTest {
+
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
private val dpm: DevicePolicyManager = mock(DevicePolicyManager::class.java)
- @Before
- fun setup() {
- Assume.assumeTrue(SdkLevel.isAtLeastS())
- }
+ @RunWith(Parameterized::class)
+ class AdminRestrictedPermissionsUtilsParameterizedTest(
+ private val permission: String,
+ private val group: String?,
+ private val canAdminGrantSensorsPermissions: Boolean,
+ private val expected: Boolean,
+ ) {
- @AsbSecurityTest(cveBugId = [308138085])
- @Test
- fun mayAdminGrantPermissionTest() {
- val canGrant =
- AdminRestrictedPermissionsUtils.mayAdminGrantPermission(
- permission,
- group,
- canAdminGrantSensorsPermissions,
- false,
- dpm
- )
- assertEquals(expected, canGrant)
- }
+ @Before
+ fun setup() {
+ Assume.assumeTrue(SdkLevel.isAtLeastS())
+ }
- companion object {
- /**
- * Returns a list of arrays containing the following values:
- *
- * 0. Permission name (String)
- * 1. Permission group name (String)
- * 2. Can admin grant sensors permissions (Boolean)
- * 3. Expected return from mayAdminGrantPermission method (Boolean)
- */
- @JvmStatic
- @Parameterized.Parameters(name = "{index}: validate({0}, {1}, {3}) = {4}")
- fun getParameters(): List<Array<out Any?>> {
- return listOf(
- arrayOf("abc", "xyz", false, true),
- arrayOf("abc", null, false, true),
- arrayOf("android.permission.RECORD_AUDIO", "xyz", false, false),
- arrayOf("abc", "android.permission-group.MICROPHONE", false, false),
- arrayOf(
- "android.permission.RECORD_AUDIO",
- "android.permission-group.MICROPHONE",
+ @AsbSecurityTest(cveBugId = [308138085])
+ @Test
+ fun mayAdminGrantPermissionTest() {
+ val canGrant =
+ AdminRestrictedPermissionsUtils.mayAdminGrantPermission(
+ permission,
+ group,
+ canAdminGrantSensorsPermissions,
false,
- false
- ),
- arrayOf(
- "android.permission.RECORD_AUDIO",
- "android.permission-group.MICROPHONE",
- true,
- true
- ),
+ dpm,
+ )
+ assertEquals(expected, canGrant)
+ }
+
+ companion object {
+ /**
+ * Returns a list of arrays containing the following values:
+ * 0. Permission name (String)
+ * 1. Permission group name (String)
+ * 2. Can admin grant sensors permissions (Boolean)
+ * 3. Expected return from mayAdminGrantPermission method (Boolean)
+ */
+ @JvmStatic
+ @Parameterized.Parameters(name = "{index}: validate({0}, {1}, {3}) = {4}")
+ fun getParameters(): List<Array<out Any?>> {
+ return listOf(
+ arrayOf("abc", "xyz", false, true),
+ arrayOf("abc", null, false, true),
+ arrayOf("android.permission.RECORD_AUDIO", "xyz", false, false),
+ arrayOf("abc", "android.permission-group.MICROPHONE", false, false),
+ arrayOf(
+ "android.permission.RECORD_AUDIO",
+ "android.permission-group.MICROPHONE",
+ false,
+ false,
+ ),
+ arrayOf(
+ "android.permission.RECORD_AUDIO",
+ "android.permission-group.MICROPHONE",
+ true,
+ true,
+ ),
+ )
+ }
+ }
+ }
+
+ @RunWith(AndroidJUnit4::class)
+ class AdminRestrictedPermissionsUtilsSingleTest {
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ @RequiresFlagsEnabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ fun addAdminRestrictedPermission_addsPermissionToRestrictedList() {
+ var canGrant =
+ AdminRestrictedPermissionsUtils.mayAdminGrantPermission(
+ HealthPermissions.READ_HEART_RATE,
+ HealthPermissions.HEALTH_PERMISSION_GROUP,
+ /* canAdminGrantSensorsPermissions= */ false,
+ /* isManagedProfile= */ false,
+ dpm,
+ )
+ assertEquals(true, canGrant)
+
+ AdminRestrictedPermissionsUtils.addAdminRestrictedPermission(
+ HealthPermissions.READ_HEART_RATE
)
+
+ canGrant =
+ AdminRestrictedPermissionsUtils.mayAdminGrantPermission(
+ HealthPermissions.READ_HEART_RATE,
+ HealthPermissions.HEALTH_PERMISSION_GROUP,
+ /* canAdminGrantSensorsPermissions= */ false,
+ /* isManagedProfile= */ false,
+ dpm,
+ )
+ assertEquals(false, canGrant)
}
}
}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt
index 28f69b136..593061a83 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt
@@ -241,7 +241,7 @@ class GrantRevokeTests {
perms: Map<String, LightPermission> = emptyMap()
): LightAppPermGroup {
val pGi = LightPermGroupInfo(PERM_GROUP_NAME, TEST_PACKAGE_NAME, 0, 0, 0, false)
- return LightAppPermGroup(pkgInfo, pGi, perms, false, false)
+ return LightAppPermGroup(pkgInfo, pGi, perms, false, false, false)
}
/**
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/WorkPolicyInfoTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/WorkPolicyInfoTest.kt
index bbbf0e86a..38baee3ed 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/WorkPolicyInfoTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/WorkPolicyInfoTest.kt
@@ -21,6 +21,7 @@ import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.os.Build
+import android.os.Process
import android.os.UserManager
import android.safetycenter.SafetyCenterManager
import android.safetycenter.SafetyEvent
@@ -118,6 +119,8 @@ class WorkPolicyInfoTest {
whenever(PermissionControllerApplication.get()).thenReturn(application)
whenever(application.applicationContext).thenReturn(application)
workPolicyInfo = WorkPolicyInfo(mockWorkPolicyUtils)
+ val userId = Process.myUserHandle().identifier
+ whenever(mockWorkPolicyUtils.managedProfileUserId).thenReturn(userId)
}
@After
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt
index ec6ab4eff..da1430c0a 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt
@@ -16,9 +16,17 @@
package com.android.permissioncontroller.tests.mocking.role.model
+import android.app.AppOpsManager
+import android.content.pm.PackageManager
+import android.content.pm.PermissionInfo
+import android.os.Process
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.permissioncontroller.role.model.RoleParserInitializer
+import com.android.role.controller.model.AppOp
+import com.android.role.controller.model.Permission
+import com.android.role.controller.model.PermissionSet
+import com.android.role.controller.model.Role
import com.android.role.controller.model.RoleParser
import org.junit.BeforeClass
import org.junit.Test
@@ -37,9 +45,10 @@ class RoleParserTest {
private val instrumentation = InstrumentationRegistry.getInstrumentation()
private val uiAutomation = instrumentation.uiAutomation
private val targetContext = instrumentation.targetContext
+ private val packageManager = targetContext.packageManager
@Test
- fun testParseRolesWithValidation() {
+ fun parseRoles() {
// We may need to call privileged APIs to determine availability of things.
uiAutomation.adoptShellPermissionIdentity()
try {
@@ -48,4 +57,129 @@ class RoleParserTest {
uiAutomation.dropShellPermissionIdentity()
}
}
+
+ @Test
+ fun validateRoles() {
+ // We may need to call privileged APIs to determine availability of things.
+ uiAutomation.adoptShellPermissionIdentity()
+ try {
+ val xml = RoleParser(targetContext).parseRolesXml()
+ requireNotNull(xml)
+ validateRoles(xml.first, xml.second)
+ } finally {
+ uiAutomation.dropShellPermissionIdentity()
+ }
+ }
+
+ fun validateRoles(permissionSets: Map<String, PermissionSet>, roles: Map<String, Role>) {
+ for (permissionSet in permissionSets.values) {
+ for (permission in permissionSet.permissions) {
+ validatePermission(permission)
+ }
+ }
+
+ for (role in roles.values) {
+ if (!role.isAvailableByFeatureFlagAndSdkVersion) {
+ continue
+ }
+
+ for (requiredComponent in role.requiredComponents) {
+ val permission = requiredComponent.permission
+ if (permission != null) {
+ validatePermission(permission)
+ }
+ }
+
+ for (permission in role.permissions) {
+ validatePermission(permission)
+ }
+
+ for (appOp in role.appOps) {
+ validateAppOp(appOp)
+ }
+
+ for (appOpPermission in role.appOpPermissions) {
+ validateAppOpPermission(appOpPermission)
+ }
+
+ for (preferredActivity in role.preferredActivities) {
+ require(preferredActivity.activity in role.requiredComponents) {
+ "<activity> of <preferred-activity> not required in <required-components>," +
+ " role: ${role.name}, preferred activity: $preferredActivity"
+ }
+ }
+ }
+ }
+
+ private fun validatePermission(permission: Permission) {
+ if (!permission.isAvailableAsUser(Process.myUserHandle(), targetContext)) {
+ return
+ }
+ validatePermission(permission.name, true)
+ }
+
+ private fun validatePermission(permissionName: String) {
+ validatePermission(permissionName, false)
+ }
+
+ private fun validatePermission(permissionName: String, enforceIsRuntimeOrRole: Boolean) {
+ val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ // Skip validation for car permissions which may not be available on all build targets.
+ if (!isAutomotive && permissionName.startsWith("android.car")) {
+ return
+ }
+
+ val permissionInfo =
+ try {
+ packageManager.getPermissionInfo(permissionName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ throw IllegalArgumentException("Unknown permission: $permissionName", e)
+ }
+
+ if (enforceIsRuntimeOrRole) {
+ require(
+ permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS ||
+ permissionInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_ROLE ==
+ PermissionInfo.PROTECTION_FLAG_ROLE
+ ) {
+ "Permission is not a runtime or role permission: $permissionName"
+ }
+ }
+ }
+
+ private fun validateAppOpPermission(appOpPermission: Permission) {
+ if (!appOpPermission.isAvailableAsUser(Process.myUserHandle(), targetContext)) {
+ return
+ }
+ validateAppOpPermission(appOpPermission.name)
+ }
+
+ private fun validateAppOpPermission(permissionName: String) {
+ val permissionInfo =
+ try {
+ packageManager.getPermissionInfo(permissionName, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ throw IllegalArgumentException("Unknown app op permission: $permissionName", e)
+ }
+ require(
+ permissionInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_APPOP ==
+ PermissionInfo.PROTECTION_FLAG_APPOP
+ ) {
+ "Permission is not an app op permission: $permissionName"
+ }
+ }
+
+ private fun validateAppOp(appOp: AppOp) {
+ if (!appOp.isAvailableByFeatureFlagAndSdkVersion) {
+ return
+ }
+ // This throws IllegalArgumentException if app op is unknown.
+ val permissionName = AppOpsManager.opToPermission(appOp.name)
+ if (permissionName != null) {
+ val permissionInfo = packageManager.getPermissionInfo(permissionName, 0)
+ require(permissionInfo.protection != PermissionInfo.PROTECTION_DANGEROUS) {
+ "App op has an associated runtime permission: ${appOp.name}"
+ }
+ }
+ }
}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/SafetyCenterUiDataTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/SafetyCenterUiDataTest.kt
index ca0392716..e53fabc90 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/SafetyCenterUiDataTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/SafetyCenterUiDataTest.kt
@@ -18,6 +18,7 @@ package com.android.permissioncontroller.tests.mocking.safetycenter.ui.model
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.Bundle
+import android.os.UserHandle
import android.safetycenter.SafetyCenterData
import android.safetycenter.SafetyCenterEntryGroup
import android.safetycenter.SafetyCenterEntryOrGroup
@@ -28,8 +29,14 @@ import android.safetycenter.SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATIO
import android.safetycenter.SafetyCenterStatus
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
+import com.android.permissioncontroller.safetycenter.ui.model.ActionId
+import com.android.permissioncontroller.safetycenter.ui.model.IssueId
+import com.android.permissioncontroller.safetycenter.ui.model.IssueUiData
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
import com.android.safetycenter.internaldata.SafetyCenterBundles.ISSUES_TO_GROUPS_BUNDLE_KEY
+import com.android.safetycenter.internaldata.SafetyCenterIds
+import com.android.safetycenter.internaldata.SafetyCenterIssueId
+import com.android.safetycenter.internaldata.SafetyCenterIssueKey
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -40,42 +47,42 @@ class SafetyCenterUiDataTest {
@Test
fun getMatchingGroup_validMatchingGroup_returnsExpectedEntryGroup() {
- val matchingGroup = createSafetyCenterEntryGroup(MATCHING_GROUP_ID)
- val nonMatchingGroup = createSafetyCenterEntryGroup(NON_MATCHING_GROUP_ID)
+ val matchingGroup = entryGroup(MATCHING_GROUP_ID)
+ val nonMatchingGroup = entryGroup(NON_MATCHING_GROUP_ID)
val safetyCenterData =
createSafetyCenterData(entryGroups = listOf(matchingGroup, nonMatchingGroup))
- val result = SafetyCenterUiData(safetyCenterData).getMatchingGroup(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingGroup(MATCHING_GROUP_ID)
assertThat(result).isEqualTo(matchingGroup)
}
@Test
fun getMatchingGroup_noMatchingGroup_returnsNull() {
- val nonMatchingGroup = createSafetyCenterEntryGroup(NON_MATCHING_GROUP_ID)
+ val nonMatchingGroup = entryGroup(NON_MATCHING_GROUP_ID)
val safetyCenterData = createSafetyCenterData(entryGroups = listOf(nonMatchingGroup))
- val result = SafetyCenterUiData(safetyCenterData).getMatchingGroup(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingGroup(MATCHING_GROUP_ID)
assertThat(result).isNull()
}
@Test
fun getMatchingIssues_defaultMatchingIssue_noExtras_returnsListOfIssues() {
- val defaultMatchingIssue = createSafetyCenterIssue("id1", MATCHING_GROUP_ID)
- val nonMatchingIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID)
+ val defaultMatchingIssue = issue("id1", MATCHING_GROUP_ID)
+ val nonMatchingIssue = issue("id2", NON_MATCHING_GROUP_ID)
val safetyCenterData =
createSafetyCenterData(issues = listOf(defaultMatchingIssue, nonMatchingIssue))
- val result = SafetyCenterUiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID)
assertThat(result).containsExactly(defaultMatchingIssue)
}
@Test
fun getMatchingIssues_defaultMatchingIssue_unrelatedExtras_returnsListOfIssues() {
- val defaultMatchingIssue = createSafetyCenterIssue("id1", MATCHING_GROUP_ID)
- val nonMatchingIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID)
+ val defaultMatchingIssue = issue("id1", MATCHING_GROUP_ID)
+ val nonMatchingIssue = issue("id2", NON_MATCHING_GROUP_ID)
val safetyCenterData =
createSafetyCenterData(
issues = listOf(defaultMatchingIssue, nonMatchingIssue),
@@ -84,21 +91,21 @@ class SafetyCenterUiDataTest {
Bundle().apply {
putStringArrayList(
nonMatchingIssue.id,
- arrayListOf(NON_MATCHING_GROUP_ID)
+ arrayListOf(NON_MATCHING_GROUP_ID),
)
}
- )
+ ),
)
- val result = SafetyCenterUiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID)
assertThat(result).containsExactly(defaultMatchingIssue)
}
@Test
fun getMatchingIssues_mappingMatchingIssue_returnsListOfIssues() {
- val mappingMatchingIssue = createSafetyCenterIssue("id1", NON_MATCHING_GROUP_ID)
- val nonMatchingIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID)
+ val mappingMatchingIssue = issue("id1", NON_MATCHING_GROUP_ID)
+ val nonMatchingIssue = issue("id2", NON_MATCHING_GROUP_ID)
val safetyCenterData =
createSafetyCenterData(
issues = listOf(mappingMatchingIssue, nonMatchingIssue),
@@ -107,51 +114,50 @@ class SafetyCenterUiDataTest {
Bundle().apply {
putStringArrayList(
mappingMatchingIssue.id,
- arrayListOf(MATCHING_GROUP_ID)
+ arrayListOf(MATCHING_GROUP_ID),
)
}
- )
+ ),
)
- val result = SafetyCenterUiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID)
assertThat(result).containsExactly(mappingMatchingIssue)
}
@Test
fun getMatchingIssues_noDefaultMatchingIssue_returnsEmptyList() {
- val nonMatchingIssue = createSafetyCenterIssue("id1", NON_MATCHING_GROUP_ID)
- val dismissedIssue = createSafetyCenterIssue("id2", MATCHING_GROUP_ID)
+ val nonMatchingIssue = issue("id1", NON_MATCHING_GROUP_ID)
+ val dismissedIssue = issue("id2", MATCHING_GROUP_ID)
val safetyCenterData =
createSafetyCenterData(
issues = listOf(nonMatchingIssue),
- dismissedIssues = listOf(dismissedIssue)
+ dismissedIssues = listOf(dismissedIssue),
)
- val result = SafetyCenterUiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingIssues(MATCHING_GROUP_ID)
assertThat(result).isEmpty()
}
@Test
fun getMatchingDismissedIssues_defaultMatchingDismissedIssue_returnsListOfDismissedIssues() {
- val defaultMatchingDismissedIssue = createSafetyCenterIssue("id1", MATCHING_GROUP_ID)
- val nonMatchingDismissedIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID)
+ val defaultMatchingDismissedIssue = issue("id1", MATCHING_GROUP_ID)
+ val nonMatchingDismissedIssue = issue("id2", NON_MATCHING_GROUP_ID)
val safetyCenterData =
createSafetyCenterData(
dismissedIssues = listOf(defaultMatchingDismissedIssue, nonMatchingDismissedIssue)
)
- val result =
- SafetyCenterUiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID)
assertThat(result).containsExactly(defaultMatchingDismissedIssue)
}
@Test
fun getMatchingDismissedIssues_defaultMatchingDismissedIssue2_returnsListOfDismissedIssues() {
- val defaultMatchingDismissedIssue = createSafetyCenterIssue("id1", MATCHING_GROUP_ID)
- val nonMatchingDismissedIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID)
+ val defaultMatchingDismissedIssue = issue("id1", MATCHING_GROUP_ID)
+ val nonMatchingDismissedIssue = issue("id2", NON_MATCHING_GROUP_ID)
val safetyCenterData =
createSafetyCenterData(
dismissedIssues = listOf(defaultMatchingDismissedIssue, nonMatchingDismissedIssue),
@@ -160,22 +166,21 @@ class SafetyCenterUiDataTest {
Bundle().apply {
putStringArrayList(
nonMatchingDismissedIssue.id,
- arrayListOf(NON_MATCHING_GROUP_ID)
+ arrayListOf(NON_MATCHING_GROUP_ID),
)
}
- )
+ ),
)
- val result =
- SafetyCenterUiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID)
assertThat(result).containsExactly(defaultMatchingDismissedIssue)
}
@Test
fun getMatchingDismissedIssues_mappingMatchingDismissedIssue_returnsListOfDismissedIssues() {
- val mappingMatchingDismissedIssue = createSafetyCenterIssue("id1", NON_MATCHING_GROUP_ID)
- val nonMatchingDismissedIssue = createSafetyCenterIssue("id2", NON_MATCHING_GROUP_ID)
+ val mappingMatchingDismissedIssue = issue("id1", NON_MATCHING_GROUP_ID)
+ val nonMatchingDismissedIssue = issue("id2", NON_MATCHING_GROUP_ID)
val safetyCenterData =
createSafetyCenterData(
dismissedIssues = listOf(mappingMatchingDismissedIssue, nonMatchingDismissedIssue),
@@ -184,30 +189,28 @@ class SafetyCenterUiDataTest {
Bundle().apply {
putStringArrayList(
mappingMatchingDismissedIssue.id,
- arrayListOf(MATCHING_GROUP_ID)
+ arrayListOf(MATCHING_GROUP_ID),
)
}
- )
+ ),
)
- val result =
- SafetyCenterUiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID)
assertThat(result).containsExactly(mappingMatchingDismissedIssue)
}
@Test
fun getMatchingDismissedIssues_noDefaultMatchingDismissedIssue_returnsEmptyList() {
- val nonMatchingDismissedIssue = createSafetyCenterIssue("id1", NON_MATCHING_GROUP_ID)
- val nonDismissedIssue = createSafetyCenterIssue("id2", MATCHING_GROUP_ID)
+ val nonMatchingDismissedIssue = issue("id1", NON_MATCHING_GROUP_ID)
+ val nonDismissedIssue = issue("id2", MATCHING_GROUP_ID)
val safetyCenterData =
createSafetyCenterData(
issues = listOf(nonDismissedIssue),
- dismissedIssues = listOf(nonMatchingDismissedIssue)
+ dismissedIssues = listOf(nonMatchingDismissedIssue),
)
- val result =
- SafetyCenterUiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID)
assertThat(result).isEmpty()
}
@@ -215,24 +218,12 @@ class SafetyCenterUiDataTest {
@Test
fun getMatchingDismissedIssues_doesntReturnGreenIssues() {
val greenDismissedIssue =
- createSafetyCenterIssue(
- "id1",
- MATCHING_GROUP_ID,
- severityLevel = ISSUE_SEVERITY_LEVEL_OK
- )
+ issue("id1", MATCHING_GROUP_ID, severityLevel = ISSUE_SEVERITY_LEVEL_OK)
val yellowDismissedIssue =
- createSafetyCenterIssue(
- "id2",
- MATCHING_GROUP_ID,
- severityLevel = ISSUE_SEVERITY_LEVEL_RECOMMENDATION
- )
+ issue("id2", MATCHING_GROUP_ID, severityLevel = ISSUE_SEVERITY_LEVEL_RECOMMENDATION)
val redDismissedIssue =
- createSafetyCenterIssue(
- "id3",
- MATCHING_GROUP_ID,
- severityLevel = ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING
- )
- val nonMatchingDismissedIssue = createSafetyCenterIssue("id4", NON_MATCHING_GROUP_ID)
+ issue("id3", MATCHING_GROUP_ID, severityLevel = ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
+ val nonMatchingDismissedIssue = issue("id4", NON_MATCHING_GROUP_ID)
val safetyCenterData =
createSafetyCenterData(
dismissedIssues =
@@ -240,25 +231,82 @@ class SafetyCenterUiDataTest {
redDismissedIssue,
yellowDismissedIssue,
greenDismissedIssue,
- nonMatchingDismissedIssue
- ),
+ nonMatchingDismissedIssue,
+ )
)
- val result =
- SafetyCenterUiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID)
+ val result = uiData(safetyCenterData).getMatchingDismissedIssues(MATCHING_GROUP_ID)
assertThat(result).containsExactly(redDismissedIssue, yellowDismissedIssue).inOrder()
}
+ @Test
+ fun issueUiDatas_returnsIssueUiData() {
+ val issue1 = issue("id1", "group1")
+ val issue2 = issue("id2", "group2")
+ val safetyCenterData = createSafetyCenterData(listOf(issue1, issue2))
+
+ val result = uiData(safetyCenterData).issueUiDatas
+
+ assertThat(result)
+ .containsExactly(
+ IssueUiData(issue1, isDismissed = false),
+ IssueUiData(issue2, isDismissed = false),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun issueUiDatas_withResolvedIssues_returnsExpectedIssueUiData() {
+ val resolvedActionId = "actionId"
+ val resolvedIssue = issue("resolvedId", "group1")
+ val unresolvedIssue = issue("unresolvedId", "group2")
+ val safetyCenterData = createSafetyCenterData(listOf(resolvedIssue, unresolvedIssue))
+
+ val result =
+ uiData(safetyCenterData, resolvedIssues = mapOf(resolvedIssue.id to resolvedActionId))
+ .issueUiDatas
+
+ assertThat(result[0].resolvedIssueActionId).isEqualTo(resolvedActionId)
+ assertThat(result[1].resolvedIssueActionId).isNull()
+ }
+
+ @Test
+ fun issueUiDatas_withSameTaskSourceId_returnsExpectedIssueUiData() {
+ val taskId = 42
+ val sameTaskSourceId = "sameTaskSourceId"
+ val sameTaskIssue =
+ issueWithEncodedId(
+ encodeIssueId("sameTaskIssue", sourceId = sameTaskSourceId),
+ "group1",
+ )
+ val differentTaskIssue = issue("differentTaskIssue", "group2")
+ val safetyCenterData = createSafetyCenterData(listOf(sameTaskIssue, differentTaskIssue))
+
+ val result =
+ uiData(safetyCenterData, taskId, sameTaskSourceIds = listOf(sameTaskSourceId))
+ .issueUiDatas
+
+ assertThat(result[0].launchTaskId).isEqualTo(taskId)
+ assertThat(result[1].launchTaskId).isNull()
+ }
+
private companion object {
const val MATCHING_GROUP_ID = "matching_group_id"
const val NON_MATCHING_GROUP_ID = "non_matching_group_id"
+ private fun uiData(
+ safetyCenterData: SafetyCenterData,
+ taskId: Int = 0,
+ sameTaskSourceIds: List<String> = emptyList(),
+ resolvedIssues: Map<IssueId, ActionId> = emptyMap(),
+ ) = SafetyCenterUiData(safetyCenterData, taskId, sameTaskSourceIds, resolvedIssues)
+
fun createSafetyCenterData(
issues: List<SafetyCenterIssue> = listOf(),
entryGroups: List<SafetyCenterEntryGroup> = listOf(),
dismissedIssues: List<SafetyCenterIssue> = listOf(),
- extras: Bundle = Bundle()
+ extras: Bundle = Bundle(),
): SafetyCenterData {
val safetyCenterStatus =
SafetyCenterStatus.Builder("status title", "status summary").build()
@@ -276,20 +324,44 @@ class SafetyCenterUiDataTest {
return builder.build()
}
- fun createSafetyCenterEntryGroup(groupId: String) =
+ fun createSafetyCenterExtras(issuesToGroupsMapping: Bundle) =
+ Bundle().apply { putBundle(ISSUES_TO_GROUPS_BUNDLE_KEY, issuesToGroupsMapping) }
+
+ fun entryGroup(groupId: String) =
SafetyCenterEntryGroup.Builder(groupId, "group title").build()
- fun createSafetyCenterIssue(
+ fun issue(
issueId: String,
groupId: String,
- severityLevel: Int = ISSUE_SEVERITY_LEVEL_RECOMMENDATION
+ severityLevel: Int = ISSUE_SEVERITY_LEVEL_RECOMMENDATION,
+ ): SafetyCenterIssue = issueWithEncodedId(encodeIssueId(issueId), groupId, severityLevel)
+
+ private fun issueWithEncodedId(
+ encodedIssueId: String,
+ groupId: String,
+ severityLevel: Int = ISSUE_SEVERITY_LEVEL_RECOMMENDATION,
) =
- SafetyCenterIssue.Builder(issueId, "issue title", "issue summary")
+ SafetyCenterIssue.Builder(encodedIssueId, "issue title", "issue summary")
.setSeverityLevel(severityLevel)
.setGroupId(groupId)
.build()
- fun createSafetyCenterExtras(issuesToGroupsMapping: Bundle) =
- Bundle().apply { putBundle(ISSUES_TO_GROUPS_BUNDLE_KEY, issuesToGroupsMapping) }
+ fun encodeIssueId(
+ sourceIssueId: String,
+ sourceId: String = "defaultSource",
+ issueTypeId: String = "defaultIssueTypeId",
+ ): String =
+ SafetyCenterIds.encodeToString(
+ SafetyCenterIssueId.newBuilder()
+ .setSafetyCenterIssueKey(
+ SafetyCenterIssueKey.newBuilder()
+ .setSafetySourceId(sourceId)
+ .setSafetySourceIssueId(sourceIssueId)
+ .setUserId(UserHandle.myUserId())
+ .build()
+ )
+ .setIssueTypeId(issueTypeId)
+ .build()
+ )
}
}
diff --git a/PermissionController/tests/permissionui/Android.bp b/PermissionController/tests/permissionui/Android.bp
index 6e1839d1d..8cc91bd99 100644
--- a/PermissionController/tests/permissionui/Android.bp
+++ b/PermissionController/tests/permissionui/Android.bp
@@ -47,7 +47,8 @@ android_test {
"androidx.test.ext.truth",
"androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
- "com.android.permission.flags-aconfig-java",
+ "android.permission.flags-aconfig-java-export",
+ "com.android.permission.flags-aconfig-java-export",
"compatibility-device-util-axt",
"flag-junit",
"permission-test-util-lib",
@@ -71,6 +72,8 @@ android_test {
":PermissionUiUseAdditionalPermissionApp",
":PermissionUiUseTwoAdditionalPermissionsApp",
":PermissionUiReadCalendarPermissionApp",
+ ":PermissionUiUseLegacyBodySensorsPermissionApp",
+ ":PermissionUiUseReadHeartRatePermissionApp",
],
per_testcase_directory: true,
}
diff --git a/PermissionController/tests/permissionui/AndroidTest.xml b/PermissionController/tests/permissionui/AndroidTest.xml
index a4aa03abe..9cadbd12f 100644
--- a/PermissionController/tests/permissionui/AndroidTest.xml
+++ b/PermissionController/tests/permissionui/AndroidTest.xml
@@ -57,6 +57,10 @@
value="/data/local/tmp/pc-permissionui/PermissionUiUseTwoAdditionalPermissionsApp.apk" />
<option name="push-file" key="PermissionUiReadCalendarPermissionApp.apk"
value="/data/local/tmp/pc-permissionui/PermissionUiReadCalendarPermissionApp.apk" />
+ <option name="push-file" key="PermissionUiUseLegacyBodySensorsPermissionApp.apk"
+ value="/data/local/tmp/pc-permissionui/PermissionUiUseLegacyBodySensorsPermissionApp.apk" />
+ <option name="push-file" key="PermissionUiUseReadHeartRatePermissionApp.apk"
+ value="/data/local/tmp/pc-permissionui/PermissionUiUseReadHeartRatePermissionApp.apk" />
</target_preparer>
<!-- Uninstall test-apps -->
diff --git a/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/Android.bp b/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/Android.bp
new file mode 100644
index 000000000..eb0e2d0fc
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_modules_Permission_PermissionController_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: [
+ "packages_modules_Permission_PermissionController_license",
+ ],
+}
+
+android_test_helper_app {
+ name: "PermissionUiUseLegacyBodySensorsPermissionApp",
+
+ srcs: ["src/**/*.kt"],
+
+ sdk_version: "34",
+}
diff --git a/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/AndroidManifest.xml b/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/AndroidManifest.xml
new file mode 100644
index 000000000..fac299b3e
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseLegacyBodySensorsPermissionApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.permissioncontroller.tests.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+ <uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
+
+ <application />
+</manifest>
diff --git a/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/Android.bp b/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/Android.bp
new file mode 100644
index 000000000..ccc9c9636
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_modules_Permission_PermissionController_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: [
+ "packages_modules_Permission_PermissionController_license",
+ ],
+}
+
+android_test_helper_app {
+ name: "PermissionUiUseReadHeartRatePermissionApp",
+
+ srcs: ["src/**/*.kt"],
+
+ sdk_version: "34",
+}
diff --git a/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/AndroidManifest.xml b/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/AndroidManifest.xml
new file mode 100644
index 000000000..278af1987
--- /dev/null
+++ b/PermissionController/tests/permissionui/PermissionUiUseReadHeartRatePermissionApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.permissioncontroller.tests.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.health.READ_HEART_RATE" />
+ <uses-permission android:name="android.permission.health.READ_HEALTH_DATA_IN_BACKGROUND" />
+
+ <application />
+</manifest>
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt
index 0f4f3841a..ecc7e161f 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt
@@ -90,26 +90,6 @@ class HealthConnectAllAppPermissionFragmentTest : BasePermissionUiTest() {
}
}
- @Test
- fun invalidGrantedUsedHealthConnectPermissionsAreListed() {
- installInvalidTestAppThatUsesHealthConnectPermission()
- grantTestAppPermission(HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED)
-
- startManageAppPermissionsActivity()
-
- // Ensure that Health Connect permission group permissions are present if a single one is
- // already granted, regardless of whether the intent filters are incorrectly or not setup
- // for the app
- eventually {
- waitFindObject(By.text(HEALTH_CONNECT_LABEL))
- waitFindObject(By.text(HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED_LABEL))
-
- // READ_STEPS is not granted, but should still be present due to READ_FLOORS_CLIMBED
- // being granted
- waitFindObject(By.text(HEALTH_CONNECT_PERMISSION_READ_STEPS_LABEL))
- }
- }
-
private fun startManageAppPermissionsActivity() {
uiDevice.performActionAndWait(
{
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt
index 04dbac39f..d4d4be6ec 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAppPermissionFragmentTest.kt
@@ -16,10 +16,16 @@
package com.android.permissioncontroller.permissionui.ui
+import android.content.Context
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.Build
+import android.permission.flags.Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
@@ -28,7 +34,9 @@ import com.android.compatibility.common.util.UiAutomatorUtils2.waitUntilObjectGo
import com.android.permissioncontroller.permissionui.wakeUpScreen
import org.junit.After
import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -41,10 +49,17 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
+
+ @Rule @JvmField val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private lateinit var context: Context
+
@Before fun assumeNotTelevision() = assumeFalse(isTelevision)
@Before
- fun wakeScreenUp() {
+ fun setUp() {
+ context = InstrumentationRegistry.getInstrumentation().context
+
wakeUpScreen()
}
@@ -52,8 +67,10 @@ class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
fun uninstallTestApp() {
uninstallTestApps()
}
+
@Test
- fun usedHealthConnectPermissionsAreListed() {
+ fun usedHealthConnectPermissionsAreListed_handHeldDevices() {
+ assumeFalse(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
installTestAppThatUsesHealthConnectPermission()
startManageAppPermissionsActivity()
@@ -62,7 +79,8 @@ class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
}
@Test
- fun invalidUngrantedUsedHealthConnectPermissionsAreNotListed() {
+ fun invalidUngrantedUsedHealthConnectPermissionsAreNotListed_handHeldDevices() {
+ assumeFalse(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
installInvalidTestAppThatUsesHealthConnectPermission()
startManageAppPermissionsActivity()
@@ -70,16 +88,54 @@ class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
waitUntilObjectGone(By.text(HEALTH_CONNECT_LABEL), TIMEOUT_SHORT)
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
@Test
- fun invalidGrantedUsedHealthConnectPermissionsAreListed() {
- installInvalidTestAppThatUsesHealthConnectPermission()
- grantTestAppPermission(HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED)
+ fun startManageAppPermissionsActivity_wearDevices_requestLegacyBodySensorsUngranted_fitnessAndWellnessShowsUp() {
+ assumeTrue(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ installTestAppThatUsesLegacyBodySensorsPermissions()
+
+ startManageAppPermissionsActivity()
+
+ eventually { waitFindObject(By.text(FITNESS_AND_WELLNESS_LABEL)) }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun startManageAppPermissionsActivity_wearDevices_requestReadHeartRateUngranted_fitnessAndWellnessShowsUp() {
+ assumeTrue(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ installTestAppThatUsesReadHeartRatePermissions()
+
+ startManageAppPermissionsActivity()
+
+ eventually { waitFindObject(By.text(FITNESS_AND_WELLNESS_LABEL)) }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun startManageAppPermissionsActivity_handHeldDevices_requestLegacyBodySensorsUngranted_healthConnectShowsUp() {
+ assumeFalse(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ installTestAppThatUsesLegacyBodySensorsPermissions()
startManageAppPermissionsActivity()
eventually { waitFindObject(By.text(HEALTH_CONNECT_LABEL)) }
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun startManageAppPermissionsActivity_handHeldDevices_requestReadHeartRateUngranted_healthConnectNotShowsUp() {
+ assumeFalse(context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
+ installTestAppThatUsesReadHeartRatePermissions()
+
+ startManageAppPermissionsActivity()
+
+ waitUntilObjectGone(By.text(HEALTH_CONNECT_LABEL), TIMEOUT_SHORT)
+ }
+
private fun startManageAppPermissionsActivity() {
runWithShellPermissionIdentity {
instrumentationContext.startActivity(
@@ -93,6 +149,7 @@ class HealthConnectAppPermissionFragmentTest : BasePermissionUiTest() {
}
companion object {
+ private const val FITNESS_AND_WELLNESS_LABEL = "Fitness and wellness"
// Health connect label uses a non breaking space
private const val HEALTH_CONNECT_LABEL = "Health\u00A0Connect"
private const val HEALTH_CONNECT_PERMISSION_READ_FLOORS_CLIMBED =
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt
index 7227ea3d8..8eef11e73 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/TestAppUtils.kt
@@ -36,6 +36,10 @@ private const val TWO_ADDITIONAL_PERM_USER_APK =
"$APK_DIRECTORY/PermissionUiUseTwoAdditionalPermissionsApp.apk"
private const val ADDITIONAL_PERM_DEFINER_APK =
"$APK_DIRECTORY/PermissionUiDefineAdditionalPermissionApp.apk"
+private const val LEGACY_BODY_SENSORS_APK =
+ "$APK_DIRECTORY/PermissionUiUseLegacyBodySensorsPermissionApp.apk"
+private const val READ_HEART_RATE_APK =
+ "$APK_DIRECTORY/PermissionUiUseReadHeartRatePermissionApp.apk"
// All 4 of the AppThatUses_X_Permission(s) applications share the same package name.
private const val PERM_DEFINER_PACKAGE =
@@ -69,6 +73,10 @@ fun installTestAppThatUsesTwoAdditionalPermissions() = install(TWO_ADDITIONAL_PE
fun installTestAppThatDefinesAdditionalPermissions() = install(ADDITIONAL_PERM_DEFINER_APK)
+fun installTestAppThatUsesLegacyBodySensorsPermissions() = install(LEGACY_BODY_SENSORS_APK)
+
+fun installTestAppThatUsesReadHeartRatePermissions() = install(READ_HEART_RATE_APK)
+
fun uninstallTestApps() {
uninstallApp(PERM_USER_PACKAGE)
uninstallApp(PERM_DEFINER_PACKAGE)
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageCustomPermissionsFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageCustomPermissionsFragmentTest.kt
index eb7be564b..2ad4d4925 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageCustomPermissionsFragmentTest.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageCustomPermissionsFragmentTest.kt
@@ -17,26 +17,40 @@
package com.android.permissioncontroller.permissionui.ui.handheld
import android.content.Intent
+import android.os.Build
+import android.permission.flags.Flags
import android.permission.cts.PermissionUtils.grantPermission
import android.permission.cts.PermissionUtils.install
import android.permission.cts.PermissionUtils.revokePermission
import android.permission.cts.PermissionUtils.uninstallApp
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
import androidx.test.uiautomator.By
import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObjectOrNull
import com.android.permissioncontroller.permissionui.getUsageCountsFromUi
import com.android.permissioncontroller.permissionui.wakeUpScreen
import com.google.common.truth.Truth.assertThat
import org.junit.After
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/** Simple tests for {@link ManageCustomPermissionsFragment} */
@RunWith(AndroidJUnit4::class)
class ManageCustomPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
+
+ @JvmField @Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
private val ONE_PERMISSION_DEFINER_APK =
"/data/local/tmp/pc-permissionui/" + "PermissionUiDefineAdditionalPermissionApp.apk"
private val PERMISSION_USER_APK =
@@ -47,6 +61,7 @@ class ManageCustomPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
private val PERM_LABEL = "Permission A"
private val PERM = "com.android.permissioncontroller.tests.A"
private val ADDITIONAL_PERMISSIONS_LABEL = "Additional permissions"
+ private val BODY_SENSORS_LABEL = "Body sensors"
@Before
fun setup() {
@@ -92,6 +107,40 @@ class ManageCustomPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
eventually { assertThat(getUsageCountsFromUi(PERM_LABEL)).isEqualTo(original) }
}
+
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ )
+ @Test
+ fun testFindBodySensor_preV_labelDisplayed() {
+ if (waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL)) == null) {
+ waitFindObject(By.textContains(ADDITIONAL_PERMISSIONS_LABEL)).click()
+ assertNotNull(waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL)))
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun testFindBodySensor_replaceBodySensorFlagDisabled_labelDisplayed() {
+ if (waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL)) == null) {
+ waitFindObject(By.textContains(ADDITIONAL_PERMISSIONS_LABEL)).click()
+ assertNotNull(waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL)))
+ }
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA)
+ @RequiresFlagsEnabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun testFindBodySensor_replaceBodySensorFlagEnabled_labelNotDisplayed() {
+ install(ONE_PERMISSION_DEFINER_APK)
+
+ assertNull(waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL)))
+ waitFindObject(By.textContains(ADDITIONAL_PERMISSIONS_LABEL)).click()
+ assertNull(waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL)))
+ }
+
@After
fun tearDown() {
uninstallApp(DEFINER_PKG)
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageStandardPermissionsFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageStandardPermissionsFragmentTest.kt
index 1ad876245..fcce09450 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageStandardPermissionsFragmentTest.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageStandardPermissionsFragmentTest.kt
@@ -23,6 +23,8 @@ import android.permission.cts.PermissionUtils.grantPermission
import android.permission.cts.PermissionUtils.install
import android.permission.cts.PermissionUtils.revokePermission
import android.permission.cts.PermissionUtils.uninstallApp
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.uiautomator.By
@@ -30,17 +32,23 @@ import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.getEventually
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObjectOrNull
+import com.android.permission.flags.Flags
import com.android.permissioncontroller.permissionui.getUsageCountsFromUi
import com.android.permissioncontroller.permissionui.wakeUpScreen
import com.google.common.truth.Truth.assertThat
import org.junit.After
+import org.junit.Assert.assertNull
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/** Simple tests for {@link ManageStandardPermissionsFragment} */
@RunWith(AndroidJUnit4::class)
class ManageStandardPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
+
+ @JvmField @Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
@Before
fun setup() {
wakeUpScreen()
@@ -117,7 +125,7 @@ class ManageStandardPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
assertThat(afterInstall.granted).isEqualTo(original.granted)
assertThat(afterInstall.total).isEqualTo(original.total + 1)
},
- TIMEOUT
+ TIMEOUT,
)
}
@@ -127,13 +135,13 @@ class ManageStandardPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
install(LOCATION_USER_APK)
eventually(
{ assertThat(getUsageCountsFromUi(LOCATION_GROUP_LABEL)).isNotEqualTo(original) },
- TIMEOUT
+ TIMEOUT,
)
uninstallApp(LOCATION_USER_PKG)
eventually(
{ assertThat(getUsageCountsFromUi(LOCATION_GROUP_LABEL)).isEqualTo(original) },
- TIMEOUT
+ TIMEOUT,
)
}
@@ -147,7 +155,7 @@ class ManageStandardPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
assertThat(getUsageCountsFromUi(LOCATION_GROUP_LABEL).total)
.isEqualTo(original.total + 1)
},
- TIMEOUT
+ TIMEOUT,
)
grantPermission(LOCATION_USER_PKG, ACCESS_COARSE_LOCATION)
@@ -170,7 +178,7 @@ class ManageStandardPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
assertThat(getUsageCountsFromUi(LOCATION_GROUP_LABEL).granted)
.isNotEqualTo(original.granted)
},
- TIMEOUT
+ TIMEOUT,
)
revokePermission(LOCATION_USER_PKG, ACCESS_COARSE_LOCATION)
@@ -190,7 +198,7 @@ class ManageStandardPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
{
assertThat(getAdditionalPermissionCount()).isEqualTo(additionalPermissionBefore + 1)
},
- TIMEOUT
+ TIMEOUT,
)
}
@@ -202,13 +210,13 @@ class ManageStandardPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
install(ADDITIONAL_USER_APK)
eventually(
{ assertThat(getAdditionalPermissionCount()).isNotEqualTo(additionalPermissionBefore) },
- TIMEOUT
+ TIMEOUT,
)
uninstallApp(ADDITIONAL_USER_PKG)
eventually(
{ assertThat(getAdditionalPermissionCount()).isEqualTo(additionalPermissionBefore) },
- TIMEOUT
+ TIMEOUT,
)
}
@@ -220,16 +228,23 @@ class ManageStandardPermissionsFragmentTest : BaseHandheldPermissionUiTest() {
install(ADDITIONAL_USER_APK)
eventually(
{ assertThat(getAdditionalPermissionCount()).isNotEqualTo(additionalPermissionBefore) },
- TIMEOUT
+ TIMEOUT,
)
uninstallApp(ADDITIONAL_DEFINER_PKG)
eventually(
{ assertThat(getAdditionalPermissionCount()).isEqualTo(additionalPermissionBefore) },
- TIMEOUT
+ TIMEOUT,
)
}
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DECLUTTERED_PERMISSION_MANAGER_ENABLED)
+ fun noUnusedPermissionGroupDisplayedInTheMainPage() {
+ eventually { assertNull(waitFindObjectOrNull(By.hasChild(By.textStartsWith("0 of 0")))) }
+ eventually { assertNull(waitFindObjectOrNull(By.hasChild(By.textStartsWith("0/0")))) }
+ }
+
companion object {
private val LOG_TAG = ManageStandardPermissionsFragmentTest::class.java.simpleName
diff --git a/PermissionController/wear-permission-components/Android.bp b/PermissionController/wear-permission-components/Android.bp
new file mode 100644
index 000000000..bdd783b60
--- /dev/null
+++ b/PermissionController/wear-permission-components/Android.bp
@@ -0,0 +1,67 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_team: "trendy_team_android_permissions",
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "wear-permission-components-sources",
+ srcs: [
+ "src/**/*.kt",
+ ],
+ visibility: ["//visibility:private"],
+}
+
+android_library {
+ name: "wear-permission-components",
+ manifest: "AndroidManifest.xml",
+ sdk_version: "system_current",
+ min_sdk_version: "30",
+ target_sdk_version: "33",
+ srcs: [
+ ":wear-permission-components-sources",
+ ],
+ resource_dirs: ["res"],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-annotations-lib",
+ ],
+ static_libs: [
+ "androidx.fragment_fragment",
+ "androidx.appcompat_appcompat",
+ "androidx.compose.foundation_foundation",
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.runtime_runtime-livedata",
+ "androidx.compose.ui_ui",
+ "androidx.wear.compose_compose-material",
+ "androidx.wear.compose_compose-material3",
+ ],
+ kotlin_lang_version: "1.9",
+ kotlincflags: [
+ "-Werror",
+ "-opt-in=kotlinx.coroutines.DelicateCoroutinesApi",
+ "-Xjvm-default=all",
+ ],
+ installable: false,
+ visibility: [
+ "//packages/modules:__subpackages__",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ "com.android.healthfitness",
+ ],
+}
diff --git a/PermissionController/wear-permission-components/AndroidManifest.xml b/PermissionController/wear-permission-components/AndroidManifest.xml
new file mode 100644
index 000000000..57eca6176
--- /dev/null
+++ b/PermissionController/wear-permission-components/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<manifest package="com.android.permissioncontroller.wear.permission.components">
+</manifest>
diff --git a/PermissionController/res/drawable/ic_security_update_good.xml b/PermissionController/wear-permission-components/res/drawable/ic_security_update_good.xml
index 59e7a67fa..59e7a67fa 100644
--- a/PermissionController/res/drawable/ic_security_update_good.xml
+++ b/PermissionController/wear-permission-components/res/drawable/ic_security_update_good.xml
diff --git a/PermissionController/wear-permission-components/res/values/donottranslate.xml b/PermissionController/wear-permission-components/res/values/donottranslate.xml
new file mode 100644
index 000000000..03038ddd1
--- /dev/null
+++ b/PermissionController/wear-permission-components/res/values/donottranslate.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- font-family-device-default is expected to be preloaded in the font_customization.xml(/vendor/<OEM>/products/<PRODUCT>/fonts/fonts_customization.xml)-->
+ <!-- Falls back to system default when font-family-device-default doesn't exist -->
+ <string name="wear_material_compose_display_1_font_family">font-family-device-default</string>
+ <string name="wear_material_compose_display_2_font_family">font-family-device-default</string>
+ <string name="wear_material_compose_display_3_font_family">font-family-device-default</string>
+ <string name="wear_material_compose_title_1_font_family">font-family-medium-device-default</string>
+ <string name="wear_material_compose_title_2_font_family">font-family-medium-device-default</string>
+ <string name="wear_material_compose_title_3_font_family">font-family-device-default</string>
+ <string name="wear_material_compose_body_1_font_family">font-family-text-device-default</string>
+ <string name="wear_material_compose_body_2_font_family">font-family-text-device-default</string>
+ <string name="wear_material_compose_button_font_family">font-family-text-medium-device-default</string>
+ <string name="wear_material_compose_caption_1_font_family">font-family-text-medium-device-default</string>
+ <string name="wear_material_compose_caption_2_font_family">font-family-text-medium-device-default</string>
+ <string name="wear_material_compose_caption_3_font_family">font-family-text-medium-device-default</string>
+
+ <string name="wear_compose_material3_arc_small_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_arc_medium_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_arc_large_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_body_extra_small_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_body_small_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_body_medium_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_body_large_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_display_small_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_display_medium_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_display_large_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_label_small_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_label_medium_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_label_large_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_numeral_extra_small_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_numeral_small_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_numeral_medium_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_numeral_large_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_numeral_extra_large_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_title_small_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_title_medium_font_family">font-family-flex-device-default</string>
+ <string name="wear_compose_material3_title_large_font_family">font-family-flex-device-default</string>
+
+ <dimen name="wear_compose_material3_arc_small_font_size">14sp</dimen>
+ <dimen name="wear_compose_material3_arc_medium_font_size">15sp</dimen>
+ <dimen name="wear_compose_material3_arc_large_font_size">20sp</dimen>
+ <dimen name="wear_compose_material3_body_extra_small_font_size">10sp</dimen>
+ <dimen name="wear_compose_material3_body_small_font_size">12sp</dimen>
+ <dimen name="wear_compose_material3_body_medium_font_size">14sp</dimen>
+ <dimen name="wear_compose_material3_body_large_font_size">16sp</dimen>
+ <dimen name="wear_compose_material3_display_small_font_size">24sp</dimen>
+ <dimen name="wear_compose_material3_display_medium_font_size">30sp</dimen>
+ <dimen name="wear_compose_material3_display_large_font_size">40sp</dimen>
+ <dimen name="wear_compose_material3_label_small_font_size">13sp</dimen>
+ <dimen name="wear_compose_material3_label_medium_font_size">15sp</dimen>
+ <dimen name="wear_compose_material3_label_large_font_size">20sp</dimen>
+ <dimen name="wear_compose_material3_numeral_extra_small_font_size">24sp</dimen>
+ <dimen name="wear_compose_material3_numeral_small_font_size">30sp</dimen>
+ <dimen name="wear_compose_material3_numeral_medium_font_size">40sp</dimen>
+ <dimen name="wear_compose_material3_numeral_large_font_size">50sp</dimen>
+ <dimen name="wear_compose_material3_numeral_extra_large_font_size">60sp</dimen>
+ <dimen name="wear_compose_material3_title_small_font_size">14sp</dimen>
+ <dimen name="wear_compose_material3_title_medium_font_size">16sp</dimen>
+ <dimen name="wear_compose_material3_title_large_font_size">18sp</dimen>
+
+ <dimen name="wear_compose_material3_shape_corner_extra_small_size">4dp</dimen>
+ <dimen name="wear_compose_material3_shape_corner_small_size">8dp</dimen>
+ <dimen name="wear_compose_material3_shape_corner_medium_size">18dp</dimen>
+ <dimen name="wear_compose_material3_shape_corner_large_size">26dp</dimen>
+ <dimen name="wear_compose_material3_shape_corner_extra_large_size">36dp</dimen>
+
+</resources>
diff --git a/PermissionController/wear-permission-components/res/values/overlayable.xml b/PermissionController/wear-permission-components/res/values/overlayable.xml
new file mode 100644
index 000000000..94c5972f4
--- /dev/null
+++ b/PermissionController/wear-permission-components/res/values/overlayable.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <overlayable name="WearPermissionControllerStyles">
+ <policy type="product|system|vendor|odm|oem">
+ <!--START WEAR SPECIFIC MATERIAL3 FONT FACE TOKENS-->
+ <item type="string" name="wear_compose_material3_arc_small_font_family" />
+ <item type="string" name="wear_compose_material3_arc_medium_font_family" />
+ <item type="string" name="wear_compose_material3_arc_large_font_family" />
+
+ <item type="string" name="wear_compose_material3_body_extra_small_font_family" />
+ <item type="string" name="wear_compose_material3_body_small_font_family" />
+ <item type="string" name="wear_compose_material3_body_medium_font_family" />
+ <item type="string" name="wear_compose_material3_body_large_font_family" />
+
+ <item type="string" name="wear_compose_material3_display_small_font_family" />
+ <item type="string" name="wear_compose_material3_display_medium_font_family" />
+ <item type="string" name="wear_compose_material3_display_large_font_family" />
+
+ <item type="string" name="wear_compose_material3_label_small_font_family" />
+ <item type="string" name="wear_compose_material3_label_medium_font_family" />
+ <item type="string" name="wear_compose_material3_label_large_font_family" />
+
+ <item type="string" name="wear_compose_material3_numeral_extra_small_font_family" />
+ <item type="string" name="wear_compose_material3_numeral_small_font_family" />
+ <item type="string" name="wear_compose_material3_numeral_medium_font_family" />
+ <item type="string" name="wear_compose_material3_numeral_large_font_family" />
+ <item type="string" name="wear_compose_material3_numeral_extra_large_font_family" />
+
+ <item type="string" name="wear_compose_material3_title_small_font_family" />
+ <item type="string" name="wear_compose_material3_title_medium_font_family" />
+ <item type="string" name="wear_compose_material3_title_large_font_family" />
+ <!--END WEAR SPECIFIC MATERIAL3 FONT FACE TOKENS-->
+
+
+ <!--START WEAR SPECIFIC MATERIAL3 FONT SIZE TOKENS-->
+ <item type="dimen" name="wear_compose_material3_arc_small_font_size" />
+ <item type="dimen" name="wear_compose_material3_arc_medium_font_size" />
+ <item type="dimen" name="wear_compose_material3_arc_large_font_size" />
+
+ <item type="dimen" name="wear_compose_material3_body_extra_small_font_size" />
+ <item type="dimen" name="wear_compose_material3_body_small_font_size" />
+ <item type="dimen" name="wear_compose_material3_body_medium_font_size" />
+ <item type="dimen" name="wear_compose_material3_body_large_font_size" />
+
+ <item type="dimen" name="wear_compose_material3_display_small_font_size" />
+ <item type="dimen" name="wear_compose_material3_display_medium_font_size" />
+ <item type="dimen" name="wear_compose_material3_display_large_font_size" />
+
+ <item type="dimen" name="wear_compose_material3_label_small_font_size" />
+ <item type="dimen" name="wear_compose_material3_label_medium_font_size" />
+ <item type="dimen" name="wear_compose_material3_label_large_font_size" />
+
+ <item type="dimen" name="wear_compose_material3_numeral_extra_small_font_size" />
+ <item type="dimen" name="wear_compose_material3_numeral_small_font_size" />
+ <item type="dimen" name="wear_compose_material3_numeral_medium_font_size" />
+ <item type="dimen" name="wear_compose_material3_numeral_large_font_size" />
+ <item type="dimen" name="wear_compose_material3_numeral_extra_large_font_size" />
+
+ <item type="dimen" name="wear_compose_material3_title_small_font_size" />
+ <item type="dimen" name="wear_compose_material3_title_medium_font_size" />
+ <item type="dimen" name="wear_compose_material3_title_large_font_size" />
+ <!--END WEAR SPECIFIC MATERIAL3 FONT SIZE TOKENS-->
+
+ <!--START WEAR SPECIFIC MATERIAL3 SHAPE TOKENS-->
+ <item type="dimen" name="wear_compose_material3_shape_corner_extra_small_size" />
+ <item type="dimen" name="wear_compose_material3_shape_corner_small_size" />
+ <item type="dimen" name="wear_compose_material3_shape_corner_medium_size" />
+ <item type="dimen" name="wear_compose_material3_shape_corner_large_size" />
+ <item type="dimen" name="wear_compose_material3_shape_corner_extra_large_size" />
+ <!--END WEAR SPECIFIC MATERIAL3 SHAPE TOKENS-->
+ </policy>
+ </overlayable>
+</resources> \ No newline at end of file
diff --git a/PermissionController/wear-permission-components/res/values/strings.xml b/PermissionController/wear-permission-components/res/values/strings.xml
new file mode 100644
index 000000000..a9956ddb5
--- /dev/null
+++ b/PermissionController/wear-permission-components/res/values/strings.xml
@@ -0,0 +1,7 @@
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Accessibility description indicating that a toggle control status is on. [CHAR LIMIT=NONE] -->
+ <string name="on">On</string>
+
+ <!-- Accessibility description indicating that a status is off. [CHAR LIMIT=NONE] -->
+ <string name="off">Off</string>
+</resources> \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt b/PermissionController/wear-permission-components/src/wear.permission.components/AnnotatedText.kt
index bcdf3b661..4d7954c5a 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AnnotatedText.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/AnnotatedText.kt
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.wear.permission.components
import android.text.Spanned
import android.text.style.ClickableSpan
@@ -32,47 +31,74 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.wear.compose.material.MaterialTheme
+import java.util.Locale
const val CLICKABLE_SPAN_TAG = "CLICKABLE_SPAN_TAG"
+fun String.capitalize(): String = replaceFirstChar {
+ if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
+}
+
@Composable
-fun AnnotatedText(text: CharSequence, style: TextStyle, modifier: Modifier = Modifier) {
+fun AnnotatedText(
+ text: CharSequence,
+ style: TextStyle,
+ modifier: Modifier = Modifier,
+ shouldCapitalize: Boolean,
+) {
val onClickCallbacks = mutableMapOf<String, (View) -> Unit>()
val context = LocalContext.current
val listener = LinkInteractionListener {
if (it is LinkAnnotation.Clickable) {
- onClickCallbacks.get(it.tag)?.invoke(View(context))
+ onClickCallbacks[it.tag]?.invoke(View(context))
}
}
val annotatedString =
- spannableStringToAnnotatedString(text, onClickCallbacks, listener = listener)
+ spannableStringToAnnotatedString(
+ text,
+ shouldCapitalize,
+ onClickCallbacks,
+ listener = listener,
+ )
BasicText(text = annotatedString, style = style, modifier = modifier)
}
@Composable
private fun spannableStringToAnnotatedString(
text: CharSequence,
+ shouldCapitalize: Boolean,
onClickCallbacks: MutableMap<String, (View) -> Unit>,
spanColor: Color = MaterialTheme.colors.primary,
- listener: LinkInteractionListener
-) =
- if (text is Spanned) {
- buildAnnotatedString {
- append((text.toString()))
- for (span in text.getSpans(0, text.length, Any::class.java)) {
- val start = text.getSpanStart(span)
- val end = text.getSpanEnd(span)
- when (span) {
- is ClickableSpan ->
- addClickableSpan(span, spanColor, start, end, onClickCallbacks, listener)
- else -> addStyle(SpanStyle(), start, end)
+ listener: LinkInteractionListener,
+): AnnotatedString {
+ val finalString = if (shouldCapitalize) text.toString().capitalize() else text.toString()
+ val annotatedString =
+ if (text is Spanned) {
+ buildAnnotatedString {
+ append(finalString)
+ for (span in text.getSpans(0, text.length, Any::class.java)) {
+ val start = text.getSpanStart(span)
+ val end = text.getSpanEnd(span)
+ when (span) {
+ is ClickableSpan ->
+ addClickableSpan(
+ span,
+ spanColor,
+ start,
+ end,
+ onClickCallbacks,
+ listener,
+ )
+ else -> addStyle(SpanStyle(), start, end)
+ }
}
}
+ } else {
+ AnnotatedString(finalString)
}
- } else {
- AnnotatedString(text.toString())
- }
+ return annotatedString
+}
private fun AnnotatedString.Builder.addClickableSpan(
span: ClickableSpan,
@@ -80,14 +106,10 @@ private fun AnnotatedString.Builder.addClickableSpan(
start: Int,
end: Int,
onClickCallbacks: MutableMap<String, (View) -> Unit>,
- listener: LinkInteractionListener
+ listener: LinkInteractionListener,
) {
val key = "${CLICKABLE_SPAN_TAG}:$start:$end"
onClickCallbacks[key] = span::onClick
addLink(LinkAnnotation.Clickable(key, linkInteractionListener = listener), start, end)
- addStyle(
- SpanStyle(color = spanColor, textDecoration = TextDecoration.Underline),
- start,
- end,
- )
+ addStyle(SpanStyle(color = spanColor, textDecoration = TextDecoration.Underline), start, end)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/CheckYourPhone.kt b/PermissionController/wear-permission-components/src/wear.permission.components/CheckYourPhone.kt
index 59376b8fa..467aae4e1 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/CheckYourPhone.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/CheckYourPhone.kt
@@ -13,26 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.wear.permission.components
import android.content.res.Configuration
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material.CircularProgressIndicator
+import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Scaffold
import androidx.wear.compose.material.Text
-import com.android.permissioncontroller.R
private const val TOP_PADDING_SCREEN_PERCENTAGE = 0.1248f
private const val BOTTOM_PADDING_SCREEN_PERCENTAGE = 0.0624f
@@ -41,16 +47,12 @@ private const val TEXT_PADDING_SCREEN_PERCENTAGE = 0.0416f
enum class CheckYourPhoneState {
InProgress,
- Success
+ Success,
}
/**
* A screen to request the user to check their paired phone to proceed. It also allows a [message]
* to be displayed.
- *
- * <img
- * src="https://media.githubusercontent.com/media/google/horologist/main/docs/auth-composables/check_your_phone_screen_code.png"
- * height="120" width="120"/>
*/
@Composable
fun CheckYourPhoneScreen(
@@ -60,9 +62,7 @@ fun CheckYourPhoneScreen(
message: String? = null,
) {
val configuration = LocalConfiguration.current
-
val isLarge = configuration.isLargeScreen
-
val topPadding = (configuration.screenHeightDp * TOP_PADDING_SCREEN_PERCENTAGE).dp
val bottomPadding = (configuration.screenHeightDp * BOTTOM_PADDING_SCREEN_PERCENTAGE).dp
val sidePadding = (configuration.screenHeightDp * SIDE_PADDING_SCREEN_PERCENTAGE).dp
@@ -79,7 +79,7 @@ fun CheckYourPhoneScreen(
bottom = bottomPadding,
start = sidePadding,
end = sidePadding,
- ),
+ )
) {
Column(
modifier = Modifier.fillMaxWidth().weight(1f).padding(horizontal = textPadding),
@@ -104,21 +104,53 @@ fun CheckYourPhoneScreen(
}
}
when (state) {
- CheckYourPhoneState.InProgress ->
- RemoteConnectionProgressIndicator(
- iconRes = R.drawable.ic_security_update_good,
- Modifier.align(Alignment.CenterHorizontally)
- )
- CheckYourPhoneState.Success ->
- RemoteConnectionSuccess(
- iconRes = R.drawable.ic_security_update_good,
- Modifier.align(Alignment.CenterHorizontally)
- )
+ CheckYourPhoneState.InProgress -> RemoteConnectionProgressIndicator()
+ CheckYourPhoneState.Success -> RemoteConnectionSuccess()
}
}
}
}
+@Composable
+private fun RemoteConnectionProgressIndicator() {
+ val indicatorPadding = 8.dp
+ val iconSize = 48.dp
+ val progressBarStrokeWidth = 4.dp
+ Box(modifier = Modifier.size(iconSize).clip(CircleShape)) {
+ CircularProgressIndicator(
+ modifier = Modifier.size(iconSize - progressBarStrokeWidth + indicatorPadding),
+ strokeWidth = progressBarStrokeWidth,
+ )
+ Icon(
+ painter = painterResource(R.drawable.ic_security_update_good),
+ contentDescription = null,
+ modifier =
+ Modifier.align(Alignment.Center)
+ .size(iconSize - indicatorPadding - 8.dp)
+ .clip(CircleShape),
+ )
+ }
+}
+
+@Composable
+private fun RemoteConnectionSuccess() {
+ val indicatorPadding = 8.dp
+ val iconSize = 48.dp
+ val backgroundColor = MaterialTheme.colors.onSurface
+ val contentColor = MaterialTheme.colors.surface
+ Box(modifier = Modifier.size(iconSize).clip(CircleShape).background(backgroundColor)) {
+ Icon(
+ painter = painterResource(R.drawable.ic_security_update_good),
+ contentDescription = null,
+ tint = contentColor,
+ modifier =
+ Modifier.align(Alignment.Center)
+ .size(iconSize - indicatorPadding - 8.dp)
+ .clip(CircleShape),
+ )
+ }
+}
+
/** Whether the device is considered large screen for layout adjustment purposes. */
internal val Configuration.isLargeScreen: Boolean
get() = screenHeightDp > 224
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/DrawablePainter.kt b/PermissionController/wear-permission-components/src/wear.permission.components/DrawablePainter.kt
index d505a0ea4..a22fb2b96 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/DrawablePainter.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/DrawablePainter.kt
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.wear.permission.components
import android.graphics.drawable.Animatable
import android.graphics.drawable.ColorDrawable
@@ -166,5 +165,6 @@ private val Drawable.intrinsicSize: Size
internal object EmptyPainter : Painter() {
override val intrinsicSize: Size
get() = Size.Unspecified
+
override fun DrawScope.onDraw() {}
}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/ScrollableScreen.kt b/PermissionController/wear-permission-components/src/wear.permission.components/ScrollableScreen.kt
new file mode 100644
index 000000000..6e7be01ec
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/ScrollableScreen.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components
+
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.LocalContext
+import androidx.fragment.app.FragmentActivity
+import androidx.wear.compose.foundation.ExpandableState
+import androidx.wear.compose.foundation.SwipeToDismissValue
+import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
+import androidx.wear.compose.material.SwipeToDismissBox
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionScaffold
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
+
+/**
+ * Screen that contains a list of items defined using the [content] parameter, adds the time text
+ * (if [showTimeText] is true), the tile (if [title] is not null), the vignette and the position
+ * indicator. It also manages the scaling animation and allows the user to scroll the content using
+ * the crown.
+ */
+@Composable
+fun ScrollableScreen(
+ materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
+ asScalingList: Boolean = false,
+ showTimeText: Boolean = true,
+ title: String? = null,
+ subtitle: CharSequence? = null,
+ image: Any? = null,
+ isLoading: Boolean = false,
+ titleTestTag: String? = null,
+ subtitleTestTag: String? = null,
+ content: ListScopeWrapper.() -> Unit,
+) {
+ var dismissed by remember { mutableStateOf(false) }
+ val activity = LocalContext.current.findActivity()
+ val state = rememberSwipeToDismissBoxState()
+
+ LaunchedEffect(state.currentValue) {
+ // If the swipe is complete
+ if (state.currentValue == SwipeToDismissValue.Dismissed) {
+ // pop the top fragment immediately or dismiss activity.
+ dismiss(activity)
+ // Set dismissed state as true
+ dismissed = true
+ // Set swipe box back to starting position(that is cancelled swipe effect) to
+ // show loading indicator while fragment dismisses.
+ // For some reason fragment `popBackImmediate` takes few secs at times.
+ state.snapTo(SwipeToDismissValue.Default)
+ }
+ }
+
+ if (getBackStackEntryCount(activity) > 0) {
+ SwipeToDismissBox(state = state) { isBackground ->
+ WearPermissionScaffold(
+ materialUIVersion,
+ asScalingList,
+ showTimeText,
+ title,
+ subtitle,
+ image,
+ isLoading = isLoading || isBackground || dismissed,
+ content,
+ titleTestTag,
+ subtitleTestTag,
+ )
+ }
+ } else {
+ WearPermissionScaffold(
+ materialUIVersion,
+ asScalingList,
+ showTimeText,
+ title,
+ subtitle,
+ image,
+ isLoading,
+ content,
+ titleTestTag,
+ subtitleTestTag,
+ )
+ }
+}
+
+fun dismiss(activity: Activity) {
+ if (activity is FragmentActivity) {
+ if (!activity.supportFragmentManager.popBackStackImmediate()) {
+ activity.finish()
+ }
+ } else {
+ activity.finish()
+ }
+}
+
+internal fun getBackStackEntryCount(activity: Activity): Int {
+ return if (activity is FragmentActivity) {
+ activity.supportFragmentManager.primaryNavigationFragment
+ ?.childFragmentManager
+ ?.backStackEntryCount ?: 0
+ } else {
+ 0
+ }
+}
+
+fun Context.findActivity(): Activity {
+ var context = this
+ while (context is ContextWrapper) {
+ if (context is Activity) return context
+ context = context.baseContext
+ }
+ throw IllegalStateException("The screen should be called in the context of an Activity")
+}
+
+interface ListScopeWrapper {
+ fun item(key: Any? = null, contentType: Any? = null, content: @Composable () -> Unit)
+
+ fun items(
+ count: Int,
+ key: ((index: Int) -> Any)? = null,
+ contentType: (index: Int) -> Any? = { null },
+ content: @Composable (index: Int) -> Unit,
+ )
+
+ fun expandableItems(
+ state: ExpandableState,
+ count: Int,
+ key: ((index: Int) -> Any)? = null,
+ itemContent: @Composable BoxScope.(index: Int) -> Unit,
+ )
+
+ fun expandableButton(state: ExpandableState, key: Any? = null, content: @Composable () -> Unit)
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material2/AlertDialog.kt
index c07d2ba9e..4abe5c8ca 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material2/AlertDialog.kt
@@ -14,20 +14,16 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.wear.permission.components.material2
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Check
-import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalFocusManager
-import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.heading
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@@ -35,15 +31,15 @@ import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
import androidx.wear.compose.foundation.lazy.ScalingLazyListState
-import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.LocalTextStyle
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
-import androidx.wear.compose.material.dialog.Alert
import androidx.wear.compose.material.dialog.Dialog
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnDefaults
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.rememberColumnState
+import com.android.permissioncontroller.wear.permission.components.material2.layout.ScalingLazyColumnDefaults
+import com.android.permissioncontroller.wear.permission.components.material2.layout.ScalingLazyColumnState
+import com.android.permissioncontroller.wear.permission.components.material2.layout.rememberColumnState
+import com.android.permissioncontroller.wear.permission.components.material3.DialogButtonContent
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
/**
* This component is an alternative to [AlertContent], providing the following:
@@ -54,95 +50,40 @@ import com.android.permissioncontroller.permission.ui.wear.elements.layout.remem
*/
@Composable
fun AlertDialog(
- message: String,
- iconRes: Int? = null,
- okButtonIcon: Any = Icons.Default.Check,
- cancelButtonIcon: Any = Icons.Default.Close,
- onCancelButtonClick: () -> Unit,
- onOKButtonClick: () -> Unit,
- showDialog: Boolean,
- scalingLazyListState: ScalingLazyListState,
- modifier: Modifier = Modifier,
title: String? = null,
- okButtonContentDescription: String = stringResource(android.R.string.ok),
- cancelButtonContentDescription: String = stringResource(android.R.string.cancel)
-) {
- val focusManager = LocalFocusManager.current
- Dialog(
- showDialog = showDialog,
- onDismissRequest = {
- focusManager.clearFocus()
- onCancelButtonClick()
- },
- scrollState = scalingLazyListState,
- modifier = modifier
- ) {
- AlertContent(
- title = title,
- icon = { AlertIcon(iconRes) },
- message = message,
- okButtonIcon = okButtonIcon,
- cancelButtonIcon = cancelButtonIcon,
- onCancel = onCancelButtonClick,
- onOk = onOKButtonClick,
- okButtonContentDescription = okButtonContentDescription,
- cancelButtonContentDescription = cancelButtonContentDescription
- )
- }
-}
-
-/**
- * This component is an alternative to [Alert], providing the following:
- * - a convenient way of passing a title and a message;
- * - default one button;
- * - wrapped in a [Dialog];
- */
-@Composable
-fun SingleButtonAlertDialog(
message: String,
- iconRes: Int? = null,
- okButtonIcon: Any = Icons.Default.Check,
- onButtonClick: () -> Unit,
+ positiveButtonContent: DialogButtonContent?,
+ negativeButtonContent: DialogButtonContent?,
showDialog: Boolean,
- scalingLazyListState: ScalingLazyListState,
modifier: Modifier = Modifier,
- title: String? = null,
- buttonContentDescription: String = stringResource(android.R.string.ok)
+ iconRes: WearPermissionIconBuilder? = null,
+ scalingLazyListState: ScalingLazyListState,
) {
Dialog(
showDialog = showDialog,
- onDismissRequest = {},
+ onDismissRequest = { negativeButtonContent?.onClick?.invoke() },
scrollState = scalingLazyListState,
- modifier = modifier
+ modifier = modifier,
) {
AlertContent(
title = title,
- icon = { AlertIcon(iconRes) },
+ icon = { iconRes?.build() },
message = message,
- okButtonIcon = okButtonIcon,
- onOk = onButtonClick,
- okButtonContentDescription = buttonContentDescription
+ positiveButtonContent = positiveButtonContent,
+ negativeButtonContent = negativeButtonContent,
)
}
}
@Composable
fun AlertContent(
- onCancel: (() -> Unit)? = null,
- onOk: (() -> Unit)? = null,
icon: @Composable (() -> Unit)? = null,
title: String? = null,
message: String? = null,
- okButtonIcon: Any = Icons.Default.Check,
- cancelButtonIcon: Any = Icons.Default.Close,
- okButtonContentDescription: String = stringResource(android.R.string.ok),
- cancelButtonContentDescription: String = stringResource(android.R.string.cancel),
+ positiveButtonContent: DialogButtonContent?,
+ negativeButtonContent: DialogButtonContent?,
state: ScalingLazyColumnState =
- rememberColumnState(
- ScalingLazyColumnDefaults.responsive(
- additionalPaddingAtBottom = 0.dp,
- ),
- ),
+ rememberColumnState(ScalingLazyColumnDefaults.responsive(additionalPaddingAtBottom = 0.dp)),
showPositionIndicator: Boolean = true,
content: (ScalingLazyListScope.() -> Unit)? = null,
) {
@@ -155,7 +96,7 @@ fun AlertContent(
title?.let {
{
Text(
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier.fillMaxWidth().semantics() { heading() },
text = it,
color = MaterialTheme.colors.onBackground,
textAlign = TextAlign.Center,
@@ -185,7 +126,7 @@ fun AlertContent(
maxWidth =
(maxScreenWidthPx *
(1f - totalPaddingPercentage * 2f / 100f))
- .toInt(),
+ .toInt()
),
)
.lineCount
@@ -200,21 +141,9 @@ fun AlertContent(
}
},
content = content,
- onOk = onOk,
- onCancel = onCancel,
- okButtonIcon = okButtonIcon,
- cancelButtonIcon = cancelButtonIcon,
- okButtonContentDescription = okButtonContentDescription,
- cancelButtonContentDescription = cancelButtonContentDescription,
+ positiveButtonContent = positiveButtonContent,
+ negativeButtonContent = negativeButtonContent,
state = state,
showPositionIndicator = showPositionIndicator,
)
}
-
-@Composable
-private fun AlertIcon(iconRes: Int?) =
- if (iconRes != null && iconRes != 0) {
- Icon(painter = painterResource(iconRes), contentDescription = null)
- } else {
- null
- }
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material2/Chip.kt
index 40f097c67..23074a06f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material2/Chip.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.wear.permission.components.material2
import android.graphics.drawable.Drawable
import androidx.annotation.StringRes
@@ -33,7 +33,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.Hyphens
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@@ -46,6 +45,7 @@ import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
import androidx.wear.compose.material.contentColorFor
+import com.android.permissioncontroller.wear.permission.components.rememberDrawablePainter
/**
* This component is an alternative to [Chip], providing the following:
@@ -67,7 +67,7 @@ fun Chip(
textColor: Color = MaterialTheme.colors.onSurface,
iconColor: Color = Color.Unspecified,
colors: ChipColors = chipDefaultColors(),
- enabled: Boolean = true
+ enabled: Boolean = true,
) {
val iconParam: (@Composable BoxScope.() -> Unit)? =
icon?.let {
@@ -87,21 +87,21 @@ fun Chip(
imageVector = icon,
tint = iconColor,
contentDescription = iconContentDescription,
- modifier = iconModifier
+ modifier = iconModifier,
)
is Int ->
Icon(
painter = painterResource(id = icon),
tint = iconColor,
contentDescription = iconContentDescription,
- modifier = iconModifier
+ modifier = iconModifier,
)
is Drawable ->
Icon(
painter = rememberDrawablePainter(icon),
tint = iconColor,
contentDescription = iconContentDescription,
- modifier = iconModifier
+ modifier = iconModifier,
)
else -> {}
}
@@ -120,7 +120,7 @@ fun Chip(
largeIcon = largeIcon,
textColor = textColor,
colors = colors,
- enabled = enabled
+ enabled = enabled,
)
}
@@ -143,7 +143,7 @@ fun Chip(
textColor: Color = MaterialTheme.colors.onSurface,
iconColor: Color = Color.Unspecified,
colors: ChipColors = chipDefaultColors(),
- enabled: Boolean = true
+ enabled: Boolean = true,
) {
Chip(
label = stringResource(id = labelId),
@@ -157,7 +157,7 @@ fun Chip(
textColor = textColor,
iconColor = iconColor,
colors = colors,
- enabled = enabled
+ enabled = enabled,
)
}
@@ -180,7 +180,7 @@ fun Chip(
textColor: Color = MaterialTheme.colors.onSurface,
secondaryTextColor: Color = MaterialTheme.colors.primary,
colors: ChipColors = chipDefaultColors(),
- enabled: Boolean = true
+ enabled: Boolean = true,
) {
val hasSecondaryLabel = secondaryLabel != null
val hasIcon = icon != null
@@ -193,11 +193,7 @@ fun Chip(
textAlign = if (hasSecondaryLabel || hasIcon) TextAlign.Start else TextAlign.Center,
overflow = TextOverflow.Ellipsis,
maxLines = labelMaxLines ?: if (hasSecondaryLabel) 1 else 2,
- style =
- MaterialTheme.typography.button.copy(
- fontWeight = FontWeight.W600,
- hyphens = Hyphens.Auto
- )
+ style = MaterialTheme.typography.button.copy(hyphens = Hyphens.Auto),
)
}
@@ -209,7 +205,7 @@ fun Chip(
color = secondaryTextColor,
overflow = TextOverflow.Ellipsis,
maxLines = secondaryLabelMaxLines ?: 1,
- style = MaterialTheme.typography.caption2
+ style = MaterialTheme.typography.caption2,
)
}
}
@@ -221,7 +217,7 @@ fun Chip(
start = 10.dp,
top = verticalPadding,
end = ChipDefaults.ChipHorizontalPadding,
- bottom = verticalPadding
+ bottom = verticalPadding,
)
} else {
ChipDefaults.ContentPadding
@@ -236,7 +232,7 @@ fun Chip(
colors = colors,
enabled = enabled,
contentPadding = contentPadding,
- shape = RoundedCornerShape(26.dp)
+ shape = RoundedCornerShape(26.dp),
)
}
@@ -258,6 +254,6 @@ fun chipDisabledColors(): ChipColors {
backgroundColor = backgroundColor.copy(alpha = ContentAlpha.disabled),
contentColor = contentColor.copy(alpha = ContentAlpha.disabled),
secondaryContentColor = secondaryContentColor.copy(alpha = ContentAlpha.disabled),
- iconColor = iconColor.copy(alpha = ContentAlpha.disabled)
+ iconColor = iconColor.copy(alpha = ContentAlpha.disabled),
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material2/Icon.kt
index 1a304b37e..dfed0b196 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material2/Icon.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.wear.permission.components.material2
import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
@@ -29,18 +29,19 @@ import androidx.compose.ui.unit.LayoutDirection
import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.LocalContentAlpha
import androidx.wear.compose.material.LocalContentColor
+import com.android.permissioncontroller.wear.permission.components.rememberDrawablePainter
/**
* This component is an alternative to [Icon], providing the following:
* - a convenient way of setting the icon to be mirrored in RTL mode;
*/
@Composable
-public fun Icon(
+fun Icon(
imageVector: ImageVector,
contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
- rtlMode: IconRtlMode = IconRtlMode.Default
+ rtlMode: IconRtlMode = IconRtlMode.Default,
) {
val shouldMirror =
rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl
@@ -48,7 +49,7 @@ public fun Icon(
modifier = modifier.scale(scaleX = if (shouldMirror) -1f else 1f, scaleY = 1f),
imageVector = imageVector,
contentDescription = contentDescription,
- tint = tint
+ tint = tint,
)
}
@@ -57,12 +58,12 @@ public fun Icon(
* - a convenient way of setting the icon to be mirrored in RTL mode;
*/
@Composable
-public fun Icon(
+fun Icon(
@DrawableRes id: Int,
contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
- rtlMode: IconRtlMode = IconRtlMode.Default
+ rtlMode: IconRtlMode = IconRtlMode.Default,
) {
val shouldMirror =
rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl
@@ -71,7 +72,7 @@ public fun Icon(
painter = painterResource(id = id),
contentDescription = contentDescription,
modifier = modifier.scale(scaleX = if (shouldMirror) -1f else 1f, scaleY = 1f),
- tint = tint
+ tint = tint,
)
}
@@ -86,7 +87,7 @@ fun Icon(
contentDescription: String?,
modifier: Modifier = Modifier,
tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current),
- rtlMode: IconRtlMode = IconRtlMode.Default
+ rtlMode: IconRtlMode = IconRtlMode.Default,
) {
val shouldMirror =
rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl
@@ -98,7 +99,7 @@ fun Icon(
imageVector = icon,
modifier = iconModifier,
contentDescription = contentDescription,
- tint = tint
+ tint = tint,
)
}
is Int -> {
@@ -106,7 +107,7 @@ fun Icon(
painter = painterResource(id = icon),
contentDescription = contentDescription,
modifier = iconModifier,
- tint = tint
+ tint = tint,
)
}
is Drawable -> {
@@ -114,14 +115,14 @@ fun Icon(
painter = rememberDrawablePainter(icon),
contentDescription = contentDescription,
modifier = iconModifier,
- tint = tint
+ tint = tint,
)
}
else -> throw IllegalArgumentException("Type not supported.")
}
}
-public enum class IconRtlMode {
+enum class IconRtlMode {
Default,
- Mirrored
+ Mirrored,
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material2/ListFooter.kt
index 5ed912ec6..ac603d492 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material2/ListFooter.kt
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.wear.permission.components.material2
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
@@ -52,7 +51,7 @@ fun ListFooter(description: String, iconRes: Int? = null, onClick: (() -> Unit)?
contentDescription = null,
modifier =
Modifier.size(LeadingIconSize, LeadingIconSize)
- .align(Alignment.CenterVertically)
+ .align(Alignment.CenterVertically),
)
Spacer(modifier = Modifier.width(LeadingIconEndSpacing))
}
@@ -62,7 +61,7 @@ fun ListFooter(description: String, iconRes: Int? = null, onClick: (() -> Unit)?
textAlign = TextAlign.Start,
overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colors.onSurfaceVariant,
- style = MaterialTheme.typography.caption2
+ style = MaterialTheme.typography.caption2,
)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material2/ListHeader.kt
index 0a2a3937c..6ed81353a 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material2/ListHeader.kt
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.wear.permission.components.material2
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -35,7 +34,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.Hyphens
import androidx.compose.ui.unit.dp
import androidx.wear.compose.material.LocalContentColor
@@ -60,7 +58,7 @@ fun ListHeader(
modifier: Modifier = Modifier,
backgroundColor: Color = Color.Transparent,
contentColor: Color = MaterialTheme.colors.onBackground,
- content: @Composable RowScope.() -> Unit
+ content: @Composable RowScope.() -> Unit,
) {
Row(
horizontalArrangement = Arrangement.Center,
@@ -69,15 +67,11 @@ fun ListHeader(
mergeDescendants = true
) {
heading()
- }
+ },
) {
CompositionLocalProvider(
LocalContentColor provides contentColor,
- LocalTextStyle provides
- MaterialTheme.typography.title3.copy(
- fontWeight = FontWeight.W600,
- hyphens = Hyphens.Auto
- ),
+ LocalTextStyle provides MaterialTheme.typography.title3.copy(hyphens = Hyphens.Auto),
) {
content()
}
@@ -111,7 +105,7 @@ fun ListSubheader(
.fillMaxWidth()
.wrapContentSize(align = Alignment.CenterStart)
.background(backgroundColor)
- .semantics(mergeDescendants = true) { heading() }
+ .semantics(mergeDescendants = true) { heading() },
) {
CompositionLocalProvider(
LocalContentColor provides contentColor,
@@ -120,7 +114,7 @@ fun ListSubheader(
if (icon != null) {
Box(
modifier = Modifier.wrapContentSize(align = Alignment.CenterStart),
- content = icon
+ content = icon,
)
Spacer(modifier = Modifier.width(6.dp))
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ResponsiveDialog.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material2/ResponsiveDialog.kt
index e1e869f71..64597e23d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ResponsiveDialog.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material2/ResponsiveDialog.kt
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.wear.permission.components.material2
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Arrangement.spacedBy
@@ -29,15 +28,11 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Check
-import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
@@ -49,13 +44,14 @@ import androidx.wear.compose.material.LocalTextStyle
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.PositionIndicator
import androidx.wear.compose.material.Scaffold
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumn
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnDefaults.responsive
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.rememberColumnState
-
-// This file is a copy of ResponsiveDialogContent.kt from Horologist (go/horologist),
-// remove it once after wear compose supports large screen dialogs.
+import com.android.permissioncontroller.wear.permission.components.material2.layout.ScalingLazyColumn
+import com.android.permissioncontroller.wear.permission.components.material2.layout.ScalingLazyColumnDefaults.responsive
+import com.android.permissioncontroller.wear.permission.components.material2.layout.ScalingLazyColumnState
+import com.android.permissioncontroller.wear.permission.components.material2.layout.rememberColumnState
+import com.android.permissioncontroller.wear.permission.components.material3.DialogButtonContent
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionIconBuilder
+import com.android.permissioncontroller.wear.permission.components.material3.defaultAlertConfirmIcon
+import com.android.permissioncontroller.wear.permission.components.material3.defaultAlertDismissIcon
@Composable
fun ResponsiveDialogContent(
@@ -63,18 +59,11 @@ fun ResponsiveDialogContent(
icon: @Composable (() -> Unit)? = null,
title: @Composable (() -> Unit)? = null,
message: @Composable (() -> Unit)? = null,
- okButtonIcon: Any = Icons.Default.Check,
- cancelButtonIcon: Any = Icons.Default.Close,
- onOk: (() -> Unit)? = null,
- onCancel: (() -> Unit)? = null,
- okButtonContentDescription: String = stringResource(android.R.string.ok),
- cancelButtonContentDescription: String = stringResource(android.R.string.cancel),
+ positiveButtonContent: DialogButtonContent? = null,
+ negativeButtonContent: DialogButtonContent? = null,
state: ScalingLazyColumnState =
rememberColumnState(
- responsive(
- firstItemIsFullWidth = icon == null,
- additionalPaddingAtBottom = 0.dp,
- ),
+ responsive(firstItemIsFullWidth = icon == null, additionalPaddingAtBottom = 0.dp)
),
showPositionIndicator: Boolean = true,
content: (ScalingLazyListScope.() -> Unit)? = null,
@@ -89,9 +78,7 @@ fun ResponsiveDialogContent(
timeText = {},
) {
// This will be applied only to the content.
- CompositionLocalProvider(
- LocalTextStyle provides MaterialTheme.typography.body2,
- ) {
+ CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.body2) {
ScalingLazyColumn(columnState = state) {
icon?.let {
item {
@@ -107,11 +94,11 @@ fun ResponsiveDialogContent(
item {
CompositionLocalProvider(
LocalTextStyle provides
- MaterialTheme.typography.title3.copy(fontWeight = FontWeight.W600),
+ MaterialTheme.typography.title3.copy(fontWeight = FontWeight.W600)
) {
Box(
Modifier.fillMaxWidth(titleMaxWidthFraction)
- .padding(bottom = 8.dp), // 12.dp below icon
+ .padding(bottom = 8.dp) // 12.dp below icon
) {
it()
}
@@ -123,22 +110,20 @@ fun ResponsiveDialogContent(
item { Spacer(Modifier.height(20.dp)) }
}
message?.let {
- item {
- Box(
- Modifier.fillMaxWidth(messageMaxWidthFraction),
- ) {
- it()
- }
- }
+ item { Box(Modifier.fillMaxWidth(messageMaxWidthFraction)) { it() } }
}
content?.let { it() }
- if (onOk != null || onCancel != null) {
+ if (positiveButtonContent != null || negativeButtonContent != null) {
item {
val width = LocalConfiguration.current.screenWidthDp
// Single buttons, or buttons on smaller screens are not meant to be
// responsive.
val buttonWidth =
- if (width < 225 || onOk == null || onCancel == null) {
+ if (
+ width < 225 ||
+ positiveButtonContent == null ||
+ negativeButtonContent == null
+ ) {
ButtonDefaults.DefaultButtonSize
} else {
// 14.56% on top of 5.2% margin on the sides, 12.dp between.
@@ -147,25 +132,30 @@ fun ResponsiveDialogContent(
Row(
Modifier.fillMaxWidth()
.padding(
- top = if (content != null || message != null) 12.dp else 0.dp,
+ top = if (content != null || message != null) 12.dp else 0.dp
),
horizontalArrangement = spacedBy(12.dp, Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically,
) {
- onCancel?.let {
+ negativeButtonContent?.run {
ResponsiveButton(
- icon = cancelButtonIcon,
- cancelButtonContentDescription,
- onClick = it,
+ this.icon
+ ?: WearPermissionIconBuilder.defaultAlertDismissIcon()
+ .tint(
+ ChipDefaults.secondaryChipColors()
+ .contentColor(true)
+ .value
+ ),
+ onClick,
buttonWidth,
ChipDefaults.secondaryChipColors(),
)
}
- onOk?.let {
+ positiveButtonContent?.run {
ResponsiveButton(
- icon = okButtonIcon,
- okButtonContentDescription,
- onClick = it,
+ this.icon
+ ?: WearPermissionIconBuilder.defaultAlertConfirmIcon(),
+ onClick,
buttonWidth,
)
}
@@ -179,8 +169,7 @@ fun ResponsiveDialogContent(
@Composable
private fun ResponsiveButton(
- icon: Any,
- contentDescription: String,
+ icon: WearPermissionIconBuilder,
onClick: () -> Unit,
buttonWidth: Dp,
colors: ChipColors = ChipDefaults.primaryChipColors(),
@@ -188,12 +177,9 @@ private fun ResponsiveButton(
androidx.wear.compose.material.Chip(
label = {
Box(Modifier.fillMaxWidth()) {
- Icon(
- icon = icon,
- contentDescription = contentDescription,
- modifier =
- Modifier.size(ButtonDefaults.DefaultIconSize).align(Alignment.Center),
- )
+ icon
+ .modifier(Modifier.size(ButtonDefaults.DefaultIconSize).align(Alignment.Center))
+ .build()
}
},
contentPadding = PaddingValues(0.dp),
@@ -210,19 +196,10 @@ internal const val titleExtraHorizontalPadding = 8.84f
// Fraction of the max available width that message should take (after global and message padding)
internal val messageMaxWidthFraction =
- 1f -
- 2f *
- calculatePaddingFraction(
- messageExtraHorizontalPadding,
- )
+ 1f - 2f * calculatePaddingFraction(messageExtraHorizontalPadding)
// Fraction of the max available width that title should take (after global and message padding)
-internal val titleMaxWidthFraction =
- 1f -
- 2f *
- calculatePaddingFraction(
- titleExtraHorizontalPadding,
- )
+internal val titleMaxWidthFraction = 1f - 2f * calculatePaddingFraction(titleExtraHorizontalPadding)
// Calculate total padding given global padding and additional padding required inside that.
internal fun calculatePaddingFraction(extraPadding: Float) =
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material2/ToggleChip.kt
index a21a9d015..de8e0542d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material2/ToggleChip.kt
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.wear.permission.components.material2
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.BoxScope
@@ -44,7 +43,8 @@ import androidx.wear.compose.material.ToggleChip
import androidx.wear.compose.material.ToggleChipColors
import androidx.wear.compose.material.ToggleChipDefaults
import androidx.wear.compose.material.contentColorFor
-import com.android.permissioncontroller.R
+import com.android.permissioncontroller.wear.permission.components.R
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionToggleControlType
/**
* This component is an alternative to [ToggleChip], providing the following:
@@ -58,7 +58,7 @@ fun ToggleChip(
onCheckedChanged: (Boolean) -> Unit,
label: String,
labelMaxLine: Int? = null,
- toggleControl: ToggleChipToggleControl,
+ toggleControl: WearPermissionToggleControlType,
modifier: Modifier = Modifier,
icon: Any? = null,
iconColor: Color = Color.Unspecified,
@@ -67,7 +67,7 @@ fun ToggleChip(
secondaryLabelMaxLine: Int? = null,
colors: ToggleChipColors = ToggleChipDefaults.toggleChipColors(),
enabled: Boolean = true,
- interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val hasSecondaryLabel = secondaryLabel != null
@@ -78,7 +78,7 @@ fun ToggleChip(
textAlign = TextAlign.Start,
overflow = TextOverflow.Ellipsis,
maxLines = labelMaxLine ?: if (hasSecondaryLabel) 1 else 2,
- style = MaterialTheme.typography.button
+ style = MaterialTheme.typography.button,
)
}
@@ -89,7 +89,7 @@ fun ToggleChip(
text = secondaryLabel,
overflow = TextOverflow.Ellipsis,
maxLines = secondaryLabelMaxLine ?: 1,
- style = MaterialTheme.typography.caption2
+ style = MaterialTheme.typography.caption2,
)
}
}
@@ -98,19 +98,20 @@ fun ToggleChip(
Icon(
imageVector =
when (toggleControl) {
- ToggleChipToggleControl.Switch -> ToggleChipDefaults.switchIcon(checked)
- ToggleChipToggleControl.Radio -> ToggleChipDefaults.radioIcon(checked)
- ToggleChipToggleControl.Checkbox -> ToggleChipDefaults.checkboxIcon(checked)
+ WearPermissionToggleControlType.Switch -> ToggleChipDefaults.switchIcon(checked)
+ WearPermissionToggleControlType.Radio -> ToggleChipDefaults.radioIcon(checked)
+ WearPermissionToggleControlType.Checkbox ->
+ ToggleChipDefaults.checkboxIcon(checked)
},
contentDescription = null,
// This potentially be removed once this issue is addressed:
// https://issuetracker.google.com/issues/287087138
rtlMode =
- if (toggleControl == ToggleChipToggleControl.Switch) {
+ if (toggleControl == WearPermissionToggleControlType.Switch) {
IconRtlMode.Mirrored
} else {
IconRtlMode.Default
- }
+ },
)
}
@@ -123,42 +124,28 @@ fun ToggleChip(
tint = iconColor,
contentDescription = null,
modifier = Modifier.size(ChipDefaults.IconSize).clip(CircleShape),
- rtlMode = iconRtlMode
+ rtlMode = iconRtlMode,
)
}
}
}
- val semanticsRole =
- when (toggleControl) {
- ToggleChipToggleControl.Switch -> Role.Switch
- ToggleChipToggleControl.Radio -> Role.RadioButton
- ToggleChipToggleControl.Checkbox -> Role.Checkbox
- }
-
- val stateDescriptionSemantics =
- stringResource(
- if (checked) {
- R.string.on
- } else {
- R.string.off
- }
- )
ToggleChip(
checked = checked,
- onCheckedChange = onCheckedChanged,
+ onCheckedChange = { newChecked ->
+ // Radio buttons cannot be toggled off by tapping on it again.
+ if (toggleControl != WearPermissionToggleControlType.Radio || newChecked) {
+ onCheckedChanged.invoke(newChecked)
+ }
+ },
label = labelParam,
toggleControl = toggleControlParam,
- modifier =
- modifier.fillMaxWidth().semantics {
- role = semanticsRole
- stateDescription = stateDescriptionSemantics
- },
+ modifier = modifier.fillMaxWidth().toggleControlSemantics(toggleControl, checked),
appIcon = iconParam,
secondaryLabel = secondaryLabelParam,
colors = colors,
enabled = enabled,
- interactionSource = interactionSource
+ interactionSource = interactionSource,
)
}
@@ -198,7 +185,7 @@ fun toggleChipDisabledColors(): ToggleChipColors {
uncheckedSecondaryContentColor =
uncheckedSecondaryContentColor.copy(alpha = ContentAlpha.disabled),
uncheckedToggleControlColor =
- uncheckedToggleControlColor.copy(alpha = ContentAlpha.disabled)
+ uncheckedToggleControlColor.copy(alpha = ContentAlpha.disabled),
)
}
@@ -236,6 +223,32 @@ fun toggleChipBackgroundColors(): ToggleChipColors {
uncheckedEndBackgroundColor = uncheckedEndBackgroundColor,
uncheckedContentColor = uncheckedContentColor,
uncheckedSecondaryContentColor = uncheckedSecondaryContentColor,
- uncheckedToggleControlColor = uncheckedToggleControlColor
+ uncheckedToggleControlColor = uncheckedToggleControlColor,
)
}
+
+@Composable
+fun Modifier.toggleControlSemantics(
+ toggleControl: WearPermissionToggleControlType,
+ checked: Boolean,
+): Modifier {
+ val semanticsRole =
+ when (toggleControl) {
+ WearPermissionToggleControlType.Switch -> Role.Switch
+ WearPermissionToggleControlType.Radio -> Role.RadioButton
+ WearPermissionToggleControlType.Checkbox -> Role.Checkbox
+ }
+ val stateDescriptionSemantics =
+ stringResource(
+ if (checked) {
+ R.string.on
+ } else {
+ R.string.off
+ }
+ )
+
+ return semantics {
+ role = semanticsRole
+ stateDescription = stateDescriptionSemantics
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material2/Wear2Scaffold.kt
index 53013def7..b5ba5b71e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material2/Wear2Scaffold.kt
@@ -1,11 +1,11 @@
/*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
- * http://www.apache.org/licenses/LICENSE-2.0
+ * https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,12 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.permissioncontroller.wear.permission.components.material2
-package com.android.permissioncontroller.permission.ui.wear.elements
-
-import android.app.Activity
-import android.content.Context
-import android.content.ContextWrapper
import android.graphics.drawable.Drawable
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
@@ -29,107 +25,44 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
-import androidx.wear.compose.foundation.SwipeToDismissValue
import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
import androidx.wear.compose.foundation.lazy.ScalingLazyListState
-import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState
import androidx.wear.compose.material.CircularProgressIndicator
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.PositionIndicator
import androidx.wear.compose.material.Scaffold
-import androidx.wear.compose.material.SwipeToDismissBox
import androidx.wear.compose.material.Text
import androidx.wear.compose.material.TimeText
import androidx.wear.compose.material.Vignette
import androidx.wear.compose.material.VignettePosition
import androidx.wear.compose.material.scrollAway
-import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rotaryWithScroll
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
+import com.android.permissioncontroller.wear.permission.components.AnnotatedText
+import com.android.permissioncontroller.wear.permission.components.rememberDrawablePainter
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionTheme
/**
- * Screen that contains a list of items defined using the [content] parameter, adds the time text
- * (if [showTimeText] is true), the tile (if [title] is not null), the vignette and the position
- * indicator. It also manages the scaling animation and allows the user to scroll the content using
- * the crown.
+ * This component is wrapper on material 2 scaffold component. It helps with time text, scroll
+ * indicator and standard list elements like title, icon and subtitle.
*/
@Composable
-fun ScrollableScreen(
- showTimeText: Boolean = true,
- title: String? = null,
- subtitle: CharSequence? = null,
- image: Any? = null,
- isLoading: Boolean = false,
- titleTestTag: String? = null,
- subtitleTestTag: String? = null,
- content: ScalingLazyListScope.() -> Unit,
-) {
- var dismissed by remember { mutableStateOf(false) }
- val activity = LocalContext.current.findActivity()
- val state = rememberSwipeToDismissBoxState()
-
- LaunchedEffect(state.currentValue) {
- // If the swipe is complete
- if (state.currentValue == SwipeToDismissValue.Dismissed) {
- // pop the top fragment immediately or dismiss activity.
- dismiss(activity)
- // Set dismissed state as true
- dismissed = true
- // Set swipe box back to starting position(that is cancelled swipe effect) to
- // show loading indicator while fragment dismisses.
- // For some reason fragment `popBackImmediate` takes few secs at times.
- state.snapTo(SwipeToDismissValue.Default)
- }
- }
-
- if (getBackStackEntryCount(activity) > 0) {
- SwipeToDismissBox(state = state) { isBackground ->
- Scaffold(
- showTimeText,
- title,
- subtitle,
- image,
- isLoading = isLoading || isBackground || dismissed,
- content,
- titleTestTag,
- subtitleTestTag
- )
- }
- } else {
- Scaffold(
- showTimeText,
- title,
- subtitle,
- image,
- isLoading,
- content,
- titleTestTag,
- subtitleTestTag
- )
- }
-}
-
-@Composable
-internal fun Scaffold(
+fun Wear2Scaffold(
showTimeText: Boolean,
title: String?,
subtitle: CharSequence?,
@@ -165,14 +98,14 @@ internal fun Scaffold(
start = titleHorizontalPadding,
top = 4.dp,
bottom = titleBottomPadding,
- end = titleHorizontalPadding
+ end = titleHorizontalPadding,
)
val subTitlePaddingValues =
PaddingValues(
start = subtitleHorizontalPadding,
top = 4.dp,
bottom = subtitleBottomPadding,
- end = subtitleHorizontalPadding
+ end = subtitleHorizontalPadding,
)
val initialCenterIndex = 0
val centerHeightDp = Dp(LocalConfiguration.current.screenHeightDp / 2.0f)
@@ -186,19 +119,13 @@ internal fun Scaffold(
}
WearPermissionTheme {
Scaffold(
- // TODO: Use a rotary modifier from Wear Compose once Wear Compose 1.4 is landed.
- // (b/325560444)
- modifier =
- Modifier.rotaryWithScroll(
- scrollableState = listState,
- focusRequester = focusRequester
- ),
+ modifier = Modifier.focusRequester(focusRequester),
timeText = {
if (showTimeText && !isLoading) {
TimeText(
modifier =
Modifier.scrollAway(listState, initialCenterIndex, scrollAwayOffset)
- .padding(top = timeTextTopPadding),
+ .padding(top = timeTextTopPadding)
)
}
},
@@ -208,12 +135,13 @@ internal fun Scaffold(
{ PositionIndicator(scalingLazyListState = listState) }
} else {
null
- }
+ },
) {
Box(modifier = Modifier.fillMaxSize()) {
if (isLoading) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
} else {
+ val iconColor = chipDefaultColors().iconColor(true).value
ScalingLazyColumn(
modifier = Modifier.fillMaxWidth(),
state = listState,
@@ -225,8 +153,8 @@ internal fun Scaffold(
start = scrollContentHorizontalPadding,
end = scrollContentHorizontalPadding,
top = scrollContentTopPadding,
- bottom = scrollContentBottomPadding
- )
+ bottom = scrollContentBottomPadding,
+ ),
) {
staticItem()
image?.let {
@@ -238,7 +166,8 @@ internal fun Scaffold(
painter = painterResource(id = image),
contentDescription = null,
contentScale = ContentScale.Crop,
- modifier = imageModifier
+ modifier = imageModifier,
+ colorFilter = ColorFilter.tint(iconColor),
)
}
is Drawable ->
@@ -247,7 +176,8 @@ internal fun Scaffold(
painter = rememberDrawablePainter(image),
contentDescription = null,
contentScale = ContentScale.Crop,
- modifier = imageModifier
+ modifier = imageModifier,
+ colorFilter = ColorFilter.tint(iconColor),
)
}
else -> {}
@@ -263,7 +193,7 @@ internal fun Scaffold(
Text(
text = title,
textAlign = TextAlign.Center,
- modifier = modifier
+ modifier = modifier,
)
}
}
@@ -282,6 +212,7 @@ internal fun Scaffold(
color = MaterialTheme.colors.onSurfaceVariant
),
modifier = modifier,
+ shouldCapitalize = true,
)
}
}
@@ -324,32 +255,3 @@ private fun RequestFocusOnResume(focusRequester: FocusRequester) {
}
}
}
-
-internal fun dismiss(activity: Activity) {
- if (activity is FragmentActivity) {
- if (!activity.supportFragmentManager.popBackStackImmediate()) {
- activity.finish()
- }
- } else {
- activity.finish()
- }
-}
-
-internal fun getBackStackEntryCount(activity: Activity): Int {
- return if (activity is FragmentActivity) {
- activity.supportFragmentManager.primaryNavigationFragment
- ?.childFragmentManager
- ?.backStackEntryCount ?: 0
- } else {
- 0
- }
-}
-
-internal fun Context.findActivity(): Activity {
- var context = this
- while (context is ContextWrapper) {
- if (context is Activity) return context
- context = context.baseContext
- }
- throw IllegalStateException("The screen should be called in the context of an Activity")
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnDefaults.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material2/layout/ScalingLazyColumnDefaults.kt
index 550f1dc24..f6fe5233c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnDefaults.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material2/layout/ScalingLazyColumnDefaults.kt
@@ -16,7 +16,7 @@
@file:Suppress("ObjectLiteralToLambda")
-package com.android.permissioncontroller.permission.ui.wear.elements.layout
+package com.android.permissioncontroller.wear.permission.components.material2.layout
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
@@ -33,7 +33,7 @@ import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults
import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType
import androidx.wear.compose.foundation.lazy.ScalingParams
import androidx.wear.compose.material.ChipDefaults
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState.RotaryMode
+import com.android.permissioncontroller.wear.permission.components.material2.layout.ScalingLazyColumnState.RotaryMode
import kotlin.math.sqrt
// This file's content is copied from ScalingLazyColumnDefaults.kt from Horologist (go/horologist),
@@ -63,10 +63,7 @@ object ScalingLazyColumnDefaults {
firstItemIsFullWidth: Boolean = true,
additionalPaddingAtBottom: Dp = 10.dp,
verticalArrangement: Arrangement.Vertical =
- Arrangement.spacedBy(
- space = 4.dp,
- alignment = Alignment.Top,
- ),
+ Arrangement.spacedBy(space = 4.dp, alignment = Alignment.Top),
horizontalPaddingPercent: Float = 0.052f,
rotaryMode: RotaryMode? = RotaryMode.Scroll,
hapticsEnabled: Boolean = true,
@@ -145,7 +142,7 @@ object ScalingLazyColumnDefaults {
return (radius -
sqrt(
(radius - childViewHeight + childViewWidth * 0.5f) *
- (radius - childViewWidth * 0.5f),
+ (radius - childViewWidth * 0.5f)
) -
childViewHeight * 0.5f)
.dp
@@ -225,10 +222,7 @@ object ScalingLazyColumnDefaults {
last.bottomPaddingDp * height + first.paddingCorrection
} else {
if (configuration.isScreenRound) {
- calculateVerticalOffsetForChip(
- screenWidthDp,
- horizontalPercent,
- ) + 10.dp
+ calculateVerticalOffsetForChip(screenWidthDp, horizontalPercent) + 10.dp
} else {
0.dp
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnState.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material2/layout/ScalingLazyColumnState.kt
index 0603647b1..c20b23aa3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnState.kt
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material2/layout/ScalingLazyColumnState.kt
@@ -17,7 +17,7 @@
@file:Suppress("ObjectLiteralToLambda")
@file:OptIn(ExperimentalWearFoundationApi::class)
-package com.android.permissioncontroller.permission.ui.wear.elements.layout
+package com.android.permissioncontroller.wear.permission.components.material2.layout
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.gestures.FlingBehavior
@@ -42,14 +42,8 @@ import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
import androidx.wear.compose.foundation.lazy.ScalingLazyListState
import androidx.wear.compose.foundation.lazy.ScalingParams
-import androidx.wear.compose.foundation.rememberActiveFocusRequester
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnDefaults.responsiveScalingParams
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState.RotaryMode
-import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rememberDisabledHaptic
-import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rememberRotaryHapticHandler
-import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rotaryWithScroll
-import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rotaryWithSnap
-import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.toRotaryScrollAdapter
+import com.android.permissioncontroller.wear.permission.components.material2.layout.ScalingLazyColumnDefaults.responsiveScalingParams
+import com.android.permissioncontroller.wear.permission.components.material2.layout.ScalingLazyColumnState.RotaryMode
// This file is a copy of ScalingLazyColumnState.kt from Horologist (go/horologist),
// remove it once after wear compose supports large screen dialogs.
@@ -61,10 +55,7 @@ import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.
class ScalingLazyColumnState(
val initialScrollPosition: ScrollPosition = ScrollPosition(1, 0),
val autoCentering: AutoCenteringParams? =
- AutoCenteringParams(
- initialScrollPosition.index,
- initialScrollPosition.offsetPx,
- ),
+ AutoCenteringParams(initialScrollPosition.index, initialScrollPosition.offsetPx),
val anchorType: ScalingLazyListAnchorType = ScalingLazyListAnchorType.ItemCenter,
val contentPadding: PaddingValues = PaddingValues(horizontal = 10.dp),
val rotaryMode: RotaryMode? = RotaryMode.Scroll,
@@ -120,10 +111,7 @@ class ScalingLazyColumnState(
data object Scroll : RotaryMode
}
- data class ScrollPosition(
- val index: Int,
- val offsetPx: Int,
- )
+ data class ScrollPosition(val index: Int, val offsetPx: Int)
fun interface Factory {
@Composable fun create(): ScalingLazyColumnState
@@ -133,7 +121,7 @@ class ScalingLazyColumnState(
// @Deprecated("Replaced by rememberResponsiveColumnState")
@Composable
fun rememberColumnState(
- factory: ScalingLazyColumnState.Factory = ScalingLazyColumnDefaults.responsive(),
+ factory: ScalingLazyColumnState.Factory = ScalingLazyColumnDefaults.responsive()
): ScalingLazyColumnState {
val columnState = factory.create()
@@ -150,10 +138,7 @@ fun rememberResponsiveColumnState(
last = ScalingLazyColumnDefaults.ItemType.Unspecified,
),
verticalArrangement: Arrangement.Vertical =
- Arrangement.spacedBy(
- space = 4.dp,
- alignment = Alignment.Top,
- ),
+ Arrangement.spacedBy(space = 4.dp, alignment = Alignment.Top),
rotaryMode: RotaryMode? = RotaryMode.Scroll,
hapticsEnabled: Boolean = true,
reverseLayout: Boolean = false,
@@ -173,10 +158,7 @@ fun rememberResponsiveColumnState(
val topScreenOffsetPx = screenHeightPx / 2 - topPaddingPx
val initialScrollPosition =
- ScalingLazyColumnState.ScrollPosition(
- index = 0,
- offsetPx = topScreenOffsetPx,
- )
+ ScalingLazyColumnState.ScrollPosition(index = 0, offsetPx = topScreenOffsetPx)
val columnState =
ScalingLazyColumnState(
@@ -204,36 +186,8 @@ fun ScalingLazyColumn(
modifier: Modifier = Modifier,
content: ScalingLazyListScope.() -> Unit,
) {
- val focusRequester = rememberActiveFocusRequester()
-
- val rotaryHaptics =
- if (columnState.hapticsEnabled) {
- rememberRotaryHapticHandler(columnState.state)
- } else {
- rememberDisabledHaptic()
- }
-
- val modifierWithRotary =
- when (columnState.rotaryMode) {
- RotaryMode.Snap ->
- modifier.rotaryWithSnap(
- focusRequester = focusRequester,
- rotaryScrollAdapter = columnState.state.toRotaryScrollAdapter(),
- reverseDirection = columnState.reverseLayout,
- rotaryHaptics = rotaryHaptics,
- )
- RotaryMode.Scroll ->
- modifier.rotaryWithScroll(
- focusRequester = focusRequester,
- scrollableState = columnState.state,
- reverseDirection = columnState.reverseLayout,
- rotaryHaptics = rotaryHaptics,
- )
- else -> modifier
- }
-
ScalingLazyColumn(
- modifier = modifierWithRotary.fillMaxSize(),
+ modifier = modifier.fillMaxSize(),
state = columnState.state,
contentPadding = columnState.contentPadding,
reverseLayout = columnState.reverseLayout,
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionButton.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionButton.kt
new file mode 100644
index 000000000..5f1c8dd2c
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionButton.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.material3
+
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.requiredSizeIn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.text.style.Hyphens
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.Button
+import androidx.wear.compose.material3.ButtonColors
+import androidx.wear.compose.material3.ButtonDefaults
+import androidx.wear.compose.material3.LocalTextConfiguration
+import androidx.wear.compose.material3.LocalTextStyle
+import androidx.wear.compose.material3.Text
+import com.android.permissioncontroller.wear.permission.components.material2.Chip
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
+
+/**
+ * This component is wrapper on material Button component
+ * 1. It takes icon, primary, secondary label resources and construct them applying permission app
+ * defaults
+ */
+@Composable
+fun WearPermissionButton(
+ label: String,
+ modifier: Modifier = Modifier,
+ materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
+ iconBuilder: WearPermissionIconBuilder? = null,
+ labelMaxLines: Int? = null,
+ secondaryLabel: String? = null,
+ secondaryLabelMaxLines: Int? = null,
+ onClick: () -> Unit,
+ enabled: Boolean = true,
+ style: WearPermissionButtonStyle = WearPermissionButtonStyle.Secondary,
+) {
+ if (materialUIVersion == WearPermissionMaterialUIVersion.MATERIAL2_5) {
+ Chip(
+ label = label,
+ labelMaxLines = labelMaxLines,
+ onClick = onClick,
+ modifier = modifier,
+ secondaryLabel = secondaryLabel,
+ secondaryLabelMaxLines = secondaryLabelMaxLines,
+ icon = iconBuilder?.let { { iconBuilder.build() } },
+ largeIcon = false,
+ colors = style.material2ChipColors(),
+ enabled = enabled,
+ )
+ } else {
+ WearPermissionButtonInternal(
+ iconBuilder = iconBuilder,
+ label = label,
+ labelMaxLines = labelMaxLines,
+ secondaryLabel = secondaryLabel,
+ secondaryLabelMaxLines = secondaryLabelMaxLines,
+ onClick = onClick,
+ modifier = modifier,
+ enabled = enabled,
+ colors = style.material3ButtonColors(),
+ )
+ }
+}
+
+@Composable
+internal fun WearPermissionButtonInternal(
+ modifier: Modifier = Modifier,
+ label: String? = null,
+ iconBuilder: WearPermissionIconBuilder? = null,
+ labelMaxLines: Int? = null,
+ secondaryLabel: String? = null,
+ secondaryLabelMaxLines: Int? = null,
+ onClick: () -> Unit,
+ enabled: Boolean = true,
+ colors: ButtonColors = ButtonDefaults.filledTonalButtonColors(),
+ contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
+ requiresMinimumHeight: Boolean = true,
+ shape: Shape = ButtonDefaults.shape,
+) {
+ val minHeight: Dp =
+ if (requiresMinimumHeight) {
+ 0.dp
+ } else {
+ 1.dp
+ }
+ val iconParam: (@Composable BoxScope.() -> Unit)? = iconBuilder?.let { { it.build() } }
+ val labelParam: (@Composable RowScope.() -> Unit)? =
+ label?.let {
+ {
+ Text(
+ text = label,
+ modifier = Modifier.fillMaxWidth(),
+ maxLines = labelMaxLines ?: LocalTextConfiguration.current.maxLines,
+ style = LocalTextStyle.current.copy(hyphens = Hyphens.Auto),
+ )
+ }
+ }
+
+ val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
+ secondaryLabel?.let {
+ {
+ Text(
+ text = secondaryLabel,
+ modifier = Modifier.fillMaxWidth(),
+ maxLines = secondaryLabelMaxLines ?: LocalTextConfiguration.current.maxLines,
+ )
+ }
+ }
+
+ Button(
+ icon = iconParam,
+ label = labelParam ?: {},
+ secondaryLabel = secondaryLabelParam,
+ enabled = enabled,
+ onClick = onClick,
+ modifier = modifier.requiredSizeIn(minHeight = minHeight).fillMaxWidth(),
+ contentPadding = contentPadding,
+ colors = colors,
+ shape = shape,
+ )
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionButtonStyle.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionButtonStyle.kt
new file mode 100644
index 000000000..114bcd4bc
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionButtonStyle.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.material3
+
+import androidx.compose.runtime.Composable
+import androidx.wear.compose.material.ChipColors
+import androidx.wear.compose.material.ChipDefaults
+import androidx.wear.compose.material3.ButtonColors
+import androidx.wear.compose.material3.ButtonDefaults
+import com.android.permissioncontroller.wear.permission.components.material2.chipDefaultColors
+import com.android.permissioncontroller.wear.permission.components.material2.chipDisabledColors
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButtonStyle.DisabledLike
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButtonStyle.Primary
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButtonStyle.Secondary
+import com.android.permissioncontroller.wear.permission.components.material3.WearPermissionButtonStyle.Transparent
+
+/**
+ * This component is wrapper on material control colors, It applies the right colors based material
+ * ui version.
+ */
+enum class WearPermissionButtonStyle {
+ Primary,
+ Secondary,
+ Transparent,
+ DisabledLike,
+}
+
+@Composable
+internal fun WearPermissionButtonStyle.material2ChipColors(): ChipColors {
+ return when (this) {
+ Primary -> chipDefaultColors()
+ Secondary -> ChipDefaults.secondaryChipColors()
+ Transparent -> ChipDefaults.childChipColors()
+ DisabledLike -> chipDisabledColors()
+ }
+}
+
+@Composable
+internal fun WearPermissionButtonStyle.material3ButtonColors(): ButtonColors {
+ return when (this) {
+ Primary -> ButtonDefaults.buttonColors()
+ Secondary -> ButtonDefaults.filledTonalButtonColors()
+ Transparent -> ButtonDefaults.childButtonColors()
+ DisabledLike -> ButtonDefaults.disabledLikeColors()
+ }
+}
+
+@Composable
+private fun ButtonDefaults.disabledLikeColors() =
+ filledTonalButtonColors().run {
+ ButtonColors(
+ containerPainter = disabledContainerPainter,
+ contentColor = disabledContentColor,
+ secondaryContentColor = disabledSecondaryContentColor,
+ iconColor = disabledIconColor,
+ disabledContainerPainter = disabledContainerPainter,
+ disabledContentColor = disabledContentColor,
+ disabledSecondaryContentColor = disabledSecondaryContentColor,
+ disabledIconColor = disabledIconColor,
+ )
+ }
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionConfirmationDialog.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionConfirmationDialog.kt
new file mode 100644
index 000000000..4ef6e8a72
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionConfirmationDialog.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.material3
+
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import androidx.wear.compose.material3.AlertDialog as Material3AlertDialog
+import androidx.wear.compose.material3.AlertDialogDefaults
+import androidx.wear.compose.material3.Text
+import com.android.permissioncontroller.wear.permission.components.material2.AlertDialog
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
+
+data class DialogButtonContent(
+ val icon: WearPermissionIconBuilder? = null,
+ val onClick: (() -> Unit),
+)
+
+@Composable
+fun WearPermissionConfirmationDialog(
+ materialUIVersion: WearPermissionMaterialUIVersion =
+ WearPermissionMaterialUIVersion.MATERIAL2_5,
+ show: Boolean,
+ iconRes: WearPermissionIconBuilder? = null,
+ title: String? = null,
+ message: String? = null,
+ positiveButtonContent: DialogButtonContent? = null,
+ negativeButtonContent: DialogButtonContent? = null,
+) {
+
+ if (materialUIVersion == WearPermissionMaterialUIVersion.MATERIAL3) {
+ if (
+ (positiveButtonContent == null && negativeButtonContent != null) ||
+ (positiveButtonContent != null && negativeButtonContent == null)
+ ) {
+ val edgeButtonContent = (positiveButtonContent ?: negativeButtonContent)!!
+ WearPermissionConfirmationDialogInternal(
+ show = show,
+ edgeButtonContent = edgeButtonContent,
+ iconRes = iconRes,
+ title = title,
+ message = message,
+ )
+ } else {
+ WearPermissionConfirmationDialogInternal(
+ show = show,
+ positiveButtonContent = positiveButtonContent,
+ negativeButtonContent = negativeButtonContent,
+ iconRes = iconRes,
+ title = title,
+ message = message,
+ )
+ }
+ } else {
+ AlertDialog(
+ title = title,
+ iconRes = iconRes,
+ message = message ?: "",
+ positiveButtonContent = positiveButtonContent,
+ negativeButtonContent = negativeButtonContent,
+ showDialog = show,
+ scalingLazyListState = rememberScalingLazyListState(),
+ )
+ }
+}
+
+@Composable
+private fun WearPermissionConfirmationDialogInternal(
+ show: Boolean,
+ edgeButtonContent: DialogButtonContent,
+ iconRes: WearPermissionIconBuilder?,
+ title: String?,
+ message: String?,
+) {
+ val edgeIcon: @Composable RowScope.() -> Unit =
+ edgeButtonContent.icon?.let {
+ { it.modifier(Modifier.size(36.dp).align(Alignment.CenterVertically)).build() }
+ } ?: AlertDialogDefaults.ConfirmIcon
+
+ Material3AlertDialog(
+ visible = show,
+ onDismissRequest = edgeButtonContent.onClick,
+ edgeButton = {
+ AlertDialogDefaults.EdgeButton(onClick = edgeButtonContent.onClick, content = edgeIcon)
+ },
+ icon = { iconRes?.build() },
+ title = title?.let { { Text(text = title) } } ?: {},
+ text = message?.let { { Text(text = message) } },
+ )
+}
+
+@Composable
+private fun WearPermissionConfirmationDialogInternal(
+ show: Boolean,
+ positiveButtonContent: DialogButtonContent?,
+ negativeButtonContent: DialogButtonContent?,
+ iconRes: WearPermissionIconBuilder?,
+ title: String?,
+ message: String?,
+) {
+ val positiveButton: (@Composable RowScope.() -> Unit)? =
+ positiveButtonContent?.let {
+ {
+ val positiveIcon: @Composable RowScope.() -> Unit =
+ positiveButtonContent.icon?.let {
+ {
+ it.modifier(Modifier.size(36.dp).align(Alignment.CenterVertically))
+ .build()
+ }
+ } ?: AlertDialogDefaults.ConfirmIcon
+
+ AlertDialogDefaults.ConfirmButton(
+ onClick = positiveButtonContent.onClick,
+ content = positiveIcon,
+ )
+ }
+ }
+
+ val negativeButton: (@Composable RowScope.() -> Unit)? =
+ negativeButtonContent?.let {
+ {
+ val negativeIcon: @Composable RowScope.() -> Unit =
+ negativeButtonContent.icon?.let {
+ {
+ it.modifier(Modifier.size(36.dp).align(Alignment.CenterVertically))
+ .build()
+ }
+ } ?: AlertDialogDefaults.DismissIcon
+
+ AlertDialogDefaults.DismissButton(
+ onClick = negativeButtonContent.onClick,
+ content = negativeIcon,
+ )
+ }
+ }
+
+ Material3AlertDialog(
+ visible = show,
+ onDismissRequest = negativeButtonContent?.onClick ?: {},
+ confirmButton = positiveButton ?: {},
+ dismissButton = negativeButton ?: {},
+ icon = { iconRes?.build() },
+ title = title?.let { { Text(text = title) } } ?: {},
+ text = message?.let { { Text(text = message) } },
+ )
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionIconBuilder.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionIconBuilder.kt
new file mode 100644
index 000000000..e5e51af66
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionIconBuilder.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.material3
+
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.wear.compose.material3.Icon
+import androidx.wear.compose.material3.IconButtonDefaults
+import com.android.permissioncontroller.wear.permission.components.rememberDrawablePainter
+
+/**
+ * This class simplifies the construction of icons with various attributes like resource type,
+ * content description, modifier, and tint. It supports different icon resource types, including:
+ * - ImageVector
+ * - Resource ID (Int)
+ * - Drawable
+ * - ImageBitmap
+ *
+ * Usage:
+ * ```
+ * val icon = WearPermissionIconBuilder.builder(IconResourceId)
+ * .contentDescription("Location Permission")
+ * .modifier(Modifier.size(24.dp))
+ * .tint(Color.Red)
+ * .build()
+ * ```
+ *
+ * Note: This builder uses a private constructor and is initialized through the `builder()`
+ * companion object method.
+ */
+class WearPermissionIconBuilder private constructor() {
+ var iconResource: Any? = null
+ private set
+
+ var contentDescription: String? = null
+ private set
+
+ var modifier: Modifier = Modifier.size(IconButtonDefaults.LargeIconSize)
+ private set
+
+ var tint: Color = Color.Unspecified
+ private set
+
+ fun contentDescription(description: String?): WearPermissionIconBuilder {
+ contentDescription = description
+ return this
+ }
+
+ fun modifier(modifier: Modifier): WearPermissionIconBuilder {
+ this.modifier = modifier then this.modifier
+ return this
+ }
+
+ fun tint(tint: Color): WearPermissionIconBuilder {
+ this.tint = tint
+ return this
+ }
+
+ @Composable
+ fun build() {
+ when (iconResource) {
+ is ImageVector -> Icon(iconResource as ImageVector, contentDescription, modifier, tint)
+ is Int ->
+ Icon(painterResource(id = iconResource as Int), contentDescription, modifier, tint)
+
+ is Drawable ->
+ Icon(
+ rememberDrawablePainter(iconResource as Drawable),
+ contentDescription,
+ modifier,
+ tint,
+ )
+
+ is ImageBitmap -> Icon(iconResource as ImageBitmap, contentDescription, modifier, tint)
+ else -> throw IllegalArgumentException("Type not supported.")
+ }
+ }
+
+ companion object {
+ fun builder(icon: Any) = WearPermissionIconBuilder().apply { iconResource = icon }
+ }
+}
+
+@Composable
+fun WearPermissionIconBuilder.Companion.defaultAlertConfirmIcon() =
+ builder(Icons.Default.Check).contentDescription((stringResource(android.R.string.ok)))
+
+@Composable
+fun WearPermissionIconBuilder.Companion.defaultAlertDismissIcon() =
+ builder(Icons.Default.Close).contentDescription((stringResource(android.R.string.cancel)))
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionListFooter.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionListFooter.kt
new file mode 100644
index 000000000..90ee09bab
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionListFooter.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.material3
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.ButtonDefaults
+import com.android.permissioncontroller.wear.permission.components.material2.ListFooter
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
+
+/** This component is creates a transparent styled button to use as a list footer. */
+@Composable
+fun WearPermissionListFooter(
+ materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
+ label: String,
+ iconBuilder: WearPermissionIconBuilder? = null,
+ onClick: (() -> Unit) = {},
+) {
+ if (materialUIVersion == WearPermissionMaterialUIVersion.MATERIAL2_5) {
+ ListFooter(
+ description = label,
+ iconRes = iconBuilder?.let { it.iconResource as Int },
+ onClick = onClick,
+ )
+ } else {
+ WearPermissionButtonInternal(
+ iconBuilder = iconBuilder,
+ secondaryLabel = label,
+ secondaryLabelMaxLines = Int.MAX_VALUE,
+ onClick = onClick,
+ contentPadding = PaddingValues(0.dp),
+ colors = ButtonDefaults.childButtonColors(),
+ requiresMinimumHeight = false,
+ shape = RectangleShape,
+ )
+ }
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionListSubHeader.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionListSubHeader.kt
new file mode 100644
index 000000000..2ed6e532f
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionListSubHeader.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.material3
+
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.ListSubHeader
+import com.android.permissioncontroller.wear.permission.components.material2.ListSubheader
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
+
+/*
+This component is simplified wrapper over ListSubHeader with quick padding adjustments
+ */
+@Composable
+fun WearPermissionListSubHeader(
+ wearPermissionMaterialUIVersion: WearPermissionMaterialUIVersion =
+ ResourceHelper.materialUIVersionInSettings,
+ isFirstItemInAList: Boolean,
+ label: @Composable RowScope.() -> Unit,
+) {
+ val screenWidth = LocalConfiguration.current.screenWidthDp
+ val screenHeight = LocalConfiguration.current.screenHeightDp
+ val subtitlePaddingDefaults =
+ WearPermissionScaffoldPaddingDefaults(
+ screenWidth = screenWidth,
+ screenHeight = screenHeight,
+ )
+ .subHeaderPaddingValues(needsLargePadding = !isFirstItemInAList)
+
+ if (wearPermissionMaterialUIVersion == WearPermissionMaterialUIVersion.MATERIAL3) {
+ ListSubHeader(
+ modifier = Modifier.requiredHeightIn(1.dp), // We do not want default min height
+ contentPadding = subtitlePaddingDefaults,
+ label = label,
+ )
+ } else {
+ ListSubheader(modifier = Modifier.padding(subtitlePaddingDefaults), label = label)
+ }
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionScaffold.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionScaffold.kt
new file mode 100644
index 000000000..296e45e79
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionScaffold.kt
@@ -0,0 +1,397 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.material3
+
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.ScrollableState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredHeightIn
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.Hyphens
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.ExpandableState
+import androidx.wear.compose.foundation.ScrollInfoProvider
+import androidx.wear.compose.foundation.expandableButton
+import androidx.wear.compose.foundation.expandableItems
+import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
+import androidx.wear.compose.foundation.lazy.ScalingLazyListState
+import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
+import androidx.wear.compose.foundation.lazy.TransformingLazyColumnScope
+import androidx.wear.compose.foundation.lazy.TransformingLazyColumnState
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
+import androidx.wear.compose.material3.AppScaffold
+import androidx.wear.compose.material3.CircularProgressIndicator
+import androidx.wear.compose.material3.IconButtonDefaults
+import androidx.wear.compose.material3.ListHeader
+import androidx.wear.compose.material3.MaterialTheme
+import androidx.wear.compose.material3.ScreenScaffold
+import androidx.wear.compose.material3.ScrollIndicator
+import androidx.wear.compose.material3.Text
+import androidx.wear.compose.material3.TimeText
+import androidx.wear.compose.material3.lazy.scrollTransform
+import com.android.permissioncontroller.wear.permission.components.AnnotatedText
+import com.android.permissioncontroller.wear.permission.components.ListScopeWrapper
+import com.android.permissioncontroller.wear.permission.components.material2.Wear2Scaffold
+import com.android.permissioncontroller.wear.permission.components.rememberDrawablePainter
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionTheme
+
+private class TransformingScopeConverter(private val scope: TransformingLazyColumnScope) :
+ ListScopeWrapper {
+ override fun item(key: Any?, contentType: Any?, content: @Composable () -> Unit) {
+ // TODO:https://buganizer.corp.google.com/issues/389093588.
+ scope.item { Box(modifier = Modifier.scrollTransform(this)) { content() } }
+ }
+
+ override fun items(
+ count: Int,
+ key: ((Int) -> Any)?,
+ contentType: (Int) -> Any?,
+ content: @Composable ((Int) -> Unit),
+ ) {
+ scope.items(count, key, contentType) { content(it) }
+ }
+
+ override fun expandableItems(
+ state: ExpandableState,
+ count: Int,
+ key: ((Int) -> Any)?,
+ itemContent: @Composable (BoxScope.(Int) -> Unit),
+ ) {
+ throw Exception("Expandable Items are not implemented on TLC Yet. Use SLC.")
+ }
+
+ override fun expandableButton(
+ state: ExpandableState,
+ key: Any?,
+ content: @Composable (() -> Unit),
+ ) {
+ throw Exception("Expandable Button is not implemented on TLC Yet. Use SLC.")
+ }
+}
+
+private class ScalingScopeConverter(private val scope: ScalingLazyListScope) : ListScopeWrapper {
+ override fun item(key: Any?, contentType: Any?, content: @Composable () -> Unit) {
+ scope.item { content() }
+ }
+
+ override fun items(
+ count: Int,
+ key: ((Int) -> Any)?,
+ contentType: (Int) -> Any?,
+ content: @Composable ((Int) -> Unit),
+ ) {
+ scope.items(count, key) { content(it) }
+ }
+
+ override fun expandableItems(
+ state: ExpandableState,
+ count: Int,
+ key: ((Int) -> Any)?,
+ itemContent: @Composable (BoxScope.(Int) -> Unit),
+ ) {
+ scope.expandableItems(state, count, key, itemContent)
+ }
+
+ override fun expandableButton(
+ state: ExpandableState,
+ key: Any?,
+ content: @Composable (() -> Unit),
+ ) {
+ scope.expandableButton(state, key, content)
+ }
+}
+
+/**
+ * This component is wrapper on material scaffold component. It helps with time text, scroll
+ * indicator and standard list elements like title, icon and subtitle.
+ */
+@Composable
+fun WearPermissionScaffold(
+ materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
+ asScalingList: Boolean = false,
+ showTimeText: Boolean,
+ title: String?,
+ subtitle: CharSequence?,
+ image: Any?,
+ isLoading: Boolean,
+ content: ListScopeWrapper.() -> Unit,
+ titleTestTag: String? = null,
+ subtitleTestTag: String? = null,
+) {
+
+ if (materialUIVersion == MATERIAL2_5) {
+ Wear2Scaffold(
+ showTimeText,
+ title,
+ subtitle,
+ image,
+ isLoading,
+ { content.invoke(ScalingScopeConverter(this)) },
+ titleTestTag,
+ subtitleTestTag,
+ )
+ } else {
+ WearPermissionScaffoldInternal(
+ asScalingList = asScalingList,
+ showTimeText = showTimeText,
+ title = title,
+ subtitle = subtitle,
+ image = image,
+ isLoading = isLoading,
+ content = content,
+ titleTestTag = titleTestTag,
+ subtitleTestTag = subtitleTestTag,
+ )
+ }
+}
+
+@Composable
+private fun WearPermissionScaffoldInternal(
+ asScalingList: Boolean = false,
+ showTimeText: Boolean,
+ title: String?,
+ subtitle: CharSequence?,
+ image: Any?,
+ isLoading: Boolean,
+ content: ListScopeWrapper.() -> Unit,
+ titleTestTag: String? = null,
+ subtitleTestTag: String? = null,
+) {
+
+ val scalingListState = rememberScalingLazyListState()
+ val transformingLazyColumnState = rememberTransformingLazyColumnState()
+ val listState = if (asScalingList) scalingListState else transformingLazyColumnState
+ val scrollInfoProvider =
+ if (asScalingList) ScrollInfoProvider(scalingListState)
+ else ScrollInfoProvider(transformingLazyColumnState)
+ val positionIndicator =
+ if (asScalingList) wearPermissionScrollIndicator(!isLoading, scalingListState)
+ else wearPermissionScrollIndicator(!isLoading, transformingLazyColumnState)
+
+ WearPermissionTheme(version = WearPermissionMaterialUIVersion.MATERIAL3) {
+ AppScaffold(timeText = wearPermissionTimeText(showTimeText && !isLoading)) {
+ ScreenScaffold(
+ scrollInfoProvider = scrollInfoProvider,
+ scrollIndicator = positionIndicator,
+ ) {
+ Box(modifier = Modifier.fillMaxSize()) {
+ if (isLoading) {
+ CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
+ } else {
+ LazyColumnView(
+ asScalingList = asScalingList,
+ showTimeText = showTimeText,
+ listState = listState,
+ title = title,
+ subtitle = subtitle,
+ image = image,
+ content = content,
+ titleTestTag = titleTestTag,
+ subtitleTestTag = subtitleTestTag,
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun BoxScope.LazyColumnView(
+ asScalingList: Boolean = false,
+ showTimeText: Boolean,
+ listState: ScrollableState,
+ title: String?,
+ subtitle: CharSequence?,
+ image: Any?,
+ content: ListScopeWrapper.() -> Unit,
+ titleTestTag: String? = null,
+ subtitleTestTag: String? = null,
+) {
+ val screenWidth = LocalConfiguration.current.screenWidthDp
+ val screenHeight = LocalConfiguration.current.screenHeightDp
+ val paddingDefaults =
+ WearPermissionScaffoldPaddingDefaults(
+ screenWidth = screenWidth,
+ screenHeight = screenHeight,
+ )
+ val painterImage = image?.let { painterFromImage(image = image) }
+ val scrollContentPadding =
+ if (showTimeText) {
+ paddingDefaults.scrollContentPadding
+ } else {
+ paddingDefaults.scrollContentPaddingForDialogs(painterImage == null)
+ }
+
+ fun BoxScope.scrollingViewContent(scopeWrapper: ListScopeWrapper) {
+ with(scopeWrapper) {
+ iconItem(
+ painter = painterImage,
+ modifier = Modifier.size(IconButtonDefaults.LargeIconSize),
+ )
+ titleItem(
+ text = title,
+ testTag = titleTestTag,
+ contentPaddingValues = paddingDefaults.titlePaddingValues(subtitle == null),
+ )
+ subtitleItem(
+ text = subtitle,
+ testTag = subtitleTestTag,
+ modifier =
+ Modifier.align(Alignment.Center).padding(paddingDefaults.subTitlePaddingValues),
+ )
+ content()
+ }
+ }
+
+ if (asScalingList) {
+ ScalingLazyColumn(
+ contentPadding = scrollContentPadding,
+ state = listState as ScalingLazyListState,
+ modifier = Modifier.background(MaterialTheme.colorScheme.background),
+ content = { scrollingViewContent(ScalingScopeConverter(this)) },
+ )
+ } else {
+ TransformingLazyColumn(
+ contentPadding = scrollContentPadding,
+ state = listState as TransformingLazyColumnState,
+ modifier = Modifier.background(MaterialTheme.colorScheme.background),
+ content = { scrollingViewContent(TransformingScopeConverter(this)) },
+ )
+ }
+}
+
+private fun wearPermissionTimeText(showTime: Boolean): @Composable () -> Unit {
+ return if (showTime) {
+ { TimeText() }
+ } else {
+ {}
+ }
+}
+
+private fun wearPermissionScrollIndicator(
+ showIndicator: Boolean,
+ columnState: TransformingLazyColumnState,
+): @Composable (BoxScope.() -> Unit)? {
+ return if (showIndicator) {
+ { ScrollIndicator(modifier = Modifier.align(Alignment.CenterEnd), state = columnState) }
+ } else {
+ null
+ }
+}
+
+private fun wearPermissionScrollIndicator(
+ showIndicator: Boolean,
+ columnState: ScalingLazyListState,
+): @Composable (BoxScope.() -> Unit)? {
+ return if (showIndicator) {
+ { ScrollIndicator(modifier = Modifier.align(Alignment.CenterEnd), state = columnState) }
+ } else {
+ null
+ }
+}
+
+@Composable
+private fun painterFromImage(image: Any?): Painter? {
+ return when (image) {
+ is Int -> painterResource(id = image)
+ is Drawable -> rememberDrawablePainter(image)
+ else -> null
+ }
+}
+
+private fun Modifier.optionalTestTag(tag: String?): Modifier {
+ if (tag == null) {
+ return this
+ }
+ return this then testTag(tag)
+}
+
+private fun ListScopeWrapper.iconItem(painter: Painter?, modifier: Modifier = Modifier) =
+ painter?.let {
+ item {
+ val iconColor = WearPermissionButtonStyle.Secondary.material3ButtonColors().iconColor
+ Image(
+ painter = it,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = modifier,
+ colorFilter = ColorFilter.tint(iconColor),
+ )
+ }
+ }
+
+private fun ListScopeWrapper.titleItem(
+ text: String?,
+ testTag: String?,
+ contentPaddingValues: PaddingValues,
+ modifier: Modifier = Modifier,
+) =
+ text?.let {
+ item(contentType = "header") {
+ ListHeader(
+ modifier = modifier.requiredHeightIn(1.dp), // We do not want default min height
+ contentPadding = contentPaddingValues,
+ ) {
+ Text(
+ text = it,
+ textAlign = TextAlign.Center,
+ modifier = Modifier.optionalTestTag(testTag),
+ style = MaterialTheme.typography.titleLarge.copy(hyphens = Hyphens.Auto),
+ )
+ }
+ }
+ }
+
+private fun ListScopeWrapper.subtitleItem(
+ text: CharSequence?,
+ testTag: String?,
+ modifier: Modifier = Modifier,
+) =
+ text?.let {
+ item {
+ AnnotatedText(
+ text = it,
+ style =
+ MaterialTheme.typography.bodyMedium.copy(
+ color = MaterialTheme.colorScheme.onSurfaceVariant
+ ),
+ modifier = modifier.optionalTestTag(testTag),
+ shouldCapitalize = true,
+ )
+ }
+ }
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionScaffoldPaddingDefaults.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionScaffoldPaddingDefaults.kt
new file mode 100644
index 000000000..595fb50a3
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionScaffoldPaddingDefaults.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.material3
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.unit.dp
+
+/* A common class for calculating padding for list items as per the latest design.
+https://www.figma.com/design/nb1atBKcK3luF8AXWLUe0X/BC25-Settings-on-Wear?node-id=2336-3304&t=n35PgTUC2O8hGSI0-0 */
+data class WearPermissionScaffoldPaddingDefaults(
+ private val screenWidth: Int,
+ private val screenHeight: Int,
+) {
+ private val scrollContentHorizontalPadding = (screenWidth * 0.052).dp
+ private val titleHorizontalPadding = (screenWidth * 0.0520).dp
+ private val subtitleHorizontalPadding = (screenWidth * 0.0624).dp
+ private val scrollContentTopPadding = (screenHeight * 0.1664).dp
+ private val dialogScrollContentLargeTopPadding = (screenHeight * 0.10).dp
+ private val dialogScrollContentTopPadding = (screenHeight * 0.012).dp
+ private val scrollContentBottomPadding = (screenHeight * 0.3646).dp
+ private val noPadding = 0.dp
+ private val defaultItemPadding = 4.dp
+ private val largeItemPadding = 8.dp
+ private val extraLargePadding = 12.dp
+
+ fun titlePaddingValues(needsLargePadding: Boolean): PaddingValues =
+ PaddingValues(
+ start = titleHorizontalPadding,
+ top = defaultItemPadding,
+ bottom = if (needsLargePadding) largeItemPadding else defaultItemPadding,
+ end = titleHorizontalPadding,
+ )
+
+ fun subHeaderPaddingValues(needsLargePadding: Boolean): PaddingValues =
+ PaddingValues(
+ start = subtitleHorizontalPadding,
+ top = if (needsLargePadding) extraLargePadding else noPadding,
+ bottom = largeItemPadding,
+ end = subtitleHorizontalPadding,
+ )
+
+ fun scrollContentPaddingForDialogs(needsLargePadding: Boolean) =
+ PaddingValues(
+ start = scrollContentHorizontalPadding,
+ end = scrollContentHorizontalPadding,
+ top =
+ if (needsLargePadding) {
+ dialogScrollContentLargeTopPadding
+ } else {
+ dialogScrollContentTopPadding
+ },
+ bottom = scrollContentBottomPadding,
+ )
+
+ val subTitlePaddingValues =
+ PaddingValues(
+ start = subtitleHorizontalPadding,
+ top = defaultItemPadding,
+ bottom = largeItemPadding,
+ end = subtitleHorizontalPadding,
+ )
+ val scrollContentPadding =
+ PaddingValues(
+ start = scrollContentHorizontalPadding,
+ end = scrollContentHorizontalPadding,
+ top = scrollContentTopPadding,
+ bottom = scrollContentBottomPadding,
+ )
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionToggleControl.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionToggleControl.kt
new file mode 100644
index 000000000..1dfb90682
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionToggleControl.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.material3
+
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
+import androidx.wear.compose.material3.CheckboxButton
+import androidx.wear.compose.material3.LocalTextConfiguration
+import androidx.wear.compose.material3.RadioButton
+import androidx.wear.compose.material3.SwitchButton
+import androidx.wear.compose.material3.Text
+import com.android.permissioncontroller.wear.permission.components.R
+import com.android.permissioncontroller.wear.permission.components.material2.ToggleChip
+import com.android.permissioncontroller.wear.permission.components.theme.ResourceHelper
+import com.android.permissioncontroller.wear.permission.components.theme.WearPermissionMaterialUIVersion
+
+/** Defines various toggle control types. */
+enum class WearPermissionToggleControlType {
+ Switch,
+ Radio,
+ Checkbox,
+}
+
+/**
+ * The custom component is a wrapper on different material3 toggle controls.
+ * 1. It provides an unified interface for RadioButton,CheckButton and SwitchButton.
+ * 2. It takes icon, primary, secondary label resources and construct them applying permission app
+ * defaults
+ * 3. Applies custom semantics for based on the toggle control type
+ */
+@Composable
+fun WearPermissionToggleControl(
+ toggleControl: WearPermissionToggleControlType,
+ label: String,
+ checked: Boolean,
+ onCheckedChanged: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+ labelMaxLines: Int? = null,
+ materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
+ iconBuilder: WearPermissionIconBuilder? = null,
+ secondaryLabel: String? = null,
+ secondaryLabelMaxLines: Int? = null,
+ enabled: Boolean = true,
+ style: WearPermissionToggleControlStyle = WearPermissionToggleControlStyle.Default,
+) {
+ if (materialUIVersion == WearPermissionMaterialUIVersion.MATERIAL2_5) {
+ ToggleChip(
+ toggleControl = toggleControl,
+ label = label,
+ labelMaxLine = labelMaxLines,
+ checked = checked,
+ onCheckedChanged = onCheckedChanged,
+ modifier = modifier,
+ icon = iconBuilder?.iconResource,
+ secondaryLabel = secondaryLabel,
+ secondaryLabelMaxLine = secondaryLabelMaxLines,
+ enabled = enabled,
+ colors = style.material2ToggleControlColors(),
+ )
+ } else {
+ WearPermissionToggleControlInternal(
+ label = label,
+ toggleControl = toggleControl,
+ checked = checked,
+ onCheckedChanged = onCheckedChanged,
+ modifier = modifier,
+ iconBuilder = iconBuilder,
+ labelMaxLines = labelMaxLines,
+ secondaryLabel = secondaryLabel,
+ secondaryLabelMaxLines = secondaryLabelMaxLines,
+ enabled = enabled,
+ style = style,
+ )
+ }
+}
+
+@Composable
+private fun WearPermissionToggleControlInternal(
+ label: String,
+ toggleControl: WearPermissionToggleControlType,
+ checked: Boolean,
+ onCheckedChanged: (Boolean) -> Unit,
+ modifier: Modifier = Modifier,
+ iconBuilder: WearPermissionIconBuilder? = null,
+ labelMaxLines: Int? = null,
+ secondaryLabel: String? = null,
+ secondaryLabelMaxLines: Int? = null,
+ enabled: Boolean = true,
+ style: WearPermissionToggleControlStyle = WearPermissionToggleControlStyle.Default,
+) {
+ val labelParam: (@Composable RowScope.() -> Unit) = {
+ Text(
+ text = label,
+ modifier = Modifier.fillMaxWidth(),
+ maxLines = labelMaxLines ?: LocalTextConfiguration.current.maxLines,
+ )
+ }
+
+ val secondaryLabelParam: (@Composable RowScope.() -> Unit)? =
+ secondaryLabel?.let {
+ {
+ Text(
+ text = it,
+ modifier = Modifier.fillMaxWidth(),
+ maxLines = secondaryLabelMaxLines ?: LocalTextConfiguration.current.maxLines,
+ )
+ }
+ }
+
+ val iconParam: (@Composable BoxScope.() -> Unit)? = iconBuilder?.let { { it.build() } }
+ val toggleControlStateDescription =
+ stringResource(
+ if (checked) {
+ R.string.on
+ } else {
+ R.string.off
+ }
+ )
+ val updatedModifier =
+ modifier.fillMaxWidth().semantics { stateDescription = toggleControlStateDescription }
+
+ when (toggleControl) {
+ WearPermissionToggleControlType.Radio ->
+ RadioButton(
+ selected = checked,
+ onSelect = {
+ // We do not want to call if it is already checked.
+ // Radio button can't be toggled off
+ if (!checked) {
+ onCheckedChanged(true)
+ }
+ },
+ modifier = updatedModifier,
+ enabled = enabled,
+ icon = iconParam,
+ secondaryLabel = secondaryLabelParam,
+ label = labelParam,
+ colors = style.radioButtonColorScheme(),
+ )
+
+ WearPermissionToggleControlType.Checkbox ->
+ CheckboxButton(
+ checked = checked,
+ onCheckedChange = onCheckedChanged,
+ modifier = updatedModifier,
+ enabled = enabled,
+ icon = iconParam,
+ secondaryLabel = secondaryLabelParam,
+ label = labelParam,
+ colors = style.checkboxColorScheme(),
+ )
+
+ WearPermissionToggleControlType.Switch ->
+ SwitchButton(
+ checked = checked,
+ onCheckedChange = onCheckedChanged,
+ modifier = updatedModifier,
+ enabled = enabled,
+ icon = iconParam,
+ secondaryLabel = secondaryLabelParam,
+ label = labelParam,
+ colors = style.switchButtonColorScheme(),
+ )
+ }
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionToggleControlStyle.kt b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionToggleControlStyle.kt
new file mode 100644
index 000000000..26a0ea0ef
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/material3/WearPermissionToggleControlStyle.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.material3
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
+import androidx.wear.compose.material.ToggleChipColors
+import androidx.wear.compose.material.ToggleChipDefaults.toggleChipColors
+import androidx.wear.compose.material3.CheckboxButtonColors
+import androidx.wear.compose.material3.CheckboxButtonDefaults.checkboxButtonColors
+import androidx.wear.compose.material3.RadioButtonColors
+import androidx.wear.compose.material3.RadioButtonDefaults.radioButtonColors
+import androidx.wear.compose.material3.SwitchButtonColors
+import androidx.wear.compose.material3.SwitchButtonDefaults.switchButtonColors
+import com.android.permissioncontroller.wear.permission.components.material2.toggleChipBackgroundColors
+import com.android.permissioncontroller.wear.permission.components.material2.toggleChipDisabledColors
+
+/**
+ * Defines toggle control styles, It helps in setting the right colors scheme to a toggle control.
+ */
+enum class WearPermissionToggleControlStyle {
+ Default,
+ Transparent,
+ DisabledLike,
+}
+
+@Composable
+internal fun WearPermissionToggleControlStyle.radioButtonColorScheme(): RadioButtonColors {
+ return when (this) {
+ WearPermissionToggleControlStyle.Default -> radioButtonColors()
+ WearPermissionToggleControlStyle.Transparent -> radioButtonTransparentColors()
+ WearPermissionToggleControlStyle.DisabledLike -> radioButtonDisabledLikeColors()
+ }
+}
+
+@Composable
+internal fun WearPermissionToggleControlStyle.checkboxColorScheme(): CheckboxButtonColors {
+ return when (this) {
+ WearPermissionToggleControlStyle.Default -> checkboxButtonColors()
+ WearPermissionToggleControlStyle.Transparent -> checkButtonTransparentColors()
+ WearPermissionToggleControlStyle.DisabledLike -> checkboxDisabledLikeColors()
+ }
+}
+
+@Composable
+internal fun WearPermissionToggleControlStyle.switchButtonColorScheme(): SwitchButtonColors {
+ return when (this) {
+ WearPermissionToggleControlStyle.Default -> switchButtonColors()
+ WearPermissionToggleControlStyle.Transparent -> switchButtonTransparentColors()
+ WearPermissionToggleControlStyle.DisabledLike -> switchButtonDisabledLikeColors()
+ }
+}
+
+@Composable
+internal fun WearPermissionToggleControlStyle.material2ToggleControlColors(): ToggleChipColors {
+ return when (this) {
+ WearPermissionToggleControlStyle.Default -> toggleChipColors()
+ WearPermissionToggleControlStyle.Transparent -> toggleChipBackgroundColors()
+ WearPermissionToggleControlStyle.DisabledLike -> toggleChipDisabledColors()
+ }
+}
+
+@Composable
+private fun checkButtonTransparentColors() =
+ checkboxButtonColors(
+ checkedContainerColor = Color.Transparent,
+ uncheckedContainerColor = Color.Transparent,
+ disabledCheckedContainerColor = Color.Transparent,
+ disabledUncheckedContainerColor = Color.Transparent,
+ )
+
+@Composable
+private fun radioButtonTransparentColors() =
+ radioButtonColors(
+ selectedContainerColor = Color.Transparent,
+ unselectedContainerColor = Color.Transparent,
+ disabledSelectedContainerColor = Color.Transparent,
+ disabledUnselectedContainerColor = Color.Transparent,
+ )
+
+@Composable
+private fun switchButtonTransparentColors() =
+ switchButtonColors(
+ checkedContainerColor = Color.Transparent,
+ uncheckedContainerColor = Color.Transparent,
+ disabledCheckedContainerColor = Color.Transparent,
+ disabledUncheckedContainerColor = Color.Transparent,
+ )
+
+@Composable
+private fun checkboxDisabledLikeColors(): CheckboxButtonColors {
+ val defaultColors = checkboxButtonColors()
+ return checkboxButtonColors(
+ checkedContainerColor = defaultColors.disabledCheckedContainerColor,
+ checkedContentColor = defaultColors.disabledCheckedContentColor,
+ checkedSecondaryContentColor = defaultColors.disabledCheckedSecondaryContentColor,
+ checkedIconColor = defaultColors.disabledCheckedIconColor,
+ checkedBoxColor = defaultColors.disabledCheckedBoxColor,
+ checkedCheckmarkColor = defaultColors.disabledCheckedCheckmarkColor,
+ uncheckedContainerColor = defaultColors.disabledUncheckedContainerColor,
+ uncheckedContentColor = defaultColors.disabledUncheckedContentColor,
+ uncheckedSecondaryContentColor = defaultColors.disabledUncheckedSecondaryContentColor,
+ uncheckedIconColor = defaultColors.disabledUncheckedIconColor,
+ uncheckedBoxColor = defaultColors.disabledUncheckedBoxColor,
+ )
+}
+
+@Composable
+private fun radioButtonDisabledLikeColors(): RadioButtonColors {
+ val defaultColors = radioButtonColors()
+ return radioButtonColors(
+ selectedContainerColor = defaultColors.disabledSelectedContainerColor,
+ selectedContentColor = defaultColors.disabledSelectedContentColor,
+ selectedSecondaryContentColor = defaultColors.disabledSelectedSecondaryContentColor,
+ selectedIconColor = defaultColors.disabledSelectedIconColor,
+ selectedControlColor = defaultColors.disabledSelectedControlColor,
+ unselectedContentColor = defaultColors.disabledUnselectedContentColor,
+ unselectedContainerColor = defaultColors.disabledUnselectedContainerColor,
+ unselectedSecondaryContentColor = defaultColors.disabledUnselectedSecondaryContentColor,
+ unselectedIconColor = defaultColors.disabledUnselectedIconColor,
+ unselectedControlColor = defaultColors.disabledUnselectedControlColor,
+ )
+}
+
+@Composable
+private fun switchButtonDisabledLikeColors(): SwitchButtonColors {
+ val defaultColors = switchButtonColors()
+ return switchButtonColors(
+ checkedContainerColor = defaultColors.disabledCheckedContainerColor,
+ checkedContentColor = defaultColors.disabledCheckedContentColor,
+ checkedSecondaryContentColor = defaultColors.disabledCheckedSecondaryContentColor,
+ checkedIconColor = defaultColors.disabledCheckedIconColor,
+ checkedThumbColor = defaultColors.disabledCheckedThumbColor,
+ checkedThumbIconColor = defaultColors.disabledCheckedThumbIconColor,
+ checkedTrackColor = defaultColors.disabledCheckedTrackColor,
+ checkedTrackBorderColor = defaultColors.disabledCheckedTrackBorderColor,
+ uncheckedContainerColor = defaultColors.disabledUncheckedContainerColor,
+ uncheckedContentColor = defaultColors.disabledUncheckedContentColor,
+ uncheckedSecondaryContentColor = defaultColors.disabledUncheckedSecondaryContentColor,
+ uncheckedIconColor = defaultColors.disabledUncheckedIconColor,
+ uncheckedThumbColor = defaultColors.disabledUncheckedThumbColor,
+ uncheckedTrackColor = defaultColors.checkedTrackColor.run { copy(alpha = alpha * 0.12f) },
+ uncheckedTrackBorderColor = defaultColors.disabledUncheckedTrackBorderColor,
+ )
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/theme/ResourceHelper.kt b/PermissionController/wear-permission-components/src/wear.permission.components/theme/ResourceHelper.kt
new file mode 100644
index 000000000..010d66a30
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/theme/ResourceHelper.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.theme
+
+import android.content.Context
+import android.os.SystemProperties
+import androidx.annotation.ColorRes
+import androidx.annotation.DimenRes
+import androidx.annotation.DoNotInline
+import androidx.annotation.StringRes
+import androidx.compose.ui.graphics.Color
+
+object ResourceHelper {
+
+ private const val MATERIAL3_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled"
+
+ /* This controls in app permission controller experience. */
+ private val material3Enabled: Boolean
+ get() {
+ return SystemProperties.getBoolean(MATERIAL3_ENABLED_SYSPROP, false)
+ }
+
+ val materialUIVersionInApp: WearPermissionMaterialUIVersion =
+ if (material3Enabled) {
+ WearPermissionMaterialUIVersion.MATERIAL3
+ } else {
+ WearPermissionMaterialUIVersion.MATERIAL2_5
+ }
+
+ /*
+ This is to control the permission controller screens in settings.
+ Currently it is set as false. We will either use the flag or a common property from settings
+ based on settings implementation when we are ready" */
+ private val material3EnabledInSettings: Boolean
+ get() {
+ return false
+ }
+
+ val materialUIVersionInSettings: WearPermissionMaterialUIVersion =
+ if (material3EnabledInSettings) {
+ WearPermissionMaterialUIVersion.MATERIAL3
+ } else {
+ WearPermissionMaterialUIVersion.MATERIAL2_5
+ }
+
+ @DoNotInline
+ fun getColor(context: Context, @ColorRes id: Int): Color? {
+ return try {
+ val colorInt = context.resources.getColor(id, context.theme)
+ Color(colorInt)
+ } catch (_: Exception) {
+ null
+ }
+ }
+
+ @DoNotInline
+ fun getString(context: Context, @StringRes id: Int): String? {
+ return try {
+ context.resources.getString(id)
+ } catch (_: Exception) {
+ null
+ }
+ }
+
+ @DoNotInline
+ fun getDimen(context: Context, @DimenRes id: Int): Float? {
+ return try {
+ context.resources.getDimension(id) / context.resources.displayMetrics.density
+ } catch (_: Exception) {
+ null
+ }
+ }
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3ColorScheme.kt b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3ColorScheme.kt
new file mode 100644
index 000000000..41e8fd975
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3ColorScheme.kt
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.theme
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.ColorRes
+import androidx.annotation.RequiresApi
+import androidx.compose.ui.graphics.Color
+import androidx.wear.compose.material.Colors
+import androidx.wear.compose.material3.ColorScheme
+
+/**
+ * Creates a dynamic color maps that can be overlaid. In wear we only support dark theme for the
+ * time being. If the device supports dynamic color generation these resources are updated with the
+ * generated colors
+ */
+internal object WearComposeMaterial3ColorScheme {
+
+ fun legacyColorScheme(): ColorScheme {
+ return Colors().run {
+ ColorScheme(
+ background = background,
+ onBackground = onBackground,
+ onPrimaryContainer = primary,
+ primaryDim = primaryVariant,
+ onPrimary = onPrimary,
+ tertiary = secondary, // Best-Fit Mapping: Refer WearMaterialBridgedTheme.
+ tertiaryDim = secondaryVariant,
+ onTertiary = onSecondary,
+ surfaceContainer = surface,
+ onSurface = onSurface,
+ onSurfaceVariant = onSurfaceVariant,
+ )
+ }
+ }
+
+ @RequiresApi(Build.VERSION_CODES.S)
+ fun tonalColorScheme(context: Context): ColorScheme {
+ val tonalPalette = dynamicTonalPalette(context)
+ return ColorScheme(
+ background = tonalPalette.neutral0,
+ onBackground = tonalPalette.neutral100,
+ onPrimary = tonalPalette.primary10,
+ onPrimaryContainer = tonalPalette.primary90,
+ onSecondary = tonalPalette.secondary10,
+ onSecondaryContainer = tonalPalette.secondary90,
+ onSurface = tonalPalette.neutral95,
+ onSurfaceVariant = tonalPalette.neutralVariant80,
+ onTertiary = tonalPalette.tertiary10,
+ onTertiaryContainer = tonalPalette.tertiary90,
+ outline = tonalPalette.neutralVariant60,
+ outlineVariant = tonalPalette.neutralVariant40,
+ primary = tonalPalette.primary90,
+ primaryContainer = tonalPalette.primary30,
+ primaryDim = tonalPalette.primary80,
+ secondary = tonalPalette.secondary90,
+ secondaryContainer = tonalPalette.secondary30,
+ secondaryDim = tonalPalette.secondary80,
+ surfaceContainer = tonalPalette.neutral20,
+ surfaceContainerHigh = tonalPalette.neutral30,
+ tertiary = tonalPalette.tertiary90,
+ tertiaryContainer = tonalPalette.tertiary30,
+ tertiaryDim = tonalPalette.tertiary80,
+ )
+ }
+
+ private fun Color.updatedColor(context: Context, @ColorRes colorRes: Int): Color {
+ return ResourceHelper.getColor(context, colorRes) ?: this
+ }
+
+ @RequiresApi(36)
+ fun dynamicColorScheme(context: Context): ColorScheme {
+ val defaultColorScheme = ColorScheme()
+ return ColorScheme(
+ primary =
+ defaultColorScheme.primary.updatedColor(
+ context,
+ android.R.color.system_primary_fixed,
+ ),
+ primaryDim =
+ defaultColorScheme.primaryDim.updatedColor(
+ context,
+ android.R.color.system_primary_fixed_dim,
+ ),
+ primaryContainer =
+ defaultColorScheme.primaryContainer.updatedColor(
+ context,
+ android.R.color.system_primary_container_dark,
+ ),
+ onPrimary =
+ defaultColorScheme.onPrimary.updatedColor(
+ context,
+ android.R.color.system_on_primary_fixed,
+ ),
+ onPrimaryContainer =
+ defaultColorScheme.onPrimaryContainer.updatedColor(
+ context,
+ android.R.color.system_on_primary_container_dark,
+ ),
+ secondary =
+ defaultColorScheme.secondary.updatedColor(
+ context,
+ android.R.color.system_secondary_fixed,
+ ),
+ secondaryDim =
+ defaultColorScheme.secondaryDim.updatedColor(
+ context,
+ android.R.color.system_secondary_fixed_dim,
+ ),
+ secondaryContainer =
+ defaultColorScheme.secondaryContainer.updatedColor(
+ context,
+ android.R.color.system_secondary_container_dark,
+ ),
+ onSecondary =
+ defaultColorScheme.onSecondary.updatedColor(
+ context,
+ android.R.color.system_on_secondary_fixed,
+ ),
+ onSecondaryContainer =
+ defaultColorScheme.onSecondaryContainer.updatedColor(
+ context,
+ android.R.color.system_on_secondary_container_dark,
+ ),
+ tertiary =
+ defaultColorScheme.tertiary.updatedColor(
+ context,
+ android.R.color.system_tertiary_fixed,
+ ),
+ tertiaryDim =
+ defaultColorScheme.tertiaryDim.updatedColor(
+ context,
+ android.R.color.system_tertiary_fixed_dim,
+ ),
+ tertiaryContainer =
+ defaultColorScheme.tertiaryContainer.updatedColor(
+ context,
+ android.R.color.system_tertiary_container_dark,
+ ),
+ onTertiary =
+ defaultColorScheme.onTertiary.updatedColor(
+ context,
+ android.R.color.system_on_tertiary_fixed,
+ ),
+ onTertiaryContainer =
+ defaultColorScheme.onTertiaryContainer.updatedColor(
+ context,
+ android.R.color.system_on_tertiary_container_dark,
+ ),
+ surfaceContainerLow =
+ defaultColorScheme.surfaceContainerLow.updatedColor(
+ context,
+ android.R.color.system_surface_container_low_dark,
+ ),
+ surfaceContainer =
+ defaultColorScheme.surfaceContainer.updatedColor(
+ context,
+ android.R.color.system_surface_container_dark,
+ ),
+ surfaceContainerHigh =
+ defaultColorScheme.surfaceContainerHigh.updatedColor(
+ context,
+ android.R.color.system_surface_container_high_dark,
+ ),
+ onSurface =
+ defaultColorScheme.onSurface.updatedColor(
+ context,
+ android.R.color.system_on_surface_dark,
+ ),
+ onSurfaceVariant =
+ defaultColorScheme.onSurfaceVariant.updatedColor(
+ context,
+ android.R.color.system_on_surface_variant_dark,
+ ),
+ outline =
+ defaultColorScheme.outline.updatedColor(
+ context,
+ android.R.color.system_outline_dark,
+ ),
+ outlineVariant =
+ defaultColorScheme.outlineVariant.updatedColor(
+ context,
+ android.R.color.system_outline_variant_dark,
+ ),
+ background =
+ defaultColorScheme.background.updatedColor(
+ context,
+ android.R.color.system_background_dark,
+ ),
+ onBackground =
+ defaultColorScheme.onBackground.updatedColor(
+ context,
+ android.R.color.system_on_background_dark,
+ ),
+ error =
+ defaultColorScheme.error.updatedColor(context, android.R.color.system_error_dark),
+ onError =
+ defaultColorScheme.onError.updatedColor(
+ context,
+ android.R.color.system_on_error_dark,
+ ),
+ errorContainer =
+ defaultColorScheme.errorContainer.updatedColor(
+ context,
+ android.R.color.system_error_container_dark,
+ ),
+ onErrorContainer =
+ defaultColorScheme.onErrorContainer.updatedColor(
+ context,
+ android.R.color.system_on_error_container_dark,
+ ),
+ )
+ }
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3Shapes.kt b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3Shapes.kt
new file mode 100644
index 000000000..89851d60e
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3Shapes.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.theme
+
+import android.content.Context
+import androidx.annotation.DimenRes
+import androidx.compose.foundation.shape.CornerBasedShape
+import androidx.compose.foundation.shape.CornerSize
+import androidx.compose.ui.unit.dp
+import androidx.wear.compose.material3.Shapes
+import com.android.permissioncontroller.wear.permission.components.R
+
+// TODO(b/324928718): Use system defined symbols.
+internal object WearComposeMaterial3Shapes {
+ private fun CornerBasedShape.updatedShape(
+ context: Context,
+ @DimenRes cornerSizeRes: Int,
+ ): CornerBasedShape {
+ val size = ResourceHelper.getDimen(context, cornerSizeRes)?.dp ?: return this
+ return copy(CornerSize(size))
+ }
+
+ fun dynamicShapes(context: Context): Shapes {
+ val defaultShapes = Shapes()
+ return Shapes(
+ extraLarge =
+ defaultShapes.extraLarge.updatedShape(
+ context,
+ R.dimen.wear_compose_material3_shape_corner_extra_large_size,
+ ),
+ large =
+ defaultShapes.large.updatedShape(
+ context,
+ R.dimen.wear_compose_material3_shape_corner_large_size,
+ ),
+ medium =
+ defaultShapes.medium.updatedShape(
+ context,
+ R.dimen.wear_compose_material3_shape_corner_medium_size,
+ ),
+ small =
+ defaultShapes.small.updatedShape(
+ context,
+ R.dimen.wear_compose_material3_shape_corner_small_size,
+ ),
+ extraSmall =
+ defaultShapes.extraSmall.updatedShape(
+ context,
+ R.dimen.wear_compose_material3_shape_corner_extra_small_size,
+ ),
+ )
+ }
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3TypeScaleTokens.kt b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3TypeScaleTokens.kt
new file mode 100644
index 000000000..df5057472
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3TypeScaleTokens.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.theme
+
+/*
+ * These values are retrieved from https://carbon.googleplex.com/wear-m3/pages and
+ * modified by UX.
+ * These values are internal to material3 library. We copied them to support flex font families
+ * Do not edit directly. Copy paste from compose material library.
+ */
+
+internal object WearComposeMaterial3TypeScaleTokens {
+ val ArcLargeRoundness = 100.0f
+ val ArcLargeWeight = 600.0f
+ val ArcLargeWidth = 100.0f
+
+ val ArcMediumRoundness = 100.0f
+ val ArcMediumWeight = 600.0f
+ val ArcMediumWidth = 90.0f
+
+ val ArcSmallRoundness = 100.0f
+ val ArcSmallWeight = 550.0f
+ val ArcSmallWidth = 90.0f
+
+ val BodyExtraSmallRoundness = 100.0f
+ val BodyExtraSmallWeight = 500.0f
+ val BodyExtraSmallWidth = 84.0f
+
+ val BodyLargeRoundness = 100.0f
+ val BodyLargeWeight = 450.0f
+ val BodyLargeWidth = 90.0f
+
+ val BodyMediumRoundness = 100.0f
+ val BodyMediumWeight = 450.0f
+ val BodyMediumWidth = 90.0f
+
+ val BodySmallRoundness = 100.0f
+ val BodySmallWeight = 500.0f
+ val BodySmallWidth = 86.0f
+
+ val DisplayLargeRoundness = 100.0f
+ val DisplayLargeWeight = 450.0f
+ val DisplayLargeWidth = 100.0f
+
+ val DisplayMediumRoundness = 100.0f
+ val DisplayMediumWeight = 500.0f
+ val DisplayMediumWidth = 100.0f
+
+ val DisplaySmallRoundness = 100.0f
+ val DisplaySmallWeight = 500.0f
+ val DisplaySmallWidth = 100.0f
+
+ val LabelLargeRoundness = 100.0f
+ val LabelLargeWeight = 500.0f
+ val LabelLargeWidth = 100.0f
+
+ val LabelMediumRoundness = 100.0f
+ val LabelMediumWeight = 500.0f
+ val LabelMediumWidth = 90.0f
+
+ val LabelSmallRoundness = 100.0f
+ val LabelSmallWeight = 500.0f
+ val LabelSmallWidth = 84.0f
+
+ val NumeralExtraLargeRoundness = 100.0f
+ val NumeralExtraLargeWeight = 550.0f
+ val NumeralExtraLargeWidth = 100.0f
+
+ val NumeralExtraSmallRoundness = 100.0f
+ val NumeralExtraSmallWeight = 550.0f
+ val NumeralExtraSmallWidth = 100.0f
+
+ val NumeralLargeRoundness = 100.0f
+ val NumeralLargeWeight = 600.0f
+ val NumeralLargeWidth = 100.0f
+
+ val NumeralMediumRoundness = 100.0f
+ val NumeralMediumWidth = 100.0f
+ val NumeralMediumWeight = 600.0f
+
+ val NumeralSmallRoundness = 100.0f
+ val NumeralSmallWeight = 600.0f
+ val NumeralSmallWidth = 100.0f
+
+ val TitleLargeRoundness = 100.0f
+ val TitleLargeWeight = 500.0f
+ val TitleLargeWidth = 100.0f
+
+ val TitleMediumRoundness = 100.0f
+ val TitleMediumWeight = 550.0f
+ val TitleMediumWidth = 100.0f
+
+ val TitleSmallRoundness = 100.0f
+ val TitleSmallWeight = 550.0f
+ val TitleSmallWidth = 100.0f
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3Typography.kt b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3Typography.kt
new file mode 100644
index 000000000..e44a24981
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3Typography.kt
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.theme
+
+import android.content.Context
+import androidx.annotation.DimenRes
+import androidx.annotation.StringRes
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.DeviceFontFamilyName
+import androidx.compose.ui.text.font.Font
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontVariation
+import androidx.compose.ui.unit.sp
+import androidx.wear.compose.material3.Typography
+import com.android.permissioncontroller.wear.permission.components.R
+
+internal object WearComposeMaterial3Typography {
+
+ private const val DEVICE_DEFAULT_FLEX_FONT_TYPE = "font-family-flex-device-default"
+
+ fun fontFamily(
+ context: Context,
+ @StringRes id: Int,
+ variationSettings: FontVariation.Settings? = null,
+ ): FontFamily {
+ val typefaceName = ResourceHelper.getString(context, id) ?: DEVICE_DEFAULT_FLEX_FONT_TYPE
+
+ val font =
+ if (variationSettings != null) {
+ Font(
+ familyName = DeviceFontFamilyName(typefaceName),
+ variationSettings = variationSettings,
+ )
+ } else {
+ Font(familyName = DeviceFontFamilyName(typefaceName))
+ }
+ return FontFamily(font)
+ }
+
+ private fun TextStyle.updatedTextStyle(
+ context: Context,
+ @StringRes fontRes: Int,
+ variationSettings: FontVariation.Settings? = null,
+ @DimenRes fontSizeRes: Int,
+ ): TextStyle {
+
+ val fontFamily =
+ fontFamily(context = context, id = fontRes, variationSettings = variationSettings)
+ val fontSize = ResourceHelper.getDimen(context = context, id = fontSizeRes)?.sp ?: fontSize
+
+ return copy(fontFamily = fontFamily, fontSize = fontSize)
+ }
+
+ fun dynamicTypography(context: Context): Typography {
+ val defaultTypography = Typography()
+ return Typography(
+ arcLarge =
+ defaultTypography.arcLarge.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_arc_large_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_arc_large_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.ArcLargeVariationSettings,
+ ),
+ arcMedium =
+ defaultTypography.arcMedium.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_arc_medium_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_arc_medium_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.ArcMediumVariationSettings,
+ ),
+ arcSmall =
+ defaultTypography.arcSmall.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_arc_small_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_arc_small_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.ArcSmallVariationSettings,
+ ),
+ bodyLarge =
+ defaultTypography.bodyLarge.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_body_large_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_body_large_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.BodyLargeVariationSettings,
+ ),
+ bodyMedium =
+ defaultTypography.bodyMedium.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_body_medium_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_body_medium_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.BodyMediumVariationSettings,
+ ),
+ bodySmall =
+ defaultTypography.bodySmall.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_body_small_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_body_small_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.BodySmallVariationSettings,
+ ),
+ bodyExtraSmall =
+ defaultTypography.bodyExtraSmall.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_body_extra_small_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_body_extra_small_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.BodyExtraSmallVariationSettings,
+ ),
+ displayLarge =
+ defaultTypography.displayLarge.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_display_large_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_display_large_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.DisplayLargeVariationSettings,
+ ),
+ displayMedium =
+ defaultTypography.displayMedium.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_display_medium_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_display_medium_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.DisplayMediumVariationSettings,
+ ),
+ displaySmall =
+ defaultTypography.displaySmall.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_display_small_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_display_small_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.DisplaySmallVariationSettings,
+ ),
+ labelLarge =
+ defaultTypography.labelLarge.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_label_large_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_label_large_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.LabelLargeVariationSettings,
+ ),
+ labelMedium =
+ defaultTypography.labelMedium.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_label_medium_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_label_medium_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.LabelMediumVariationSettings,
+ ),
+ labelSmall =
+ defaultTypography.labelSmall.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_label_small_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_label_small_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.LabelSmallVariationSettings,
+ ),
+ numeralExtraLarge =
+ defaultTypography.numeralExtraLarge.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_numeral_extra_large_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_numeral_extra_large_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.NumeralExtraLargeVariationSettings,
+ ),
+ numeralLarge =
+ defaultTypography.numeralLarge.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_numeral_large_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_numeral_large_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.NumeralLargeVariationSettings,
+ ),
+ numeralMedium =
+ defaultTypography.numeralMedium.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_numeral_medium_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_numeral_medium_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.NumeralMediumVariationSettings,
+ ),
+ numeralSmall =
+ defaultTypography.numeralSmall.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_numeral_small_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_numeral_small_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.NumeralSmallVariationSettings,
+ ),
+ numeralExtraSmall =
+ defaultTypography.numeralExtraSmall.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_numeral_extra_small_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_numeral_extra_small_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.NumeralExtraSmallVariationSettings,
+ ),
+ titleLarge =
+ defaultTypography.titleLarge.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_title_large_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_title_large_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.TitleLargeVariationSettings,
+ ),
+ titleMedium =
+ defaultTypography.titleMedium.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_title_medium_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_title_medium_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.TitleMediumVariationSettings,
+ ),
+ titleSmall =
+ defaultTypography.titleSmall.updatedTextStyle(
+ context = context,
+ fontRes = R.string.wear_compose_material3_title_small_font_family,
+ fontSizeRes = R.dimen.wear_compose_material3_title_small_font_size,
+ variationSettings =
+ WearComposeMaterial3VariableFontTokens.TitleSmallVariationSettings,
+ ),
+ )
+ }
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3VariableFontTokens.kt b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3VariableFontTokens.kt
new file mode 100644
index 000000000..644a1b754
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearComposeMaterial3VariableFontTokens.kt
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.theme
+
+import androidx.compose.ui.text.font.FontVariation
+
+internal object WearComposeMaterial3VariableFontTokens {
+ val ArcLargeVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.ArcLargeRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.ArcLargeWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.ArcLargeWeight),
+ )
+ val ArcMediumVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.ArcMediumRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.ArcMediumWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.ArcMediumWeight),
+ )
+ val ArcSmallVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.ArcSmallRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.ArcSmallWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.ArcSmallWeight),
+ )
+ val BodyExtraSmallVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting(
+ "ROND",
+ WearComposeMaterial3TypeScaleTokens.BodyExtraSmallRoundness,
+ ),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.BodyExtraSmallWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.BodyExtraSmallWeight),
+ )
+ val BodyLargeVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.BodyLargeRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.BodyLargeWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.BodyLargeWeight),
+ )
+ val BodyMediumVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.BodyMediumRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.BodyMediumWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.BodyMediumWeight),
+ )
+ val BodySmallVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.BodySmallRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.BodySmallWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.BodySmallWeight),
+ )
+ val DisplayLargeVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting(
+ "ROND",
+ WearComposeMaterial3TypeScaleTokens.DisplayLargeRoundness,
+ ),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.DisplayLargeWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.DisplayLargeWeight),
+ )
+ val DisplayMediumVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting(
+ "ROND",
+ WearComposeMaterial3TypeScaleTokens.DisplayMediumRoundness,
+ ),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.DisplayMediumWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.DisplayMediumWeight),
+ )
+ val DisplaySmallVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting(
+ "ROND",
+ WearComposeMaterial3TypeScaleTokens.DisplaySmallRoundness,
+ ),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.DisplaySmallWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.DisplaySmallWeight),
+ )
+ val LabelLargeVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.LabelLargeRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.LabelLargeWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.LabelLargeWeight),
+ )
+ val LabelMediumVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.LabelMediumRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.LabelMediumWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.LabelMediumWeight),
+ )
+ val LabelSmallVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.LabelSmallRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.LabelSmallWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.LabelSmallWeight),
+ )
+ val NumeralExtraLargeVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting(
+ "ROND",
+ WearComposeMaterial3TypeScaleTokens.NumeralExtraLargeRoundness,
+ ),
+ FontVariation.Setting(
+ "wdth",
+ WearComposeMaterial3TypeScaleTokens.NumeralExtraLargeWidth,
+ ),
+ FontVariation.Setting(
+ "wght",
+ WearComposeMaterial3TypeScaleTokens.NumeralExtraLargeWeight,
+ ),
+ )
+ val NumeralExtraSmallVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting(
+ "ROND",
+ WearComposeMaterial3TypeScaleTokens.NumeralExtraSmallRoundness,
+ ),
+ FontVariation.Setting(
+ "wdth",
+ WearComposeMaterial3TypeScaleTokens.NumeralExtraSmallWidth,
+ ),
+ FontVariation.Setting(
+ "wght",
+ WearComposeMaterial3TypeScaleTokens.NumeralExtraSmallWeight,
+ ),
+ )
+ val NumeralLargeVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting(
+ "ROND",
+ WearComposeMaterial3TypeScaleTokens.NumeralLargeRoundness,
+ ),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.NumeralLargeWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.NumeralLargeWeight),
+ )
+ val NumeralMediumVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting(
+ "ROND",
+ WearComposeMaterial3TypeScaleTokens.NumeralMediumRoundness,
+ ),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.NumeralMediumWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.NumeralMediumWeight),
+ )
+ val NumeralSmallVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting(
+ "ROND",
+ WearComposeMaterial3TypeScaleTokens.NumeralSmallRoundness,
+ ),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.NumeralSmallWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.NumeralSmallWeight),
+ )
+ val TitleLargeVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.TitleLargeRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.TitleLargeWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.TitleLargeWeight),
+ )
+ val TitleMediumVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.TitleMediumRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.TitleMediumWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.TitleMediumWeight),
+ )
+ val TitleSmallVariationSettings =
+ FontVariation.Settings(
+ FontVariation.Setting("ROND", WearComposeMaterial3TypeScaleTokens.TitleSmallRoundness),
+ FontVariation.Setting("wdth", WearComposeMaterial3TypeScaleTokens.TitleSmallWidth),
+ FontVariation.Setting("wght", WearComposeMaterial3TypeScaleTokens.TitleSmallWeight),
+ )
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearMaterialBridgedLegacyTheme.kt b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearMaterialBridgedLegacyTheme.kt
new file mode 100644
index 000000000..51ae16477
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearMaterialBridgedLegacyTheme.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.theme
+
+import androidx.compose.ui.unit.sp
+import androidx.wear.compose.material.Colors
+import androidx.wear.compose.material.Shapes
+import androidx.wear.compose.material.Typography
+
+/**
+ * This exists to support Permission Controller screens that may still use Material 2.5 components
+ * to maintain consistency with the settings screens.
+ *
+ * However to avoid maintaining two sets of resources for overlays, this class construct 2.5 theme
+ * from 3.0
+ */
+internal class WearMaterialBridgedLegacyTheme
+private constructor(newTheme: WearOverlayableMaterial3Theme) {
+ val colors =
+ newTheme.colorScheme.run {
+ Colors(
+ background = background,
+ onBackground = onBackground,
+ primary = onPrimaryContainer, // primary90
+ primaryVariant = primaryDim, // primary80
+ onPrimary = onPrimary, // primary10
+ secondary = tertiary, // Tertiary90
+ secondaryVariant = tertiaryDim, // Tertiary60 - Tertiary80 BestFit.
+ onSecondary = onTertiary, // Tertiary10
+ surface = surfaceContainer, // neutral20
+ onSurface = onSurface, // neutral95
+ onSurfaceVariant = onSurfaceVariant, // neutralVariant80
+ )
+ }
+
+ // Based on:
+ // Material 2:
+ // wear/compose/compose-material/src/main/java/androidx/wear/compose/material/Typography.kt
+ // Material 3:
+ // wear/compose/compose-material3/src/main/java/androidx/wear/compose/material3/tokens/TypeScaleTokens.kt
+ val typography =
+ newTheme.typography.run {
+ Typography(
+ display1 = displayLarge, // 40.sp
+ display2 = displayMedium.copy(fontSize = 34.sp, lineHeight = 40.sp),
+ display3 = displayMedium, // 30.sp
+ title1 = displaySmall, // 24.sp
+ title2 = titleLarge, // 20.sp
+ title3 = titleMedium, // 16.sp
+ body1 = bodyLarge, // 16.sp
+ body2 = bodyMedium, // 14.sp
+ caption1 = bodyMedium, // 14.sp
+ caption2 = bodySmall, // 12.sp
+ caption3 = bodyExtraSmall, // 10.sp
+ button = labelMedium, // 15.sp
+ )
+ }
+
+ val shapes = newTheme.shapes.run { Shapes(large = large, medium = medium, small = small) }
+
+ companion object {
+ fun createFrom(newTheme: WearOverlayableMaterial3Theme) =
+ WearMaterialBridgedLegacyTheme(newTheme)
+ }
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearOverlayableMaterial3Theme.kt b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearOverlayableMaterial3Theme.kt
new file mode 100644
index 000000000..f6bf56f47
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearOverlayableMaterial3Theme.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.theme
+
+import android.content.Context
+import android.os.Build
+
+/**
+ * Theme wrapper providing Material 3 styling while maintaining compatibility with Runtime Resource
+ * Overlay (RRO).
+ *
+ * Uses the tonal palette from the previous Material Design version until dynamic color tokens are
+ * available in SDK 36.
+ */
+internal class WearOverlayableMaterial3Theme(context: Context) {
+ val colorScheme =
+ when {
+ Build.VERSION.SDK_INT >= 36 -> {
+ WearComposeMaterial3ColorScheme.dynamicColorScheme(context)
+ }
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+ WearComposeMaterial3ColorScheme.tonalColorScheme(context)
+ }
+ else -> {
+ WearComposeMaterial3ColorScheme.legacyColorScheme()
+ }
+ }
+
+ val typography = WearComposeMaterial3Typography.dynamicTypography(context)
+
+ val shapes = WearComposeMaterial3Shapes.dynamicShapes(context)
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearPermissionTheme.kt b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearPermissionTheme.kt
new file mode 100644
index 000000000..b1c87108c
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearPermissionTheme.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.theme
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material3.MaterialTheme as Material3Theme
+
+/** This enum is used to specify the material version used for a specific screen */
+enum class WearPermissionMaterialUIVersion {
+ MATERIAL2_5,
+ MATERIAL3,
+}
+
+/** An overlay-able compose theme supporting both material2.5 and 3. */
+@Composable
+fun WearPermissionTheme(
+ version: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
+ content: @Composable () -> Unit,
+) {
+ WearOverlayableMaterial3Theme(LocalContext.current).run {
+ when (version) {
+ WearPermissionMaterialUIVersion.MATERIAL3 ->
+ Material3Theme(
+ colorScheme = colorScheme,
+ typography = typography,
+ shapes = shapes,
+ content = content,
+ )
+ // Material2_5 UI controls are still being used in the screen,
+ // To avoid having two set of overlay resources, we will use material3 overlay resources
+ // to
+ // support material2_5 UI controls as well.
+ WearPermissionMaterialUIVersion.MATERIAL2_5 ->
+ WearMaterialBridgedLegacyTheme.createFrom(this).run {
+ MaterialTheme(
+ colors = colors,
+ typography = typography,
+ shapes = shapes,
+ content = content,
+ )
+ }
+ }
+ }
+}
diff --git a/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearPermissionTonalPalette.kt b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearPermissionTonalPalette.kt
new file mode 100644
index 000000000..72aa3e487
--- /dev/null
+++ b/PermissionController/wear-permission-components/src/wear.permission.components/theme/WearPermissionTonalPalette.kt
@@ -0,0 +1,216 @@
+@file:Suppress("unused")
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.wear.permission.components.theme
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.ColorRes
+import androidx.annotation.DoNotInline
+import androidx.annotation.RequiresApi
+import androidx.compose.ui.graphics.Color
+
+/**
+ * Tonal Palette structure in Material.
+ *
+ * A tonal palette is comprised of 5 tonal ranges. Each tonal range includes the 13 stops, or tonal
+ * swatches.
+ *
+ * Tonal range names are:
+ * - Neutral (N)
+ * - Neutral variant (NV)
+ * - Primary (P)
+ * - Secondary (S)
+ * - Tertiary (T)
+ */
+internal class WearPermissionTonalPalette(
+ // The neutral tonal range.
+ val neutral100: Color,
+ val neutral99: Color,
+ val neutral95: Color,
+ val neutral90: Color,
+ val neutral80: Color,
+ val neutral70: Color,
+ val neutral60: Color,
+ val neutral50: Color,
+ val neutral40: Color,
+ val neutral30: Color,
+ val neutral20: Color,
+ val neutral10: Color,
+ val neutral0: Color,
+
+ // The neutral variant tonal range, sometimes called "neutral 2"
+ val neutralVariant100: Color,
+ val neutralVariant99: Color,
+ val neutralVariant95: Color,
+ val neutralVariant90: Color,
+ val neutralVariant80: Color,
+ val neutralVariant70: Color,
+ val neutralVariant60: Color,
+ val neutralVariant50: Color,
+ val neutralVariant40: Color,
+ val neutralVariant30: Color,
+ val neutralVariant20: Color,
+ val neutralVariant10: Color,
+ val neutralVariant0: Color,
+
+ // The primary tonal range, also known as accent 1
+ val primary100: Color,
+ val primary99: Color,
+ val primary95: Color,
+ val primary90: Color,
+ val primary80: Color,
+ val primary70: Color,
+ val primary60: Color,
+ val primary50: Color,
+ val primary40: Color,
+ val primary30: Color,
+ val primary20: Color,
+ val primary10: Color,
+ val primary0: Color,
+
+ // The Secondary tonal range, also know as accent 2
+ val secondary100: Color,
+ val secondary99: Color,
+ val secondary95: Color,
+ val secondary90: Color,
+ val secondary80: Color,
+ val secondary70: Color,
+ val secondary60: Color,
+ val secondary50: Color,
+ val secondary40: Color,
+ val secondary30: Color,
+ val secondary20: Color,
+ val secondary10: Color,
+ val secondary0: Color,
+
+ // The tertiary tonal range, also known as accent 3
+ val tertiary100: Color,
+ val tertiary99: Color,
+ val tertiary95: Color,
+ val tertiary90: Color,
+ val tertiary80: Color,
+ val tertiary70: Color,
+ val tertiary60: Color,
+ val tertiary50: Color,
+ val tertiary40: Color,
+ val tertiary30: Color,
+ val tertiary20: Color,
+ val tertiary10: Color,
+ val tertiary0: Color,
+)
+
+/** Dynamic colors for wear compose material to support resource overlay. */
+@RequiresApi(Build.VERSION_CODES.S)
+internal fun dynamicTonalPalette(context: Context) =
+ WearPermissionTonalPalette(
+ // The neutral tonal range from the generated dynamic color palette.
+ neutral100 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_0),
+ neutral99 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_10),
+ neutral95 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_50),
+ neutral90 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_100),
+ neutral80 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_200),
+ neutral70 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_300),
+ neutral60 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_400),
+ neutral50 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_500),
+ neutral40 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_600),
+ neutral30 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_700),
+ neutral20 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_800),
+ neutral10 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_900),
+ neutral0 = ColorResourceHelper.getColor(context, android.R.color.system_neutral1_1000),
+
+ // The neutral variant tonal range, sometimes called "neutral 2", from the
+ // generated dynamic color palette.
+ neutralVariant100 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_0),
+ neutralVariant99 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_10),
+ neutralVariant95 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_50),
+ neutralVariant90 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_100),
+ neutralVariant80 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_200),
+ neutralVariant70 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_300),
+ neutralVariant60 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_400),
+ neutralVariant50 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_500),
+ neutralVariant40 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_600),
+ neutralVariant30 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_700),
+ neutralVariant20 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_800),
+ neutralVariant10 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_900),
+ neutralVariant0 =
+ ColorResourceHelper.getColor(context, android.R.color.system_neutral2_1000),
+
+ // The primary tonal range from the generated dynamic color palette.
+ primary100 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_0),
+ primary99 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_10),
+ primary95 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_50),
+ primary90 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_100),
+ primary80 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_200),
+ primary70 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_300),
+ primary60 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_400),
+ primary50 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_500),
+ primary40 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_600),
+ primary30 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_700),
+ primary20 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_800),
+ primary10 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_900),
+ primary0 = ColorResourceHelper.getColor(context, android.R.color.system_accent1_1000),
+
+ // The secondary tonal range from the generated dynamic color palette.
+ secondary100 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_0),
+ secondary99 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_10),
+ secondary95 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_50),
+ secondary90 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_100),
+ secondary80 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_200),
+ secondary70 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_300),
+ secondary60 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_400),
+ secondary50 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_500),
+ secondary40 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_600),
+ secondary30 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_700),
+ secondary20 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_800),
+ secondary10 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_900),
+ secondary0 = ColorResourceHelper.getColor(context, android.R.color.system_accent2_1000),
+
+ // The tertiary tonal range from the generated dynamic color palette.
+ tertiary100 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_0),
+ tertiary99 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_10),
+ tertiary95 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_50),
+ tertiary90 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_100),
+ tertiary80 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_200),
+ tertiary70 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_300),
+ tertiary60 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_400),
+ tertiary50 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_500),
+ tertiary40 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_600),
+ tertiary30 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_700),
+ tertiary20 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_800),
+ tertiary10 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_900),
+ tertiary0 = ColorResourceHelper.getColor(context, android.R.color.system_accent3_1000),
+ )
+
+private object ColorResourceHelper {
+ @DoNotInline
+ fun getColor(context: Context, @ColorRes id: Int): Color {
+ return Color(context.resources.getColor(id, context.theme))
+ }
+}
diff --git a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigOverlayTest.kt b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigOverlayTest.kt
index ef9ad4e1e..d16b76846 100644
--- a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigOverlayTest.kt
+++ b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigOverlayTest.kt
@@ -17,6 +17,7 @@
package com.android.safetycenter.config
import android.content.Context
+import android.os.UserHandle
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compatibility.common.util.SystemUtil.runShellCommand
@@ -124,9 +125,12 @@ class ParserConfigOverlayTest {
"/data/local/tmp/com/safetycenter/config/tests/SafetyCenterConfigTestsOverlay.apk"
private const val STATE_ENABLED = "STATE_ENABLED"
- private fun getStateForOverlay(overlayPackage: String): String? {
+ private fun getStateForOverlay(uid: Int, overlayPackage: String): String? {
+
val result: String =
- runShellCommand("cmd overlay dump --user 0 state $overlayPackage").lines().first()
+ runShellCommand("cmd overlay dump --user $uid state $overlayPackage")
+ .lines()
+ .first()
if (!result.startsWith("STATE_")) {
return null
}
@@ -136,10 +140,11 @@ class ParserConfigOverlayTest {
@JvmStatic
@BeforeClass
fun install() {
+ val uid = UserHandle.myUserId()
runShellCommand("pm install -r --force-sdk --force-queryable $OVERLAY_PATH")
- waitForWithTimeout { getStateForOverlay(OVERLAY_PACKAGE) != null }
- runShellCommand("cmd overlay enable --user 0 $OVERLAY_PACKAGE")
- waitForWithTimeout { getStateForOverlay(OVERLAY_PACKAGE) == STATE_ENABLED }
+ waitForWithTimeout { getStateForOverlay(uid, OVERLAY_PACKAGE) != null }
+ runShellCommand("cmd overlay enable --user $uid $OVERLAY_PACKAGE")
+ waitForWithTimeout { getStateForOverlay(uid, OVERLAY_PACKAGE) == STATE_ENABLED }
}
@JvmStatic
diff --git a/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml b/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml
new file mode 100644
index 000000000..cb6323fff
--- /dev/null
+++ b/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml
@@ -0,0 +1,188 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="AndroidLockScreenSources"
+ title="@com.android.safetycenter.resources:string/lock_screen_sources_title"
+ summary="@com.android.safetycenter.resources:string/lock_screen_sources_summary">
+ <dynamic-safety-source
+ id="AndroidLockScreen"
+ packageName="com.android.settings"
+ profile="primary_profile_only"
+ title="@com.android.safetycenter.resources:string/lock_screen_title"
+ summary="@com.android.safetycenter.resources:string/lock_screen_summary_disabled"
+ searchTerms="@com.android.safetycenter.resources:string/lock_screen_search_terms"
+ initialDisplayState="disabled"
+ notificationsAllowed="true"/>
+ <dynamic-safety-source
+ id="AndroidFaceUnlock"
+ packageName="com.android.settings"
+ profile="all_profiles"
+ title="@com.android.safetycenter.resources:string/face_unlock_title"
+ titleForWork="@com.android.safetycenter.resources:string/face_unlock_title_for_work"
+ titleForPrivateProfile="@com.android.safetycenter.resources:string/face_unlock_title_for_private_profile"
+ searchTerms="@com.android.safetycenter.resources:string/face_unlock_search_terms"
+ refreshOnPageOpenAllowed="true"
+ initialDisplayState="hidden"/>
+ <dynamic-safety-source
+ id="AndroidFingerprintUnlock"
+ packageName="com.android.settings"
+ profile="all_profiles"
+ title="@com.android.safetycenter.resources:string/fingerprint_unlock_title"
+ titleForWork="@com.android.safetycenter.resources:string/fingerprint_unlock_title_for_work"
+ titleForPrivateProfile="@com.android.safetycenter.resources:string/fingerprint_unlock_title_for_private_profile"
+ searchTerms="@com.android.safetycenter.resources:string/fingerprint_unlock_search_terms"
+ refreshOnPageOpenAllowed="true"
+ initialDisplayState="hidden"/>
+ <dynamic-safety-source
+ id="AndroidWearUnlock"
+ packageName="com.android.settings"
+ profile="all_profiles"
+ title="@com.android.safetycenter.resources:string/wear_unlock_title"
+ titleForWork="@com.android.safetycenter.resources:string/wear_unlock_title_for_work"
+ titleForPrivateProfile="@com.android.safetycenter.resources:string/wear_unlock_title_for_private_profile"
+ searchTerms="@com.android.safetycenter.resources:string/wear_unlock_search_terms"
+ refreshOnPageOpenAllowed="true"
+ initialDisplayState="hidden"/>
+ <dynamic-safety-source
+ id="AndroidBiometrics"
+ packageName="com.android.settings"
+ profile="all_profiles"
+ title="@com.android.safetycenter.resources:string/biometrics_title"
+ titleForWork="@com.android.safetycenter.resources:string/biometrics_title_for_work"
+ titleForPrivateProfile="@com.android.safetycenter.resources:string/biometrics_title_for_private_profile"
+ searchTerms="@com.android.safetycenter.resources:string/biometrics_search_terms"
+ initialDisplayState="hidden"/>
+ </safety-sources-group>
+ <safety-sources-group
+ id="AndroidCellularNetworkSecuritySources"
+ title="@com.android.safetycenter.resources:string/cellular_network_security_title"
+ summary="@com.android.safetycenter.resources:string/cellular_network_security_summary">
+ <dynamic-safety-source
+ id="AndroidCellularNetworkSecurity"
+ packageName="com.android.phone"
+ profile="primary_profile_only"
+ notificationsAllowed="true"
+ initialDisplayState="hidden"/>
+ </safety-sources-group>
+ <safety-sources-group
+ id="AndroidPrivacySources"
+ title="@com.android.safetycenter.resources:string/privacy_sources_title"
+ summary="@com.android.safetycenter.resources:string/privacy_sources_summary"
+ statelessIconType="privacy">
+ <static-safety-source
+ id="AndroidPermissionManager"
+ profile="primary_profile_only"
+ intentAction="android.intent.action.MANAGE_PERMISSIONS"
+ title="@com.android.safetycenter.resources:string/permission_manager_title"
+ summary="@com.android.safetycenter.resources:string/permission_manager_summary"
+ searchTerms="@com.android.safetycenter.resources:string/permission_manager_search_terms"/>
+ <dynamic-safety-source
+ id="AndroidHealthConnect"
+ profile="primary_profile_only"
+ packageName="com.android.healthconnect.controller"
+ initialDisplayState="hidden"
+ refreshOnPageOpenAllowed="false"
+ title="@com.android.safetycenter.resources:string/health_connect_title"
+ searchTerms="@com.android.safetycenter.resources:string/health_connect_search_terms"/>
+ <dynamic-safety-source
+ id="AndroidPrivacyAppDataSharingUpdates"
+ packageName="com.android.permissioncontroller"
+ profile="primary_profile_only"
+ initialDisplayState="hidden"
+ refreshOnPageOpenAllowed="true"
+ title="@com.android.safetycenter.resources:string/app_data_sharing_updates_title"
+ searchTerms="@com.android.safetycenter.resources:string/app_data_sharing_updates_search_terms"/>
+ <static-safety-source
+ id="AndroidPrivacyControls"
+ profile="primary_profile_only"
+ intentAction="android.settings.PRIVACY_CONTROLS"
+ title="@com.android.safetycenter.resources:string/privacy_controls_title"
+ summary="@com.android.safetycenter.resources:string/privacy_controls_summary"
+ searchTerms="@com.android.safetycenter.resources:string/privacy_controls_search_terms"/>
+ <issue-only-safety-source
+ id="AndroidAccessibility"
+ packageName="com.android.permissioncontroller"
+ profile="all_profiles"
+ notificationsAllowed="true"
+ refreshOnPageOpenAllowed="true"/>
+ <issue-only-safety-source
+ id="AndroidNotificationListener"
+ packageName="com.android.permissioncontroller"
+ profile="primary_profile_only"
+ notificationsAllowed="true"
+ refreshOnPageOpenAllowed="true"/>
+ <issue-only-safety-source
+ id="AndroidBackgroundLocation"
+ packageName="com.android.permissioncontroller"
+ profile="all_profiles"
+ notificationsAllowed="true"
+ refreshOnPageOpenAllowed="true"/>
+ <issue-only-safety-source
+ id="AndroidPermissionAutoRevoke"
+ packageName="com.android.permissioncontroller"
+ profile="all_profiles"
+ notificationsAllowed="true"
+ refreshOnPageOpenAllowed="true"/>
+ <issue-only-safety-source
+ id="AndroidCertificateTransparency"
+ packageName="android"
+ profile="primary_profile_only"
+ notificationsAllowed="false"
+ refreshOnPageOpenAllowed="true"/>
+ </safety-sources-group>
+ <safety-sources-group
+ id="AndroidPrivacySourcesAdditional"
+ title="@com.android.safetycenter.resources:string/privacy_additional_title">
+ <static-safety-source
+ id="AndroidPermissionUsage"
+ profile="primary_profile_only"
+ intentAction="android.intent.action.REVIEW_PERMISSION_USAGE"
+ title="@com.android.safetycenter.resources:string/permission_usage_title"
+ summary="@com.android.safetycenter.resources:string/permission_usage_summary"
+ searchTerms="@com.android.safetycenter.resources:string/permission_usage_search_terms"/>
+ <dynamic-safety-source
+ id="AndroidPrivateSpace"
+ packageName="com.android.settings"
+ profile="primary_profile_only"
+ title="@com.android.safetycenter.resources:string/private_space_title"
+ summary="@com.android.safetycenter.resources:string/private_space_summary"
+ searchTerms="@com.android.safetycenter.resources:string/private_space_search_terms"
+ initialDisplayState="hidden"
+ maxSeverityLevel="0"/>
+ </safety-sources-group>
+ <safety-sources-group
+ id="AndroidAdvancedSources"
+ title="@com.android.safetycenter.resources:string/advanced_title">
+ <dynamic-safety-source
+ id="AndroidWorkPolicyInfo"
+ packageName="com.android.permissioncontroller"
+ profile="primary_profile_only"
+ title="@com.android.safetycenter.resources:string/work_policy_title"
+ initialDisplayState="hidden"
+ refreshOnPageOpenAllowed="true"/>
+ <static-safety-source
+ id="AndroidMoreSettings"
+ profile="primary_profile_only"
+ intentAction="com.android.settings.MORE_SECURITY_PRIVACY_SETTINGS"
+ title="@com.android.safetycenter.resources:string/more_settings_title"
+ summary="@com.android.safetycenter.resources:string/more_settings_summary"
+ searchTerms="@com.android.safetycenter.resources:string/more_settings_search_terms"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Resources/res/values-af-v36/strings.xml b/SafetyCenter/Resources/res/values-af-v36/strings.xml
new file mode 100644
index 000000000..304c3ec83
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-af-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Gesig"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Gesig vir werk"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Gesigslot, gesig"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Vingerafdruk"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Vingerafdruk vir werk"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Vingerafdruk, vinger, voeg vingerafdruk by"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Horlosie"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Horlosie vir werk"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Horlosie, Horlosieontsluiting"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-am-v36/strings.xml b/SafetyCenter/Resources/res/values-am-v36/strings.xml
new file mode 100644
index 000000000..573331193
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-am-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"መልክ"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"መልክ ለሥራ"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"በመልክ መክፈት፣ መልክ"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"የጣት አሻራ"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"የጣት አሻራ ለሥራ"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"የጣት አሻራ፣ ጣት፣ የጣት አሻራ ያክሉ"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"የእጅ ሰዓት"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"የእጅ ሰዓት ለሥራ"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"የእጅ ሰዓት፣ የእጅ ሰዓት መክፈቻ"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ar-v36/strings.xml b/SafetyCenter/Resources/res/values-ar-v36/strings.xml
new file mode 100644
index 000000000..0012b1548
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ar-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"الوجه"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"بصمة الوجه للعمل"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"فتح الجهاز ببصمة الوجه، الوجه"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"بصمة الإصبع"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"بصمة الإصبع للعمل"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"بصمة الإصبع، إصبع، إضافة بصمة إصبع"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"الساعة"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"فتح الجهاز من الساعة للعمل"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"الساعة، فتح الجهاز من الساعة"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-as-v36/strings.xml b/SafetyCenter/Resources/res/values-as-v36/strings.xml
new file mode 100644
index 000000000..dff88f101
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-as-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"মুখাৱয়ব"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"কৰ্মস্থানৰ বাবে মুখাৱয়ব"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"ফে’চ আনলক, মুখাৱয়ব"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ফিংগাৰপ্ৰিণ্ট"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"কৰ্মস্থানৰ ফিংগাৰপ্ৰিণ্ট"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ফিংগাৰপ্ৰিণ্ট, আঙুলি, ফিংগাৰপ্ৰিণ্ট যোগ দিয়ক"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"ঘড়ী"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"কৰ্মস্থানৰ বাবে ঘড়ী"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"ঘড়ী, ঘড়ীৰ দ্বাৰা আনলক কৰাৰ সুবিধা"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-az-v36/strings.xml b/SafetyCenter/Resources/res/values-az-v36/strings.xml
new file mode 100644
index 000000000..26a77a7d7
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-az-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Üz"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"İş üçün üz"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Üz ilə Kiliddən Açma, Üz"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Barmaq izi"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"İş üçün barmaq izi"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Barmaq izi, barmaq, barmaq izi əlavə edin"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Saat"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"İş üçün saat"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Saat, Saatla kilidaçma"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-b+sr+Latn-v36/strings.xml b/SafetyCenter/Resources/res/values-b+sr+Latn-v36/strings.xml
new file mode 100644
index 000000000..3613b2c02
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-b+sr+Latn-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Lice"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Otključavanje licem za posao"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Otključavanje licem, lice"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Otisak prsta"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Otisak prsta za posao"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Otisak prsta, prst, dodaj otisak prsta"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Sat"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Otključavanje satom za posao"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Sat, otključavanje satom"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-be-v36/strings.xml b/SafetyCenter/Resources/res/values-be-v36/strings.xml
new file mode 100644
index 000000000..7fdab64d0
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-be-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Твар"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Твар (для выкарыстання)"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Распазнаванне твару, твар"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Адбітак пальца"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Адбітак пальца (для выкарыстання)"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Адбітак пальца, палец, дадаць адбітак пальца"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Гадзіннік"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Гадзіннік (для выкарыстання)"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Гадзіннік, разблакіроўка гадзіннікам"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-bg-v36/strings.xml b/SafetyCenter/Resources/res/values-bg-v36/strings.xml
new file mode 100644
index 000000000..d3f211236
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-bg-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Лице"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Лице за служебни цели"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Отключване с лице, лице"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Отпечатък"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Отпечатък за служебни цели"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Отпечатък, пръст, добавяне на отпечатък"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Часовник"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Часовник за служебни цели"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Часовник, отключване чрез часовника"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-bn-v36/strings.xml b/SafetyCenter/Resources/res/values-bn-v36/strings.xml
new file mode 100644
index 000000000..d06b0617c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-bn-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"ফেস"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"ডিভাইসে কাজ করার জন্য ফেস আনলক"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"ফেস আনলক, ফেস"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ফিঙ্গারপ্রিন্ট"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"ডিভাইসে কাজ করার জন্য ফিঙ্গারপ্রিন্ট আনলক"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ফিঙ্গারপ্রিন্ট, ফিঙ্গার, ফিঙ্গারপ্রিন্ট যোগ করুন"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"স্মার্টওয়াচ"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ডিভাইসে কাজ করার জন্য স্মার্টওয়াচ আনলক"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"স্মার্টওয়াচ, স্মার্টওয়াচ আনলক করা"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-bs-v36/strings.xml b/SafetyCenter/Resources/res/values-bs-v36/strings.xml
new file mode 100644
index 000000000..21ef83688
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-bs-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Lice"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Otključavanje licem za radni profil"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Otključavanje licem, lice"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Otisak prsta"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Otisak prsta za radni profil"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Otisak prsta, prst, dodavanje otiska prsta"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Sat"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Sat za radni profil"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Sat, otključavanje pomoću sata"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ca-v36/strings.xml b/SafetyCenter/Resources/res/values-ca-v36/strings.xml
new file mode 100644
index 000000000..7921f0a93
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ca-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Cara"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Cara de la feina"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Desbloqueig facial, cara"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Empremta digital"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Empremta digital de la feina"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Empremta digital, dit, afegir una empremta digital"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Rellotge"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Rellotge de la feina"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Rellotge, Desbloqueig amb rellotge"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-cs-v36/strings.xml b/SafetyCenter/Resources/res/values-cs-v36/strings.xml
new file mode 100644
index 000000000..e14e7f8ee
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-cs-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Obličej"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Obličej pro práci"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Odemknutí obličejem, obličej"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Otisk prstu"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Otisk prstu pro práci"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Otisk prstu, prst, přidat otisk prstu"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Hodinky"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Hodinky pro práci"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Hodinky, odemknutí hodinkami"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-da-v36/strings.xml b/SafetyCenter/Resources/res/values-da-v36/strings.xml
new file mode 100644
index 000000000..be7c51016
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-da-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Ansigt"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Ansigt til arbejdsprofil"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Ansigtsoplåsning, ansigt"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Fingeraftryk"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Fingeraftryk til arbejdsprofil"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Fingeraftryk, finger, tilføj fingeraftryk"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Ur"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Ur til arbejdsprofil"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Ur, uroplåsning"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-de-v34/strings.xml b/SafetyCenter/Resources/res/values-de-v34/strings.xml
index 4289c14a0..8f75bdfbb 100644
--- a/SafetyCenter/Resources/res/values-de-v34/strings.xml
+++ b/SafetyCenter/Resources/res/values-de-v34/strings.xml
@@ -25,7 +25,7 @@
<string name="app_data_sharing_updates_title" msgid="7428862330643262588">"Änderungen bei der Weitergabe von Standortdaten"</string>
<string name="app_data_sharing_updates_search_terms" msgid="8414777373734245398">"Daten, Datenweitergabe, Aktualisierungen der Datenweitergabe, Aktualisierungen der Weitergabe von Standortdaten, Weitergabe"</string>
<string name="advanced_title" msgid="6259362998269627310">"Weitere Einstellungen"</string>
- <string name="more_settings_title" msgid="9033454654010697185">"Mehr Sicherheit und Datenschutz"</string>
+ <string name="more_settings_title" msgid="9033454654010697185">"Mehr Sicherheit &amp; Datenschutz"</string>
<string name="more_settings_summary" msgid="7086620830002515710">"Autofill, Benachrichtigungen und mehr"</string>
<string name="more_settings_search_terms" msgid="1371913937610933955"></string>
<string name="work_policy_title" msgid="915692932391542104">"Informationen zu den Arbeitsrichtlinien"</string>
diff --git a/SafetyCenter/Resources/res/values-de-v36/strings.xml b/SafetyCenter/Resources/res/values-de-v36/strings.xml
new file mode 100644
index 000000000..693573d89
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-de-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Gesicht"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Einstellungen der Entsperrung per Gesichtserkennung für die Arbeit"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Entsperrung per Gesichtserkennung, Gesichtsentsperrung, Gesicht"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Fingerabdruck"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Einstellungen der Entsperrung per Fingerabdruck für die Arbeit"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Fingerabdruck, Finger, Fingerabdruck hinzufügen"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Smartwatch"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Einstellungen von „Mit Smartwatch entsperren“ für die Arbeit"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Smartwatch, Mit Smartwatch entsperren"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-el-v36/strings.xml b/SafetyCenter/Resources/res/values-el-v36/strings.xml
new file mode 100644
index 000000000..9a57115d4
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-el-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Πρόσωπο"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Πρόσωπο για επαγγελματική χρήση"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Ξεκλείδωμα με το πρόσωπο, Πρόσωπο"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Δακτυλικό αποτύπωμα"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Δακτυλικό αποτύπωμα για επαγγελματική χρήση"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Δακτυλικό αποτύπωμα, Δάχτυλο, Προσθήκη δακτυλικού αποτυπώματος"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Ρολόι"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Ρολόι για επαγγελματική χρήση"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Ρολόι, Ξεκλείδωμα ρολογιού"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-en-rAU-v36/strings.xml b/SafetyCenter/Resources/res/values-en-rAU-v36/strings.xml
new file mode 100644
index 000000000..327a15b5d
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-en-rAU-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Face"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Face for work"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Face Unlock, Face"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Fingerprint"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Fingerprint for work"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Fingerprint, Finger, Add fingerprint"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Watch"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Watch for work"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Watch, Watch Unlock"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-en-rCA-v36/strings.xml b/SafetyCenter/Resources/res/values-en-rCA-v36/strings.xml
new file mode 100644
index 000000000..6a70e3a9b
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-en-rCA-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Face"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Face for work"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Face unlock, Face"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Fingerprint"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Fingerprint for work"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Fingerprint, Finger, Add fingerprint"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Watch"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Watch for work"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Watch, Watch unlock"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-en-rGB-v36/strings.xml b/SafetyCenter/Resources/res/values-en-rGB-v36/strings.xml
new file mode 100644
index 000000000..327a15b5d
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-en-rGB-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Face"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Face for work"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Face Unlock, Face"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Fingerprint"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Fingerprint for work"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Fingerprint, Finger, Add fingerprint"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Watch"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Watch for work"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Watch, Watch Unlock"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-en-rIN-v36/strings.xml b/SafetyCenter/Resources/res/values-en-rIN-v36/strings.xml
new file mode 100644
index 000000000..327a15b5d
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-en-rIN-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Face"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Face for work"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Face Unlock, Face"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Fingerprint"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Fingerprint for work"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Fingerprint, Finger, Add fingerprint"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Watch"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Watch for work"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Watch, Watch Unlock"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-es-rUS-v36/strings.xml b/SafetyCenter/Resources/res/values-es-rUS-v36/strings.xml
new file mode 100644
index 000000000..02e0214fd
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-es-rUS-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Rostro"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Rostro para el trabajo"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Desbloqueo facial, rostro"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Huella dactilar"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Huella para el trabajo"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Huella dactilar, huella, agregar huella dactilar"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Reloj"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Reloj para trabajar"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Reloj, Desbloqueo con reloj"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-es-v36/strings.xml b/SafetyCenter/Resources/res/values-es-v36/strings.xml
new file mode 100644
index 000000000..8dd8a23c4
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-es-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Cara"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Cara para el trabajo"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Desbloqueo facial, cara"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Huella digital"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Huella para el trabajo"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Huella digital, dedo, añadir huella digital"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Reloj"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Reloj para el trabajo"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Reloj, Desbloqueo con reloj"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-et-v36/strings.xml b/SafetyCenter/Resources/res/values-et-v36/strings.xml
new file mode 100644
index 000000000..c5828aebf
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-et-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Nägu"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Näoga avamine töö jaoks"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Näoga avamine, nägu"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Sõrmejälg"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Sõrmejäljega avamine töö jaoks"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Sõrmejälg, sõrm, sõrmejälje lisamine"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Kell"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Kellaga avamine töö jaoks"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Kell, kellaga avamine"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-eu-v36/strings.xml b/SafetyCenter/Resources/res/values-eu-v36/strings.xml
new file mode 100644
index 000000000..6187881b1
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-eu-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Aurpegia"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Aurpegia lanerako"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Aurpegi bidez desblokeatzea, aurpegia"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Hatz-marka"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Hatz-marka lanerako"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Hatz-marka, hatza, hatz-marka gehitu"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Ikusi"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Erlojua lanerako"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Erlojua, Erloju bidez desblokeatzea"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-fa-v36/strings.xml b/SafetyCenter/Resources/res/values-fa-v36/strings.xml
new file mode 100644
index 000000000..5a2885223
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-fa-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"چهره"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"چهره برای نمایه کاری"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"قفل‌گشایی با چهره، چهره"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"اثر انگشت"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"اثر انگشت برای نمایه کاری"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"اثر انگشت، انگشت، افزودن اثر انگشت"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"ساعت"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ساعت برای نمایه کاری"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"ساعت، قفل‌گشایی با ساعت"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-fi-v36/strings.xml b/SafetyCenter/Resources/res/values-fi-v36/strings.xml
new file mode 100644
index 000000000..42ab15309
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-fi-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Kasvot"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Kasvot (työ)"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Kasvojentunnistusavaus, kasvot"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Sormenjälki"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Sormenjälki (työ)"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Sormenjälki, sormi, lisää sormenjälki"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Kello"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Kello (työ)"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Kello, kelloavaus"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-fr-rCA-v36/strings.xml b/SafetyCenter/Resources/res/values-fr-rCA-v36/strings.xml
new file mode 100644
index 000000000..cc3a3dba6
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-fr-rCA-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Reconnaissance faciale"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Reconnaissance faciale pour le travail"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Déverrouillage par reconnaissance faciale, reconnaissance faciale"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Empreinte digitale"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Empreinte digitale pour le travail"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Empreinte digitale, doigt, ajouter une empreinte digitale"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Montre"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Montre pour le travail"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Montre, Déverrouillage à l\'aide d\'une montre"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-fr-v36/strings.xml b/SafetyCenter/Resources/res/values-fr-v36/strings.xml
new file mode 100644
index 000000000..d5b3b68ec
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-fr-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Visage"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Déverrouillage par reconnaissance faciale pour le travail"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Déverrouillage par reconnaissance faciale, Visage"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Empreinte digitale"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Déverrouillage par empreinte digitale pour le travail"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Empreinte digitale, Doigt, Ajouter une empreinte digitale"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Montre"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Déverrouillage par une montre pour le travail"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Montre, Déverrouillage par une montre"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-gl-v36/strings.xml b/SafetyCenter/Resources/res/values-gl-v36/strings.xml
new file mode 100644
index 000000000..d0eed4528
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-gl-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Cara"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Cara para o traballo"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Desbloqueo facial, cara"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Impresión dixital"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Impresión dixital para o traballo"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Impresión dixital, dedo, engadir impresión dixital"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Reloxo"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Reloxo para o traballo"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Reloxo, desbloqueo mediante reloxo"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-gu-v36/strings.xml b/SafetyCenter/Resources/res/values-gu-v36/strings.xml
new file mode 100644
index 000000000..709be02b9
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-gu-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"ફેસ"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"ડિવાઇસ પર કામ કરવા માટે ફેસ અનલૉક"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"ફેસ અનલૉક, ફેસ"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ફિંગરપ્રિન્ટ"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"ડિવાઇસ પર કામ કરવા માટે ફિંગરપ્રિન્ટ"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ફિંગરપ્રિન્ટ, ફિંગર, ફિંગરપ્રિન્ટ ઉમેરો"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"વૉચ"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ડિવાઇસ પર કામ કરવા માટે વૉચ"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"વૉચ, વૉચ અનલૉક"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-hi-v36/strings.xml b/SafetyCenter/Resources/res/values-hi-v36/strings.xml
new file mode 100644
index 000000000..3edfcabaa
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-hi-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"फ़ेस अनलॉक"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"डिवाइस पर काम करने के लिए फ़ेस अनलॉक"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"फ़ेस अनलॉक, फ़ेस"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"फ़िंगरप्रिंट"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"डिवाइस पर काम करने के लिए फ़िंगरप्रिंट"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"फ़िंगरप्रिंट, फ़िंगर, फ़िंगरप्रिंट जोड़ें"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"स्मार्टवॉच"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"डिवाइस पर काम करने के लिए स्मार्टवॉच"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"स्मार्टवॉच, स्मार्टवॉच से डिवाइस अनलॉक करने की सुविधा"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-hr-v36/strings.xml b/SafetyCenter/Resources/res/values-hr-v36/strings.xml
new file mode 100644
index 000000000..8e8c0c288
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-hr-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Lice"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Lice za poslovni profil"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Otključavanje licem, lice"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Otisak prsta"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Otisak za poslovni profil"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Otisak prsta, prst, dodavanje otiska prsta"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Sat"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Sat za poslovni profil"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Sat, otključavanje satom"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-hu-v36/strings.xml b/SafetyCenter/Resources/res/values-hu-v36/strings.xml
new file mode 100644
index 000000000..065af914c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-hu-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Arc"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Arc munkához"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Arcalapú feloldás, Arc"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Ujjlenyomat"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Ujjlenyomat munkához"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Ujjlenyomat, Ujj, Ujjlenyomat hozzáadása"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Óra"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Óra munkához"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Óra, Feloldás órával"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-hy-v36/strings.xml b/SafetyCenter/Resources/res/values-hy-v36/strings.xml
new file mode 100644
index 000000000..97da4e698
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-hy-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Դեմք"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Դեմք (աշխատանք)"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Դեմքով ապակողպում, դեմք"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Մատնահետք"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Մատնահետք (աշխատանք)"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Մատնահետք, մատ, ավելացնել մատնահետք"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Ժամացույց"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Ժամացույց (աշխատանք)"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Ժամացույց, ժամացույցով ապակողպում"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-in-v36/strings.xml b/SafetyCenter/Resources/res/values-in-v36/strings.xml
new file mode 100644
index 000000000..ed52e1557
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-in-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Wajah"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Wajah untuk kerja"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Buka dengan wajah, Wajah"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Sidik jari"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Sidik jari untuk kerja"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Sidik jari, Jari, Tambahkan sidik jari"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Smartwatch"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Smartwatch untuk kerja"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Smartwatch, Buka dengan smartwatch"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-is-v36/strings.xml b/SafetyCenter/Resources/res/values-is-v36/strings.xml
new file mode 100644
index 000000000..c1284b2a1
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-is-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Andlit"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Andlit fyrir vinnu"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Andlitskenni, andlit"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Fingrafar"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Fingrafar fyrir vinnu"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Fingrafar, fingur, bæta fingrafari við"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Úr"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Úr fyrir vinnu"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Úr, opnun með úri"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-it-v36/strings.xml b/SafetyCenter/Resources/res/values-it-v36/strings.xml
new file mode 100644
index 000000000..267daef8f
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-it-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Volto"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Volto per lavoro"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Sblocco con il Volto, Volto"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Impronta"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Impronta per lavoro"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Impronta, Dito, Aggiungi impronta"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Orologio"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Orologio per lavoro"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Orologio, Sblocco dall\'orologio"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-iw-v36/strings.xml b/SafetyCenter/Resources/res/values-iw-v36/strings.xml
new file mode 100644
index 000000000..e17879fc3
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-iw-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"זיהוי הפנים"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"זיהוי הפנים לפרופיל העבודה"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"פתיחה בזיהוי פנים, פנים"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"זיהוי טביעת אצבע"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"זיהוי טביעת אצבע לפרופיל עבודה"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"טביעת אצבע, אצבע, הוספת טביעת אצבע"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"ביטול נעילה עם השעון"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ביטול נעילה עם השעון לפרופיל עבודה"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"שעון, ביטול נעילה עם השעון"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-iw/strings.xml b/SafetyCenter/Resources/res/values-iw/strings.xml
index bde0d294b..a6c39074b 100644
--- a/SafetyCenter/Resources/res/values-iw/strings.xml
+++ b/SafetyCenter/Resources/res/values-iw/strings.xml
@@ -18,7 +18,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="safetyCenterResourcesAppLabel" msgid="4043334186295695930">"משאבים למרכז הבטיחות"</string>
- <string name="lock_screen_sources_title" msgid="3317906280484627707">"נעילת מכשיר"</string>
+ <string name="lock_screen_sources_title" msgid="3317906280484627707">"נעילת המכשיר"</string>
<string name="lock_screen_sources_summary" msgid="7220439741282516496"></string>
<string name="lock_screen_title" msgid="4069104894527169877">"נעילת מסך"</string>
<string name="lock_screen_summary_disabled" msgid="354071230916616692">"אין עדיין פרטים"</string>
diff --git a/SafetyCenter/Resources/res/values-ja-v36/strings.xml b/SafetyCenter/Resources/res/values-ja-v36/strings.xml
new file mode 100644
index 000000000..7fc642fe8
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ja-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"顔認証"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"顔認証(仕事用)"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"顔認証, 顔"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"指紋認証"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"指紋認証(仕事用)"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"指紋, 指, 指紋 追加"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"ウォッチ認証"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ウォッチ認証(仕事用)"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"ウォッチ, ウォッチ認証"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ka-v36/strings.xml b/SafetyCenter/Resources/res/values-ka-v36/strings.xml
new file mode 100644
index 000000000..723402437
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ka-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"სახე"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"სახე სამუშაოსთვის"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"სახით განბლოკვა, სახე"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"თითის ანაბეჭდი"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"თითის ანაბეჭდი სამუშაოსთვის"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"თითის ანაბეჭდი, თითი, თითის ანაბეჭდის დამატება"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"საათი"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"საათი სამუშაოსთვის"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"საათი, საათის განბლოკვა"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-kk-v36/strings.xml b/SafetyCenter/Resources/res/values-kk-v36/strings.xml
new file mode 100644
index 000000000..c61a05257
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-kk-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Бет"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Жұмысқа арналған бет тану функциясы"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Бет тану, бет"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Саусақ ізі"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Жұмысқа арналған саусақ ізі"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Саусақ ізі, саусақ, саусақ ізін қосу"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Сағат"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Жұмысқа арналған сағат"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Сағат, құлыпты сағат арқылы ашу"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-km-v36/strings.xml b/SafetyCenter/Resources/res/values-km-v36/strings.xml
new file mode 100644
index 000000000..cefd48bec
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-km-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"មុខ"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"មុខ​សម្រាប់​ការងារ"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"ការដោះសោ​ដោយស្កេន​មុខ, មុខ"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ស្នាម​ម្រាមដៃ"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"ស្នាមម្រាមដៃសម្រាប់ការងារ"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ស្នាមម្រាមដៃ, ម្រាមដៃ, បញ្ចូល​ស្នាមម្រាមដៃ"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"នាឡិកា"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"នាឡិកា​សម្រាប់​ការងារ"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"នាឡិកា, ការដោះសោ​ដោយប្រើ​នាឡិកា"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-kn-v36/strings.xml b/SafetyCenter/Resources/res/values-kn-v36/strings.xml
new file mode 100644
index 000000000..222c4f1ab
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-kn-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"ಫೇಸ್"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"ಕೆಲಸದ ಸ್ಥಳದ ಫೇಸ್"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"ಫೇಸ್ ಅನ್‌ಲಾಕ್, ಫೇಸ್"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ಫಿಂಗರ್‌ ಪ್ರಿಂಟ್"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"ಕೆಲಸಕ್ಕಾಗಿ ಫಿಂಗರ್‌ ಪ್ರಿಂಟ್"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ಫಿಂಗರ್‌ ಪ್ರಿಂಟ್, ಫಿಂಗರ್, ಫಿಂಗರ್‌ ಪ್ರಿಂಟ್ ಸೇರಿಸಿ"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"ವಾಚ್"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ಕೆಲಸಕ್ಕಾಗಿ ಒಂದು ವಾಚ್"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"ವಾಚ್, ವಾಚ್ ಅನ್‌ಲಾಕ್"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ko-v36/strings.xml b/SafetyCenter/Resources/res/values-ko-v36/strings.xml
new file mode 100644
index 000000000..619c47d66
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ko-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"얼굴"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"직장용 얼굴"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"얼굴 인식 잠금 해제, 얼굴"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"지문"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"직장용 지문"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"지문, 손가락, 지문 추가"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"시계"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"직장용 스마트시계"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"시계, 스마트시계 잠금 해제"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ky-v36/strings.xml b/SafetyCenter/Resources/res/values-ky-v36/strings.xml
new file mode 100644
index 000000000..3e2823a05
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ky-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Жүз"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Жумуш колдонмолорун жүзүнөн таанып ачуу"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Жүзүнөн таанып ачуу, Жүз"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Манжа изи"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Жумуш колдонмолору үчүн манжа изи"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Манжа изи, Манжа, Манжа изин кошуу"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Саат"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Жумуш колдонмолорун саат менен ачуу"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Саат, Кулпуну саат менен ачуу"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-lo-v36/strings.xml b/SafetyCenter/Resources/res/values-lo-v36/strings.xml
new file mode 100644
index 000000000..e96e6c1a9
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-lo-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"ໃບໜ້າ"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"ໃບໜ້າສຳລັບວຽກ"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"ປົດລັອກດ້ວຍໜ້າ, ໃບໜ້າ"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ລາຍນີ້ວມື"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"ລາຍນິ້ວມື້ສຳລັບວຽກ"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ລາຍນິ້ວມື, ນິ້ວມື, ເພີ່ມລາຍນິ້ວມື"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"ໂມງ"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ໂມງສຳລັບວຽກ"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"ໂມງ, ປົດລັອກດ້ວຍໂມງ"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-lt-v36/strings.xml b/SafetyCenter/Resources/res/values-lt-v36/strings.xml
new file mode 100644
index 000000000..5376e7754
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-lt-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Veidas"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Atrakinimas pagal veidą, skirtas darbo profiliui"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Atrakinimas pagal veidą, veidas"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Piršto atspaudas"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Piršto atspaudas, skirtas darbo profiliui"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Piršto atspaudas, pirštas, pridėti piršto atspaudą"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Laikrodis"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Laikrodis, skirtas darbo profiliui"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Laikrodis, laikrodžio atrakinimas"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-lv-v36/strings.xml b/SafetyCenter/Resources/res/values-lv-v36/strings.xml
new file mode 100644
index 000000000..57861575e
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-lv-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Seja"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Seja darba profilam"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Autorizācija pēc sejas, seja"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Pirksta nospiedums"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Pirksta nospiedums darba profilam"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Pirksta nospiedums, pirksts, pievienot pirksta nospiedumu"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Pulkstenis"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Pulkstenis darba profilam"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Pulkstenis, atbloķēšana ar pulksteni"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-mk-v35/strings.xml b/SafetyCenter/Resources/res/values-mk-v35/strings.xml
index c4fc4bd17..1201ae74f 100644
--- a/SafetyCenter/Resources/res/values-mk-v35/strings.xml
+++ b/SafetyCenter/Resources/res/values-mk-v35/strings.xml
@@ -21,7 +21,7 @@
<string name="cellular_network_security_summary" msgid="7319307247487475572">"Тип мрежа, шифрирање, контроли за известувања"</string>
<string name="biometrics_title_for_private_profile" msgid="542819107383037820"></string>
<string name="privacy_title" msgid="7047524783080782769">"Приватност"</string>
- <string name="privacy_sources_title" msgid="309304028326660956">"Контроли на приватноста"</string>
+ <string name="privacy_sources_title" msgid="309304028326660956">"Контроли за приватност"</string>
<string name="privacy_sources_summary" msgid="2165270848857537278">"Дозволи, контроли"</string>
<string name="privacy_additional_title" msgid="4239060639056083649"></string>
<string name="private_space_title" msgid="6158245041481535879">"Приватен простор"</string>
diff --git a/SafetyCenter/Resources/res/values-mk-v36/strings.xml b/SafetyCenter/Resources/res/values-mk-v36/strings.xml
new file mode 100644
index 000000000..7f2986393
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-mk-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Лик"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Лик за работен профил"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"„Отклучување со лик“, лик"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Отпечаток"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Отпечаток за работен профил"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Отпечаток, прст, додај отпечаток"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Часовник"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Часовник за работен профил"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Часовник, „Отклучување со часовник“"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-mk/strings.xml b/SafetyCenter/Resources/res/values-mk/strings.xml
index 2b2414d0d..379a5234b 100644
--- a/SafetyCenter/Resources/res/values-mk/strings.xml
+++ b/SafetyCenter/Resources/res/values-mk/strings.xml
@@ -33,9 +33,9 @@
<string name="permission_manager_title" msgid="5277347862821255015">"Управувач со дозволи"</string>
<string name="permission_manager_summary" msgid="8099852107340970790">"Го контролира пристапот на апликациите до вашите податоци"</string>
<string name="permission_manager_search_terms" msgid="2895147613099694722">"Дозволи, Управувач со дозволи"</string>
- <string name="privacy_controls_title" msgid="5322875777945432395">"Контроли за приватноста"</string>
+ <string name="privacy_controls_title" msgid="5322875777945432395">"Контроли за приватност"</string>
<string name="privacy_controls_summary" msgid="2402066941190435424">"Го контролира пристапот на уредот до микрофонот, камерата и друго"</string>
- <string name="privacy_controls_search_terms" msgid="3774472175934304165">"Приватност, Контроли на приватноста"</string>
+ <string name="privacy_controls_search_terms" msgid="3774472175934304165">"Приватност, Контроли за приватност"</string>
<string name="advanced_title" msgid="8745436380690561172">"Повеќе поставки"</string>
<string name="advanced_security_title" msgid="1126833338772188155">"Повеќе поставки за безбедност"</string>
<string name="advanced_security_summary" msgid="6172253327022425123">"Шифрирање, акредитиви и друго"</string>
diff --git a/SafetyCenter/Resources/res/values-ml-v36/strings.xml b/SafetyCenter/Resources/res/values-ml-v36/strings.xml
new file mode 100644
index 000000000..902c67639
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ml-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"ഫെയ്‌സ്"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"ഔദ്യോഗിക പ്രൊഫെെലിനുള്ള ഫെയ്‌സ്"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"ഫെയ്‌സ് അൺലോക്ക്, ഫെയ്‌സ്"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ഫിംഗർപ്രിന്റ്"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"ഔദ്യോഗിക പ്രൊഫൈലിനുള്ള ഫിംഗർപ്രിന്റ്"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ഫിംഗർപ്രിൻറ്, ഫിംഗർ, ഫിംഗർപ്രിന്റ് ചേർക്കുക"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"വാച്ച്"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ഔദ്യോഗിക പ്രൊഫൈലിനുള്ള വാച്ച്"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"വാച്ച്, വാച്ച് അൺലോക്ക്"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-mn-v36/strings.xml b/SafetyCenter/Resources/res/values-mn-v36/strings.xml
new file mode 100644
index 000000000..868fe1e63
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-mn-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Царай"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Ажлын зориулалтаар ашиглах царай"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Царайгаар түгжээ тайлах, царай"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Хурууны хээ"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Ажлын зориулалтаар ашиглах хурууны хээ"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Хурууны хээ, хуруу, хурууны хээ нэмэх"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Цаг"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Ажлын зориулалтаар ашиглах цаг"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Цаг, Утасны түгжээг цагаараа тайлах"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-mr-v36/strings.xml b/SafetyCenter/Resources/res/values-mr-v36/strings.xml
new file mode 100644
index 000000000..6eef98db9
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-mr-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"फेस"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"कार्य प्रोफाइलसाठी फेस अनलॉक"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"फेस अनलॉक, फेस"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"फिंगरप्रिंट"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"कार्य प्रोफाइलसाठी फिंगरप्रिंट"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"फिंगरप्रिंट, बोट, फिंगरप्रिंट जोडा"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"वॉच"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"कार्य प्रोफाइलसाठी वॉच"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"वॉच, वॉच अनलॉक"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ms-v36/strings.xml b/SafetyCenter/Resources/res/values-ms-v36/strings.xml
new file mode 100644
index 000000000..e867d5629
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ms-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Wajah"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Wajah untuk profil kerja"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Buka kunci wajah, Wajah"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Cap jari"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Cap jari untuk kerja"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Cap jari, Jari, Tambahkan cap jari"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Jam tangan"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Jam tangan untuk bekerja"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Jam tangan, Buka kunci jam tangan"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-my-v36/strings.xml b/SafetyCenter/Resources/res/values-my-v36/strings.xml
new file mode 100644
index 000000000..60475a70b
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-my-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"မျက်နှာ"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"အလုပ်အတွက် မျက်နှာ"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"မျက်နှာပြ လော့ခ်ဖွင့်ခြင်း၊ မျက်နှာ"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"လက်ဗွေ"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"အလုပ်အတွက် လက်ဗွေ"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"လက်ဗွေ၊ လက်ချောင်း၊ လက်ဗွေထည့်ရန်"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"လက်ပတ်နာရီ"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"အလုပ်အတွက် လက်ပတ်နာရီ"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"လက်ပတ်နာရီ၊ နာရီဖြင့်ဖွင့်ခြင်း"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-nb-v36/strings.xml b/SafetyCenter/Resources/res/values-nb-v36/strings.xml
new file mode 100644
index 000000000..7ad44d31f
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-nb-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Ansikt"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Ansikt for jobb"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Ansiktslås, ansikt"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Fingeravtrykk"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Fingeravtrykk for jobb"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Fingeravtrykk, finger, legg til fingeravtrykk"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Klokke"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Klokke for jobb"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Klokke, klokkelås"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ne-v36/strings.xml b/SafetyCenter/Resources/res/values-ne-v36/strings.xml
new file mode 100644
index 000000000..5b7e16192
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ne-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"फेस अनलक"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"कामसम्बन्धी प्रयोजनका लागि फेस अनलक"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"फेस अनलक, फेस"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"फिंगरप्रिन्ट"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"कामसम्बन्धी प्रयोजनका लागि फिंगरप्रिन्ट"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"फिंगरप्रिन्ट, फिंगर, फिंगरप्रिन्ट हाल्नुहोस्"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"स्मार्ट वाच"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"कामसम्बन्धी प्रयोजनका लागि स्मार्ट वाच"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"स्मार्ट वाच, स्मार्ट वाच अनलक गर्ने सुविधा"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-nl-v36/strings.xml b/SafetyCenter/Resources/res/values-nl-v36/strings.xml
new file mode 100644
index 000000000..69db97338
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-nl-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Gezicht"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Gezicht voor werk"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Ontgrendelen via gezichtsherkenning, Gezicht"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Vingerafdruk"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Vingerafdruk voor werk"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Vingerafdruk, Vinger, Vingerafdruk toevoegen"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Smartwatch"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Smartwatch voor werk"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Smartwatch, Ontgrendelen via smartwatch"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-or-v36/strings.xml b/SafetyCenter/Resources/res/values-or-v36/strings.xml
new file mode 100644
index 000000000..6ca5d6efa
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-or-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"ଫେସ"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"ୱାର୍କ ପାଇଁ ଫେସ"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"ଫେସ ଅନଲକ, ଫେସ"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ଟିପଚିହ୍ନ"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"ୱାର୍କ ପାଇଁ ଟିପଚିହ୍ନ"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ଟିପଚିହ୍ନ, ଆଙ୍ଗୁଠି, ଟିପଚିହ୍ନ ଯୋଗ କରନ୍ତୁ"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"ୱାଚ"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ୱାର୍କ ପାଇଁ ୱାଚ"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"ୱାଚ, ୱାଚ ଅନଲକ"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-pa-v36/strings.xml b/SafetyCenter/Resources/res/values-pa-v36/strings.xml
new file mode 100644
index 000000000..2dc9bb8c9
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-pa-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"ਫ਼ੇਸ ਅਣਲਾਕ"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"ਕੰਮ ਲਈ ਫ਼ੇਸ"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"ਫ਼ੇਸ ਅਣਲਾਕ, ਫ਼ੇਸ"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ਫਿੰਗਰਪ੍ਰਿੰਟ"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"ਕੰਮ ਲਈ ਫਿੰਗਰਪ੍ਰਿੰਟ"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ਫਿੰਗਰਪ੍ਰਿੰਟ, ਫਿੰਗਰ, ਫਿੰਗਰਪ੍ਰਿੰਟ ਸ਼ਾਮਲ ਕਰੋ"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"ਵਾਚ"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ਕੰਮ ਲਈ ਵਾਚ"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"ਵਾਚ, ਵਾਚ ਅਣਲਾਕ"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-pl-v36/strings.xml b/SafetyCenter/Resources/res/values-pl-v36/strings.xml
new file mode 100644
index 000000000..8705e008c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-pl-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Twarz"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Twarz – profil służbowy"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Rozpoznawanie twarzy, twarz"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Odcisk palca"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Odcisk palca – profil służbowy"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Odcisk palca, palec, dodaj odcisk palca"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Zegarek"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Zegarek – profil służbowy"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Zegarek, odblokowywanie za pomocą zegarka"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-pt-rBR-v36/strings.xml b/SafetyCenter/Resources/res/values-pt-rBR-v36/strings.xml
new file mode 100644
index 000000000..144078c11
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-pt-rBR-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Rosto"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Reconhecimento facial para o trabalho"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Desbloqueio facial, Rosto"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Impressão digital"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Impressão digital para o trabalho"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Impressão digital, Dedo, Adicionar impressão digital"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Relógio"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Relógio para o trabalho"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Relógio, Desbloqueio do relógio"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-pt-rPT-v36/strings.xml b/SafetyCenter/Resources/res/values-pt-rPT-v36/strings.xml
new file mode 100644
index 000000000..10a613c26
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-pt-rPT-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Rosto"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Rosto para trabalho"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Desbloqueio facial, rosto"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Impressão digital"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Impressão digital para trabalho"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Impressão digital, dedo, adicionar impressão digital"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Relógio"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Relógio para trabalho"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Relógio, Desbloqueio com o relógio"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-pt-v36/strings.xml b/SafetyCenter/Resources/res/values-pt-v36/strings.xml
new file mode 100644
index 000000000..144078c11
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-pt-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Rosto"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Reconhecimento facial para o trabalho"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Desbloqueio facial, Rosto"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Impressão digital"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Impressão digital para o trabalho"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Impressão digital, Dedo, Adicionar impressão digital"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Relógio"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Relógio para o trabalho"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Relógio, Desbloqueio do relógio"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ro-v36/strings.xml b/SafetyCenter/Resources/res/values-ro-v36/strings.xml
new file mode 100644
index 000000000..edc1a5ad9
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ro-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Chip"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Chip pentru serviciu"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Deblocare facială, Chip"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Amprentă"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Amprentă pentru serviciu"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Amprentă, Deget, Adaugă o amprentă"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Ceas"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Ceas pentru serviciu"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Ceas, Deblocare cu ceasul"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ru-v36/strings.xml b/SafetyCenter/Resources/res/values-ru-v36/strings.xml
new file mode 100644
index 000000000..cdaea134f
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ru-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Лицо"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Лицо для работы"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Фейсконтроль, лицо"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Отпечаток пальца"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Отпечаток для работы"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Отпечаток пальца, палец, добавить отпечаток пальца"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Часы"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Часы для работы"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Часы, разблокировка часов"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-si-v36/strings.xml b/SafetyCenter/Resources/res/values-si-v36/strings.xml
new file mode 100644
index 000000000..4bf8b1c89
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-si-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"මුහුණ"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"රැකියාව සඳහා මුහුණ"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"මුහුණ අගුළු හැරීම, මුහුණ"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ඇඟිලි සලකුණ"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"කාර්යාලය සඳහා ඇඟිලි සලකුණ"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ඇඟිලි සලකුණ, ඇඟිල්ල, ඇඟිලි සලකුණ එක් කරන්න"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"ඔරලෝසුව"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"රැකියාව සඳහා ඔරලෝසුව"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"ඔරලෝසුව, ඔරලෝසුව අගුළු ඇරීම"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-sk-v36/strings.xml b/SafetyCenter/Resources/res/values-sk-v36/strings.xml
new file mode 100644
index 000000000..ffbc3391e
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sk-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Tvár"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Tvár pre prácu"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Odomknutie tvárou, tvár"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Odtlačok prsta"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Odtlačok prsta pre prácu"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Odtlačok prsta, prst, pridať odtlačok prsta"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Hodinky"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Hodinky pre prácu"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Hodinky, odomknutie hodinkami"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-sk/strings.xml b/SafetyCenter/Resources/res/values-sk/strings.xml
index 6357114cd..042d2dd75 100644
--- a/SafetyCenter/Resources/res/values-sk/strings.xml
+++ b/SafetyCenter/Resources/res/values-sk/strings.xml
@@ -30,7 +30,7 @@
<string name="permission_usage_title" msgid="3633779688945350407">"Panel ochrany súkromia"</string>
<string name="permission_usage_summary" msgid="5323079206029964468">"Zobraziť, ktoré aplikácie nedávno použili povolenia"</string>
<string name="permission_usage_search_terms" msgid="3852343592870257104">"Ochrana súkromia, panel ochrany súkromia"</string>
- <string name="permission_manager_title" msgid="5277347862821255015">"Správca povolení"</string>
+ <string name="permission_manager_title" msgid="5277347862821255015">"Správa povolení"</string>
<string name="permission_manager_summary" msgid="8099852107340970790">"Ovládanie prístupu aplikácií k vašim údajom"</string>
<string name="permission_manager_search_terms" msgid="2895147613099694722">"Povolenia, správca povolení"</string>
<string name="privacy_controls_title" msgid="5322875777945432395">"Nastavenia ochrany súkromia"</string>
diff --git a/SafetyCenter/Resources/res/values-sl-v36/strings.xml b/SafetyCenter/Resources/res/values-sl-v36/strings.xml
new file mode 100644
index 000000000..9f0a32d41
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sl-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Obraz"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Obraz za službo"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Odklepanje z obrazom, obraz"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Prstni odtis"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Prstni odtis za službo"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Prstni odtis, prst, dodajanje prstnega odtisa"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Ura"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Ura za službo"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Ura, odklepanje z uro"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-sq-v36/strings.xml b/SafetyCenter/Resources/res/values-sq-v36/strings.xml
new file mode 100644
index 000000000..ee8d02923
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sq-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Fytyra"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Fytyra për punën"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Shkyçja me fytyrë, fytyra"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Gjurma e gishtit"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Gjurma e gishtit për punën"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Gjurma e gishtit, gishti, shto gjurmë gishti"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Ora"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Ora për punën"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Ora, shkyçja me orë"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-sr-v36/strings.xml b/SafetyCenter/Resources/res/values-sr-v36/strings.xml
new file mode 100644
index 000000000..9a73a0707
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sr-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Лице"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Откључавање лицем за посао"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Откључавање лицем, лице"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Отисак прста"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Отисак прста за посао"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Отисак прста, прст, додај отисак прста"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Сат"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Откључавање сатом за посао"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Сат, откључавање сатом"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-sv-v36/strings.xml b/SafetyCenter/Resources/res/values-sv-v36/strings.xml
new file mode 100644
index 000000000..c79423a8f
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sv-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Ansikte"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Ansikte för jobbet"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Ansiktslås, ansikte"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Fingeravtryck"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Fingeravtryck för jobbet"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Fingeravtryck, finger, lägg till fingeravtryck"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Klocka"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Klocka för jobbet"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Klocka, upplåsning med klockan"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-sw-v36/strings.xml b/SafetyCenter/Resources/res/values-sw-v36/strings.xml
new file mode 100644
index 000000000..61d141ab9
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sw-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Uso"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Uso kwenye wasifu wa kazini"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Kufungua kwa uso, Uso"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Alama ya kidole"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Alama ya kidole kwenye wasifu wa kazini"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Alama ya kidole, Kidole, Weka alama ya kidole"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Saa"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Saa kwenye wasifu wa kazini"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Saa, Kufungua kupitia saa"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ta-v36/strings.xml b/SafetyCenter/Resources/res/values-ta-v36/strings.xml
new file mode 100644
index 000000000..2d81dcc9f
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ta-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"முகம்"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"பணிக்கான முகம்"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"முகம் காட்டித் திறத்தல், முகம்"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"கைரேகை"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"பணிக்கான கைரேகை"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"கைரேகை, விரல், கைரேகையைச் சேர்த்தல்"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"வாட்ச்"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"பணிக்கான வாட்ச்"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"வாட்ச், வாட்ச் அன்லாக்"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-te-v36/strings.xml b/SafetyCenter/Resources/res/values-te-v36/strings.xml
new file mode 100644
index 000000000..ff5785cff
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-te-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"ముఖం"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"ఆఫీస్ కోసం ఫేస్ అన్‌లాక్"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"ఫేస్ అన్‌లాక్, ముఖం"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"వేలిముద్ర"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"ఆఫీస్ కోసం వేలిముద్ర అన్‌లాక్"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"వేలిముద్ర, వేలు, వేలిముద్రను జోడించండి"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"వాచ్"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ఆఫీస్ కోసం వాచ్ అన్‌లాక్"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"వాచ్, వాచ్ అన్‌లాక్"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-th-v36/strings.xml b/SafetyCenter/Resources/res/values-th-v36/strings.xml
new file mode 100644
index 000000000..ca675dee0
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-th-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"ใบหน้า"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"ใช้ใบหน้าสำหรับการทำงาน"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"การปลดล็อกด้วยใบหน้า, ใบหน้า"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"ลายนิ้วมือ"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"ใช้ลายนิ้วมือสำหรับการทำงาน"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"ลายนิ้วมือ, นิ้ว, เพิ่มลายนิ้วมือ"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"นาฬิกา"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"ใช้นาฬิกาเพื่อการทำงาน"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"นาฬิกา, การปลดล็อกด้วยนาฬิกา"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-tl-v36/strings.xml b/SafetyCenter/Resources/res/values-tl-v36/strings.xml
new file mode 100644
index 000000000..ab3854f74
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-tl-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Mukha"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Mukha sa trabaho"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Pag-unlock gamit ang mukha, Mukha"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Fingerprint"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Fingerprint sa trabaho"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Fingerprint, Daliri, Magdagdag ng fingerprint"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Relo"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Relo sa trabaho"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Relo, Pag-unlock gamit ang relo"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-tr-v36/strings.xml b/SafetyCenter/Resources/res/values-tr-v36/strings.xml
new file mode 100644
index 000000000..9bc9110c4
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-tr-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Yüz"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Çalışma için yüz"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Yüz Tanıma Kilidi, Yüz"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Parmak izi"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Çalışma için parmak izi"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Parmak izi, Parmak, Parmak izi ekle"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Kol saati"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Çalışma için kol saati"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Kol saati, Saat ile Kilit Açma"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-uk-v36/strings.xml b/SafetyCenter/Resources/res/values-uk-v36/strings.xml
new file mode 100644
index 000000000..22cc885e6
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-uk-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Обличчя"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Фейс-контроль для роботи"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Фейс-контроль, обличчя"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Відбиток пальця"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Відбиток пальця для роботи"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Відбиток пальця, палець, додати відбиток пальця"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Годинник"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Годинник для роботи"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Годинник, розблокування годинником"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-ur-v36/strings.xml b/SafetyCenter/Resources/res/values-ur-v36/strings.xml
new file mode 100644
index 000000000..0d1b90f12
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ur-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"چہرہ"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"کام کے لیے چہرہ"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"فیس اَنلاک، چہرہ"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"فنگر پرنٹ"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"کام کیلئے فنگر پرنٹ"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"فنگر پرنٹ، انگلی، فنگر پرنٹ شامل کریں"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"گھڑی"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"کام کے لیے گھڑی"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"گھڑی، واچ اَن لاک"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-uz-v36/strings.xml b/SafetyCenter/Resources/res/values-uz-v36/strings.xml
new file mode 100644
index 000000000..06aea14bb
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-uz-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Yuz"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Ish uchun yuz"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Yuz bilan ochish, yuz"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Barmoq izi"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Ish uchun barmoq izi"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Barmoq izi, barmoq, barmoq izi kiritish"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Soat"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Ish uchun soat"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Soat, soat bilan ochish"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-v36/config.xml b/SafetyCenter/Resources/res/values-v36/config.xml
new file mode 100644
index 000000000..6fa28a340
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-v36/config.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Comma separated list of safety source IDs to show in the same task as the safety center -->
+ <string name="config_same_task_safety_source_ids" translatable="false">AndroidAccessibility,AndroidBackgroundLocation,AndroidBiometrics,AndroidFaceUnlock,AndroidFingerprintUnlock,AndroidHealthConnect,AndroidLockScreen,AndroidPrivateSpace,AndroidMoreSettings,AndroidNotificationListener,AndroidPermissionAutoRevoke,AndroidPermissionManager,AndroidPermissionUsage,AndroidPrivacyAppDataSharingUpdates,AndroidPrivacyControls,AndroidWearUnlock,AndroidWorkPolicyInfo</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-v36/strings.xml b/SafetyCenter/Resources/res/values-v36/strings.xml
new file mode 100644
index 000000000..f452e045a
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-v36/strings.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" description="The default title of the setting for managing face unlock options on the device">Face</string>
+ <string name="face_unlock_title_for_work" description="The default title of the setting for managing face unlock options for work on the device">Face for work</string>
+ <string name="face_unlock_title_for_private_profile" description="The default title of the setting for managing face unlock options for private profile on the device"><!-- Empty placeholder--></string>
+ <string name="face_unlock_search_terms" description="Search keywords of the setting for managing face unlock options on the device">Face unlock, Face</string>
+
+ <string name="fingerprint_unlock_title" description="The default title of the setting for managing fingerprint unlock options on the device">Fingerprint</string>
+ <string name="fingerprint_unlock_title_for_work" description="The default title of the setting for managing fingerprint unlock options for work on the device">Fingerprint for work</string>
+ <string name="fingerprint_unlock_title_for_private_profile" description="The default title of the setting for managing fingerprint unlock options for private profile on the device"><!-- Empty placeholder--></string>
+ <string name="fingerprint_unlock_search_terms" description="Search keywords of the setting for managing fingerprint unlock options on the device">Fingerprint, Finger, Add fingerprint</string>
+
+ <string name="wear_unlock_title" description="The default title of the setting for managing wear unlock options on the device">Watch</string>
+ <string name="wear_unlock_title_for_work" description="The default title of the setting for managing wear unlock options for work on the device">Watch for work</string>
+ <string name="wear_unlock_title_for_private_profile" description="The default title of the setting for managing wear unlock options for private profile on the device"><!-- Empty placeholder--></string>
+ <string name="wear_unlock_search_terms" description="Search keywords of the setting for managing wear unlock options on the device">Watch, Watch unlock</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-vi-v36/strings.xml b/SafetyCenter/Resources/res/values-vi-v36/strings.xml
new file mode 100644
index 000000000..3665f3bcf
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-vi-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Khuôn mặt"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Khuôn mặt cho ứng dụng công việc"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Mở khoá bằng khuôn mặt, Khuôn mặt"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Vân tay"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Vân tay cho ứng dụng công việc"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Vân tay, Ngón tay, Thêm vân tay"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Đồng hồ"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Đồng hồ cho ứng dụng công việc"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Đồng hồ, Mở khoá bằng đồng hồ"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-zh-rCN-v36/strings.xml b/SafetyCenter/Resources/res/values-zh-rCN-v36/strings.xml
new file mode 100644
index 000000000..e60d81f96
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-zh-rCN-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"人脸"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"工作专用人脸"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"人脸解锁, 人脸, Face unlock, Face"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"指纹"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"工作专用指纹"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"指纹, 手指, 添加指纹, Fingerprint, Finger, Add fingerprint"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"手表"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"工作专用手表"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"手表, 手表解锁, Watch, Watch unlock"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-zh-rHK-v36/strings.xml b/SafetyCenter/Resources/res/values-zh-rHK-v36/strings.xml
new file mode 100644
index 000000000..55c92891d
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-zh-rHK-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"面孔"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"工作設定檔的面孔"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"面孔解鎖, 面孔"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"指紋"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"工作設定檔的指紋"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"指紋, 手指, 新增指紋"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"手錶"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"工作設定檔的手錶"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"手錶, 手錶解鎖"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-zh-rTW-v36/strings.xml b/SafetyCenter/Resources/res/values-zh-rTW-v36/strings.xml
new file mode 100644
index 000000000..9adfdca6f
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-zh-rTW-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"人臉"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"使用人臉解鎖工作資料夾"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"人臉解鎖, 人臉"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"指紋"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"使用指紋解鎖工作資料夾"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"指紋, 手指, 新增指紋"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"智慧手錶"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"使用智慧手錶解鎖工作資料夾"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"智慧手錶, 智慧手錶解鎖"</string>
+</resources>
diff --git a/SafetyCenter/Resources/res/values-zu-v36/strings.xml b/SafetyCenter/Resources/res/values-zu-v36/strings.xml
new file mode 100644
index 000000000..bba0ffe78
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-zu-v36/strings.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2025 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="face_unlock_title" msgid="3991635517593572926">"Ubuso"</string>
+ <string name="face_unlock_title_for_work" msgid="1451170625947022012">"Ubuso bomsebenzi"</string>
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <string name="face_unlock_search_terms" msgid="2708195853333028283">"Ukuvula ngobuso, Ubuso"</string>
+ <string name="fingerprint_unlock_title" msgid="5579868242026550596">"Isigxivizo somunwe"</string>
+ <string name="fingerprint_unlock_title_for_work" msgid="6343690273672384918">"Isigxivizo somunwe somsebenzi"</string>
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <string name="fingerprint_unlock_search_terms" msgid="688405183240088603">"Isigxivizo somunwe, Umunwe, Faka isigxivizo somunwe"</string>
+ <string name="wear_unlock_title" msgid="1613730442896319515">"Iwashi"</string>
+ <string name="wear_unlock_title_for_work" msgid="3103157953371670280">"Iwashi lomsebenzi"</string>
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <string name="wear_unlock_search_terms" msgid="3769797118448924263">"Iwashi, Ukuvula iwashi"</string>
+</resources>
diff --git a/SafetyCenter/Resources/shared_res/values-ky/strings.xml b/SafetyCenter/Resources/shared_res/values-ky/strings.xml
index 474377b87..152661d8a 100644
--- a/SafetyCenter/Resources/shared_res/values-ky/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ky/strings.xml
@@ -37,7 +37,7 @@
<string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Коркунучтар табылды"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Аккаунт коркунучта болушу мүмкүн"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"Аккаунт коркунучта"</string>
- <string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Эскетүүнү көрүү}other{Эскертүүлөрдү көрүү}}"</string>
+ <string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Эскертүүнү көрүү}other{Эскертүүлөрдү көрүү}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Барак ачылган жок"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Эскертүү чечилген жок"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Параметр текшерилген жок}other{Параметрлер текшерилген жок}}"</string>
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 823738181..5c6f04426 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -45,6 +45,9 @@
"name" : "CtsRoleTestCases"
},
{
+ "name" : "CtsRoleMultiUserTestCases"
+ },
+ {
"name" : "CtsPermissionMultiUserTestCases"
},
{
diff --git a/flags/Android.bp b/flags/Android.bp
index aba1e44a9..166e55492 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -30,14 +30,18 @@ java_aconfig_library {
name: "com.android.permission.flags-aconfig-java-export",
aconfig_declarations: "com.android.permission.flags-aconfig",
mode: "exported",
- sdk_version: "module_current",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
min_sdk_version: "30",
installable: false,
- libs: ["framework-configinfrastructure.stubs.module_lib"],
visibility: [
+ "//frameworks/base",
"//packages/modules/Permission:__subpackages__",
+ "//packages/modules/Nfc:__subpackages__",
+ "//vendor:__subpackages__",
],
apex_available: [
+ "//apex_available:platform",
+ "com.android.nfcservices",
"com.android.permission",
"test_com.android.permission",
],
diff --git a/flags/flags.aconfig b/flags/flags.aconfig
index c94614654..866d27d43 100644
--- a/flags/flags.aconfig
+++ b/flags/flags.aconfig
@@ -65,15 +65,6 @@ flag {
}
flag {
- name: "livedata_refactor_permission_timeline_enabled"
- is_exported: true
- namespace: "permissions"
- description: "This flag is used to enable modern app architecture implementation for timeline page"
- bug: "354234946"
- is_fixed_read_only: true
-}
-
-flag {
name: "odad_notifications_supported"
is_exported: true
namespace: "permissions"
@@ -104,7 +95,73 @@ flag {
name: "app_permission_fragment_uses_preferences"
is_exported: true
namespace: "permissions"
- description: "This flag enables AppPermissionFragment rather than LegacyAppPermissionFragment (to support BC25)"
+ description: "This flag enables AppPermissionFragment rather than LegacyAppPermissionFragment"
bug: "349675008"
is_fixed_read_only: true
}
+
+flag {
+ name: "cross_user_role_enabled"
+ is_exported: true
+ namespace: "permissions"
+ description: "This flag enables cross-user roles support and API"
+ bug: "367732307"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "cross_user_role_ux_bugfix_enabled"
+ is_exported: true
+ namespace: "permissions"
+ description: "This flag enables cross-user roles support ux bug fixes"
+ bug: "367732307"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "permission_timeline_attribution_label_fix"
+ is_exported: true
+ namespace: "permissions"
+ description: "This flag is used for the attribution label fix on permission timeline page"
+ bug: "369606734"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "wear_compose_material3"
+ is_exported: true
+ namespace: "permissions"
+ description: "This flag enables material3 design system for wear ui components"
+ bug: "370489422"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "decluttered_permission_manager_enabled"
+ is_exported: true
+ namespace: "permissions"
+ description: "Enables displaying unused permission groups in the additional page, instead of displaying them in the main permission manager page"
+ bug: "365823624"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "expressive_design_enabled"
+ is_exported: true
+ namespace: "permissions"
+ description: "This flag is used to enable Expressive Design for Settings pages inside PermissionController"
+ bug: "375480184"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "default_apps_recommendation_enabled"
+ is_exported: true
+ namespace: "permissions"
+ description: "This flag enables the recommended section in default apps"
+ bug: "388234667"
+ is_fixed_read_only: true
+}
diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt
index e15887576..c5d971435 100644
--- a/framework-s/api/system-current.txt
+++ b/framework-s/api/system-current.txt
@@ -37,7 +37,9 @@ package android.app.role {
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") @Nullable @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, android.Manifest.permission.MANAGE_ROLE_HOLDERS, android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS}, conditional=true) public android.os.UserHandle getActiveUserForRole(@NonNull String);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS) public String getDefaultApplication(@NonNull String);
+ method @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getDefaultHoldersForTest(@NonNull String);
method @Deprecated @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
@@ -45,18 +47,24 @@ package android.app.role {
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public boolean isBypassingRoleQualification();
method @FlaggedApi("android.permission.flags.system_server_role_controller_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public boolean isRoleFallbackEnabled(@NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void isRoleVisible(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public boolean isRoleVisibleForTest(@NonNull String);
method @RequiresPermission(android.Manifest.permission.OBSERVE_ROLE_HOLDERS) public void removeOnRoleHoldersChangedListenerAsUser(@NonNull android.app.role.OnRoleHoldersChangedListener, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void removeRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean removeRoleHolderFromController(@NonNull String, @NonNull String);
+ method @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") @RequiresPermission(allOf={android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, android.Manifest.permission.MANAGE_ROLE_HOLDERS, android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS}, conditional=true) public void setActiveUserForRole(@NonNull String, @NonNull android.os.UserHandle, int);
method @RequiresPermission(android.Manifest.permission.BYPASS_ROLE_QUALIFICATION) public void setBypassingRoleQualification(boolean);
method @RequiresPermission(android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS) public void setDefaultApplication(@NonNull String, @Nullable String, int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void setDefaultHoldersForTest(@NonNull String, @NonNull java.util.List<java.lang.String>);
method @FlaggedApi("android.permission.flags.system_server_role_controller_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void setRoleFallbackEnabled(@NonNull String, boolean);
method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
+ method @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void setRoleVisibleForTest(@NonNull String, boolean);
field public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1; // 0x1
field public static final String ROLE_DEVICE_POLICY_MANAGEMENT = "android.app.role.DEVICE_POLICY_MANAGEMENT";
field public static final String ROLE_FINANCED_DEVICE_KIOSK = "android.app.role.FINANCED_DEVICE_KIOSK";
+ field @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") public static final String ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY = "android.app.role.RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY";
field public static final String ROLE_SYSTEM_ACTIVITY_RECOGNIZER = "android.app.role.SYSTEM_ACTIVITY_RECOGNIZER";
field public static final String ROLE_SYSTEM_CALL_STREAMING = "android.app.role.SYSTEM_CALL_STREAMING";
+ field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String ROLE_SYSTEM_DEPENDENCY_INSTALLER = "android.app.role.SYSTEM_DEPENDENCY_INSTALLER";
field public static final String ROLE_SYSTEM_SUPERVISION = "android.app.role.SYSTEM_SUPERVISION";
field public static final String ROLE_SYSTEM_WELLBEING = "android.app.role.SYSTEM_WELLBEING";
}
diff --git a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java
index 74062165e..4248a429c 100644
--- a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java
+++ b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java
@@ -20,6 +20,7 @@ import static android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_AC
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
@@ -32,18 +33,17 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.permission.flags.Flags;
import android.util.ArraySet;
-import androidx.annotation.NonNull;
-
import java.lang.annotation.Retention;
/**
* This class provides the core API for ECM (Enhanced Confirmation Mode). ECM is a feature that
* restricts access to protected **settings** (i.e., sensitive resources) by restricted **apps**
* (apps from from dangerous sources, such as sideloaded packages or packages downloaded from a web
- * browser).
+ * browser), or restricts settings globally based on device state.
*
* <p>Specifically, this class provides the ability to:
*
@@ -71,6 +71,9 @@ import java.lang.annotation.Retention;
* particular app restricted is an implementation detail of ECM. However, the user is able to
* clear any restricted app's restriction status (i.e, un-restrict it), after which ECM will
* consider the app **not restricted**.
+ * <li>A setting may be globally restricted based on device state. In this case, any app may be
+ * automatically considered *restricted*, regardless of the app's restriction state. Users
+ * cannot un-restrict the app, in these cases.
* </ol>
*
* Why is ECM needed? Consider the following (pre-ECM) scenario:
@@ -200,6 +203,19 @@ public final class EnhancedConfirmationManager {
public static final String ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG =
"android.app.ecm.action.SHOW_ECM_RESTRICTED_SETTING_DIALOG";
+ /**
+ * The setting is restricted because of the phone state of the device
+ * @hide
+ */
+ public static final String REASON_PHONE_STATE = "phone_state";
+
+ /**
+ * The setting is restricted because the restricted app op is set for the given package
+ * @hide
+ */
+ public static final String REASON_PACKAGE_RESTRICTED = "package_restricted";
+
+
/** A map of ECM states to their corresponding app op states */
@Retention(java.lang.annotation.RetentionPolicy.SOURCE)
@IntDef(prefix = {"ECM_STATE_"}, value = {EcmState.ECM_STATE_NOT_GUARDED,
@@ -313,6 +329,9 @@ public final class EnhancedConfirmationManager {
* <p>This should be called from the "Restricted setting" dialog (which {@link
* #createRestrictedSettingDialogIntent} directs to) upon being presented to the user.
*
+ * <p>This restriction clearing does not apply to any settings that are restricted based on
+ * global device state
+ *
* @param packageName package name of the application which should be considered acknowledged
* @throws NameNotFoundException if the provided package was not found
*/
@@ -344,8 +363,17 @@ public final class EnhancedConfirmationManager {
@NonNull String settingIdentifier) throws NameNotFoundException {
Intent intent = new Intent(ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
- intent.putExtra(Intent.EXTRA_UID, getPackageUid(packageName));
+ int uid = getPackageUid(packageName);
+ intent.putExtra(Intent.EXTRA_UID, uid);
intent.putExtra(Intent.EXTRA_SUBJECT, settingIdentifier);
+ try {
+ String restrictionReason = mService.getRestrictionReason(packageName,
+ settingIdentifier, UserHandle.getUserHandleForUid(uid).getIdentifier());
+ intent.putExtra(Intent.EXTRA_REASON, restrictionReason);
+ } catch (SecurityException | RemoteException e) {
+ // The caller of this method does not have permission to read the ECM state, so we
+ // won't include it in the return
+ }
return intent;
}
diff --git a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl
index 5149daa49..79d2322bd 100644
--- a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl
+++ b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl
@@ -25,6 +25,8 @@ interface IEnhancedConfirmationManager {
boolean isRestricted(in String packageName, in String settingIdentifier, int userId);
+ String getRestrictionReason(in String packageName, in String settingIdentifier, int userId);
+
void clearRestriction(in String packageName, int userId);
boolean isClearRestrictionAllowed(in String packageName, int userId);
diff --git a/framework-s/java/android/app/role/IRoleManager.aidl b/framework-s/java/android/app/role/IRoleManager.aidl
index 522967630..cf98a6016 100644
--- a/framework-s/java/android/app/role/IRoleManager.aidl
+++ b/framework-s/java/android/app/role/IRoleManager.aidl
@@ -45,6 +45,10 @@ interface IRoleManager {
void setDefaultApplicationAsUser(in String roleName, in String packageName, int flags,
int userId, in RemoteCallback callback);
+ int getActiveUserForRoleAsUser(in String roleName, int userId);
+
+ void setActiveUserForRoleAsUser(in String roleName, int activeUserId, int flags, int userId);
+
void addOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener, int userId);
void removeOnRoleHoldersChangedListenerAsUser(IOnRoleHoldersChangedListener listener,
@@ -80,4 +84,13 @@ interface IRoleManager {
boolean isApplicationVisibleForRoleAsUser(in String roleName, in String packageName,
int userId);
+
+ List<String> getDefaultHoldersForTestAsUser(in String roleName, int userId);
+
+ void setDefaultHoldersForTestAsUser(in String roleName, in List<String> packageNames,
+ int userId);
+
+ boolean isRoleVisibleForTestAsUser(in String roleName, int userId);
+
+ void setRoleVisibleForTestAsUser(in String roleName, boolean visible, int userId);
}
diff --git a/framework-s/java/android/app/role/RoleManager.java b/framework-s/java/android/app/role/RoleManager.java
index 4b8c9b388..9f28b7f19 100644
--- a/framework-s/java/android/app/role/RoleManager.java
+++ b/framework-s/java/android/app/role/RoleManager.java
@@ -40,6 +40,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.permission.flags.Flags;
+import android.permission.internal.compat.UserHandleCompat;
import android.util.ArrayMap;
import android.util.SparseArray;
@@ -211,6 +212,16 @@ public final class RoleManager {
"android.app.role.SYSTEM_CALL_STREAMING";
/**
+ * The name of the role used for testing cross-user roles.
+ *
+ * @hide
+ */
+ @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SystemApi
+ public static final String ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY =
+ "android.app.role.RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY";
+
+ /**
* @hide
*/
@IntDef(flag = true, value = { MANAGE_HOLDERS_FLAG_DONT_KILL_APP })
@@ -253,6 +264,19 @@ public final class RoleManager {
public static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER =
"com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER";
+ /**
+ * The name of the system dependency installer role.
+ *
+ * A dependency installer installs missing SDK or static shared library dependencies that an app
+ * requires to be installed.
+ *
+ * @hide
+ */
+ @FlaggedApi("android.content.pm.sdk_dependency_installer")
+ @SystemApi
+ public static final String ROLE_SYSTEM_DEPENDENCY_INSTALLER =
+ "android.app.role.SYSTEM_DEPENDENCY_INSTALLER";
+
@NonNull
private final Context mContext;
@@ -574,6 +598,87 @@ public final class RoleManager {
}
}
+ /**
+ * Get the {@link UserHandle} of the user who that is the active user for the specified role.
+ * <p>
+ * Only profile-group exclusive roles can be used with this method, and they will
+ * have one active user within a profile group.
+ * <p>
+ * <strong>Note:</strong> Using this API requires holding
+ * {@code android.permission.INTERACT_ACROSS_USERS_FULL} and one of
+ * {@code android.permission.MANAGE_ROLE_HOLDERS} or
+ * {@code android.permission.MANAGE_DEFAULT_APPLICATIONS}.
+ *
+ * @param roleName the name of the role to get the active user for
+ *
+ * @return a {@link UserHandle} of the active user for the specified role
+ *
+ * @see #setActiveUserForRole(String, UserHandle, int)
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.MANAGE_ROLE_HOLDERS,
+ Manifest.permission.MANAGE_DEFAULT_APPLICATIONS},
+ conditional = true)
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ @SystemApi
+ @UserHandleAware
+ @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Nullable
+ public UserHandle getActiveUserForRole(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ try {
+ int userId = mService.getActiveUserForRoleAsUser(roleName,
+ mContext.getUser().getIdentifier());
+ return userId == UserHandleCompat.USER_NULL ? null : UserHandle.of(userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set a specific user as active user for a role.
+ * <p>
+ * Only profile-group exclusive roles can be used with this method, and they will have
+ * one active user within a profile group.
+ * <p>
+ * <strong>Note:</strong> Using this API requires holding
+ * {@code android.permission.INTERACT_ACROSS_USERS_FULL} and one of
+ * {@code android.permission.MANAGE_ROLE_HOLDERS} or
+ * {@code android.permission.MANAGE_DEFAULT_APPLICATIONS}.
+ *
+ * @param roleName the name of the role to set the active user for
+ * @param user the user to set as active user for specified role
+ * @param flags optional behavior flags
+ *
+ * @see #getActiveUserForRole(String)
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ Manifest.permission.MANAGE_ROLE_HOLDERS,
+ Manifest.permission.MANAGE_DEFAULT_APPLICATIONS},
+ conditional = true)
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ @SystemApi
+ @UserHandleAware
+ @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ // The user handle parameter is a value to be set by this method, while the context user of the
+ // operation is indeed read from the context
+ @SuppressLint("UserHandle")
+ public void setActiveUserForRole(
+ @NonNull String roleName, @NonNull UserHandle user, @ManageHoldersFlags int flags) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Objects.requireNonNull(user, "user cannot be null");
+ try {
+ mService.setActiveUserForRoleAsUser(roleName, user.getIdentifier(), flags,
+ mContext.getUser().getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
private static RemoteCallback createRemoteCallback(@NonNull Executor executor,
@NonNull Consumer<Boolean> callback) {
@@ -1074,6 +1179,124 @@ public final class RoleManager {
}
}
+ /**
+ * Get the default holders of this role, which will be added when the role is added for the
+ * first time.
+ * <p>
+ * <strong>Note:</strong> Use of this API should be limited to tests. The values returned are
+ * not persisted.
+ * <p>
+ * Throws {@link IllegalArgumentException} if role is not a test role
+ *
+ * @param roleName the name of the role to get test default holders for
+ * @return the list of package names of the default holders
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ @SystemApi
+ @UserHandleAware
+ @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @NonNull
+ public List<String> getDefaultHoldersForTest(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ try {
+ return mService.getDefaultHoldersForTestAsUser(roleName,
+ mContext.getUser().getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set the default holders of this role, which will be added when the role is added for the
+ * first time.
+ * <p>
+ * <strong>Note:</strong> Use of this API should be limited to tests. The values used are
+ * not persisted.
+ * <p>
+ * Throws {@link IllegalArgumentException} if role is not a test role
+ * Throws {@link NullPointerException} if packageNames is {@code null}
+ *
+ * @param roleName the name of the role to set test default holders for
+ * @param packageNames a list of package names of the default holders, or an empty list to unset
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ @SystemApi
+ @UserHandleAware
+ @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ public void setDefaultHoldersForTest(
+ @NonNull String roleName, @NonNull List<String> packageNames) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Objects.requireNonNull(packageNames, "packageNames cannot be null");
+ try {
+ mService.setDefaultHoldersForTestAsUser(roleName, packageNames,
+ mContext.getUser().getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Get whether a role should be visible for testing.
+ * <p>
+ * <strong>Note:</strong> Use of this API should be limited to tests. The values returned are
+ * not persisted.
+ * <p>
+ * Throws {@link IllegalArgumentException} if role is not a test role
+ *
+ * @param roleName the name of the role to get test visibility for
+ * @return {@code true} if role is visible, {@code false} otherwise
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ @SystemApi
+ @UserHandleAware
+ @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ public boolean isRoleVisibleForTest(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ try {
+ return mService.isRoleVisibleForTestAsUser(roleName,
+ mContext.getUser().getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Set whether a role should be visible for testing.
+ * <p>
+ * <strong>Note:</strong> Use of this API should be limited to tests. The values used are
+ * not persisted.
+ * <p>
+ * Throws {@link IllegalArgumentException} if role is not a test role
+ *
+ * @param roleName the name of the role to set test visibility for
+ * @param visible {@code true} to set role as visible, {@code false} otherwise
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ROLE_HOLDERS)
+ @RequiresApi(Build.VERSION_CODES.BAKLAVA)
+ @SystemApi
+ @UserHandleAware
+ @FlaggedApi(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ public void setRoleVisibleForTest(@NonNull String roleName, boolean visible) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ try {
+ mService.setRoleVisibleForTestAsUser(roleName, visible,
+ mContext.getUser().getIdentifier());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
@NonNull
private RoleControllerManager getRoleControllerManager() {
synchronized (mRoleControllerManagerLock) {
diff --git a/framework-s/java/android/app/role/TEST_MAPPING b/framework-s/java/android/app/role/TEST_MAPPING
index 46b148e68..62c07e5d9 100644
--- a/framework-s/java/android/app/role/TEST_MAPPING
+++ b/framework-s/java/android/app/role/TEST_MAPPING
@@ -7,6 +7,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases"
}
],
"mainline-presubmit": [
@@ -24,6 +27,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]"
}
],
"permission-mainline-presubmit": [
@@ -41,6 +47,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases"
}
],
"postsubmit": [
diff --git a/service/java/com/android/permission/compat/UserHandleCompat.java b/framework-s/java/android/permission/internal/compat/UserHandleCompat.java
index 1901aa997..8a3ec444d 100644
--- a/service/java/com/android/permission/compat/UserHandleCompat.java
+++ b/framework-s/java/android/permission/internal/compat/UserHandleCompat.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permission.compat;
+package android.permission.internal.compat;
import android.annotation.UserIdInt;
import android.os.UserHandle;
@@ -29,6 +29,13 @@ public final class UserHandleCompat {
public static final int USER_ALL = UserHandle.ALL.getIdentifier();
/**
+ * A user ID to indicate an undefined user of the device.
+ *
+ * @see UserHandle#USER_NULL
+ */
+ public static final @UserIdInt int USER_NULL = -10000;
+
+ /**
* A user ID to indicate the "system" user of the device.
*/
public static final int USER_SYSTEM = UserHandle.SYSTEM.getIdentifier();
diff --git a/service/java/com/android/permission/compat/package-info.java b/framework-s/java/android/permission/internal/compat/package-info.java
index c89cc8eab..b78aac878 100644
--- a/service/java/com/android/permission/compat/package-info.java
+++ b/framework-s/java/android/permission/internal/compat/package-info.java
@@ -19,4 +19,4 @@
* TODO(b/146466118) remove this javadoc tag
*/
@android.annotation.Hide
-package com.android.permission.compat;
+package android.permission.internal.compat;
diff --git a/service/Android.bp b/service/Android.bp
index 6f851c4d2..b6e85b0fc 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -101,6 +101,7 @@ java_sdk_library {
"modules-utils-backgroundthread",
"modules-utils-build",
"modules-utils-os",
+ // framework-permission-s already includes com.android.permission.flags-aconfig-java
"role-controller",
"safety-center-config",
"safety-center-internal-data",
diff --git a/service/api/system-server-current.txt b/service/api/system-server-current.txt
index 30fbab484..a3db89370 100644
--- a/service/api/system-server-current.txt
+++ b/service/api/system-server-current.txt
@@ -1,4 +1,18 @@
// Signature format: 2.0
+package com.android.ecm {
+
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") public class EnhancedConfirmationCallTrackerService extends android.telecom.InCallService {
+ ctor public EnhancedConfirmationCallTrackerService();
+ }
+
+ @FlaggedApi("android.permission.flags.enhanced_confirmation_in_call_apis_enabled") public interface EnhancedConfirmationManagerLocal {
+ method public void addOngoingCall(@NonNull android.telecom.Call);
+ method public void clearOngoingCalls();
+ method public void removeOngoingCall(@NonNull String);
+ }
+
+}
+
package com.android.permission.persistence {
public interface RuntimePermissionsPersistence {
diff --git a/service/jarjar-rules.txt b/service/jarjar-rules.txt
index 4d4d6e050..bad5c7666 100644
--- a/service/jarjar-rules.txt
+++ b/service/jarjar-rules.txt
@@ -2,6 +2,10 @@
# RoleParser.applyJarjarTransform(), by adding NO_IFTTT=reason to your commit
# message.
# LINT.IfChange
+rule android.app.admin.flags.*FeatureFlags* com.android.permission.jarjar.@0
+rule android.app.admin.flags.FeatureFlags* com.android.permission.jarjar.@0
+rule android.app.admin.flags.FeatureFlags com.android.permission.jarjar.@0
+rule android.app.admin.flags.Flags com.android.permission.jarjar.@0
rule android.app.appfunctions.flags.*FeatureFlags* com.android.permission.jarjar.@0
rule android.app.appfunctions.flags.FeatureFlags* com.android.permission.jarjar.@0
rule android.app.appfunctions.flags.FeatureFlags com.android.permission.jarjar.@0
@@ -10,6 +14,10 @@ rule android.companion.virtualdevice.flags.*FeatureFlags* com.android.permission
rule android.companion.virtualdevice.flags.FeatureFlags* com.android.permission.jarjar.@0
rule android.companion.virtualdevice.flags.FeatureFlags com.android.permission.jarjar.@0
rule android.companion.virtualdevice.flags.Flags com.android.permission.jarjar.@0
+rule android.content.pm.*FeatureFlags* com.android.permission.jarjar.@0
+rule android.content.pm.FeatureFlags* com.android.permission.jarjar.@0
+rule android.content.pm.FeatureFlags com.android.permission.jarjar.@0
+rule android.content.pm.Flags com.android.permission.jarjar.@0
rule android.os.*FeatureFlags* com.android.permission.jarjar.@0
rule android.os.FeatureFlags* com.android.permission.jarjar.@0
rule android.os.FeatureFlags com.android.permission.jarjar.@0
@@ -34,6 +42,10 @@ rule com.android.safetycenter.annotations.** com.android.permission.jarjar.@0
rule com.android.safetycenter.internaldata.** com.android.permission.jarjar.@0
rule com.android.safetycenter.pendingintents.** com.android.permission.jarjar.@0
rule com.android.safetycenter.resources.** com.android.permission.jarjar.@0
+rule com.android.settingslib.flags.*FeatureFlags* com.android.permission.jarjar.@0
+rule com.android.settingslib.flags.FeatureFlags* com.android.permission.jarjar.@0
+rule com.android.settingslib.flags.FeatureFlags com.android.permission.jarjar.@0
+rule com.android.settingslib.flags.Flags com.android.permission.jarjar.@0
rule com.google.protobuf.** com.android.permission.jarjar.@0
rule kotlin.** com.android.permission.jarjar.@0
rule com.android.permissioncontroller.PermissionControllerStatsLog com.android.permission.jarjar.@0
diff --git a/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java b/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java
new file mode 100644
index 000000000..9117d6558
--- /dev/null
+++ b/service/java/com/android/ecm/EnhancedConfirmationCallTrackerService.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ecm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TargetApi;
+import android.content.Intent;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.telecom.Call;
+import android.telecom.InCallService;
+
+import com.android.server.LocalManagerRegistry;
+
+/**
+ * @hide
+ *
+ * This InCallService tracks called (both incoming and outgoing), and sends their information to the
+ * EnhancedConfirmationService
+ *
+ **/
+@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED)
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
+public class EnhancedConfirmationCallTrackerService extends InCallService {
+ private EnhancedConfirmationManagerLocal mEnhancedConfirmationManagerLocal;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (Flags.unknownCallPackageInstallBlockingEnabled()) {
+ mEnhancedConfirmationManagerLocal =
+ LocalManagerRegistry.getManager(EnhancedConfirmationManagerLocal.class);
+ }
+ }
+
+ @Override
+ public void onCallAdded(@Nullable Call call) {
+ if (mEnhancedConfirmationManagerLocal == null || call == null) {
+ return;
+ }
+
+ mEnhancedConfirmationManagerLocal.addOngoingCall(call);
+ }
+
+ @Override
+ public void onCallRemoved(@Nullable Call call) {
+ if (mEnhancedConfirmationManagerLocal == null || call == null) {
+ return;
+ }
+
+ mEnhancedConfirmationManagerLocal.removeOngoingCall(call.getDetails().getId());
+ }
+
+ /**
+ * When unbound, we should assume all calls have finished. Notify the system of such.
+ */
+ public boolean onUnbind(@Nullable Intent intent) {
+ if (mEnhancedConfirmationManagerLocal != null) {
+ mEnhancedConfirmationManagerLocal.clearOngoingCalls();
+ }
+ return super.onUnbind(intent);
+ }
+}
diff --git a/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java b/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java
new file mode 100644
index 000000000..483071716
--- /dev/null
+++ b/service/java/com/android/ecm/EnhancedConfirmationManagerLocal.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ecm;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.telecom.Call;
+
+/**
+ * @hide
+ *
+ * In-process API for the Enhanced Confirmation Service
+ */
+@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_IN_CALL_APIS_ENABLED)
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
+public interface EnhancedConfirmationManagerLocal {
+ /**
+ * Inform the enhanced confirmation service of an ongoing call
+ *
+ * @param call The call to potentially track
+ *
+ */
+ void addOngoingCall(@NonNull Call call);
+
+ /**
+ * Inform the enhanced confirmation service that a call has ended
+ *
+ * @param callId The ID of the call to stop tracking
+ *
+ */
+ void removeOngoingCall(@NonNull String callId);
+
+ /**
+ * Informs the enhanced confirmation service it should clear out any ongoing calls
+ */
+ void clearOngoingCalls();
+}
diff --git a/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java b/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java
new file mode 100644
index 000000000..b34eac9f4
--- /dev/null
+++ b/service/java/com/android/ecm/EnhancedConfirmationManagerLocalImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ecm;
+
+import android.annotation.NonNull;
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.telecom.Call;
+
+/** @hide */
+@TargetApi(Build.VERSION_CODES.BAKLAVA)
+class EnhancedConfirmationManagerLocalImpl implements EnhancedConfirmationManagerLocal {
+
+ private final EnhancedConfirmationService mService;
+
+ EnhancedConfirmationManagerLocalImpl(EnhancedConfirmationService service) {
+ if (Flags.unknownCallPackageInstallBlockingEnabled()) {
+ mService = service;
+ } else {
+ mService = null;
+ }
+ }
+
+ @Override
+ public void addOngoingCall(@NonNull Call call) {
+ if (mService != null) {
+ mService.addOngoingCall(call);
+ }
+ }
+
+ @Override
+ public void removeOngoingCall(@NonNull String callId) {
+ if (mService != null) {
+ mService.removeOngoingCall(callId);
+ }
+ }
+
+ @Override
+ public void clearOngoingCalls() {
+ if (mService != null) {
+ mService.clearOngoingCalls();
+ }
+ }
+}
diff --git a/service/java/com/android/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java
index 708884e85..fc0ed20d0 100644
--- a/service/java/com/android/ecm/EnhancedConfirmationService.java
+++ b/service/java/com/android/ecm/EnhancedConfirmationService.java
@@ -16,14 +16,22 @@
package com.android.ecm;
+import static android.app.ecm.EnhancedConfirmationManager.REASON_PACKAGE_RESTRICTED;
+import static android.app.ecm.EnhancedConfirmationManager.REASON_PHONE_STATE;
+
import android.Manifest;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.AppOpsManager;
import android.app.ecm.EnhancedConfirmationManager;
import android.app.ecm.IEnhancedConfirmationManager;
import android.app.role.RoleManager;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.InstallSourceInfo;
@@ -31,25 +39,35 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.SignedPackage;
+import android.database.Cursor;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.SystemConfigManager;
import android.os.UserHandle;
import android.permission.flags.Flags;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.PhoneLookup;
+import android.telecom.Call;
+import android.telecom.PhoneAccount;
+import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
+import android.view.accessibility.AccessibilityManager;
import androidx.annotation.Keep;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.permission.util.UserUtils;
+import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -66,16 +84,39 @@ import java.util.Set;
@Keep
@FlaggedApi(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+@SuppressLint("MissingPermission")
public class EnhancedConfirmationService extends SystemService {
private static final String LOG_TAG = EnhancedConfirmationService.class.getSimpleName();
private Map<String, List<byte[]>> mTrustedPackageCertDigests;
private Map<String, List<byte[]>> mTrustedInstallerCertDigests;
+ // A map of call ID to call type
+ private final Map<String, Integer> mOngoingCalls = new ArrayMap<>();
+
+ private static final int CALL_TYPE_UNTRUSTED = 0;
+ private static final int CALL_TYPE_TRUSTED = 1;
+ private static final int CALL_TYPE_EMERGENCY = 1 << 1;
+ @IntDef(flag = true, value = {
+ CALL_TYPE_UNTRUSTED,
+ CALL_TYPE_TRUSTED,
+ CALL_TYPE_EMERGENCY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface CallType {}
public EnhancedConfirmationService(@NonNull Context context) {
super(context);
+ LocalManagerRegistry.addManager(EnhancedConfirmationManagerLocal.class,
+ new EnhancedConfirmationManagerLocalImpl(this));
}
+ private ContentResolver mContentResolver;
+ private TelephonyManager mTelephonyManager;
+
+ @GuardedBy("mUserAccessibilityManagers")
+ private final Map<Integer, AccessibilityManager> mUserAccessibilityManagers =
+ new ArrayMap<>();
+
@Override
public void onStart() {
Context context = getContext();
@@ -87,6 +128,8 @@ public class EnhancedConfirmationService extends SystemService {
systemConfigManager.getEnhancedConfirmationTrustedInstallers());
publishBinderService(Context.ECM_ENHANCED_CONFIRMATION_SERVICE, new Stub());
+ mContentResolver = getContext().getContentResolver();
+ mTelephonyManager = getContext().getSystemService(TelephonyManager.class);
}
private Map<String, List<byte[]>> toTrustedPackageMap(Set<SignedPackage> signedPackages) {
@@ -99,6 +142,97 @@ public class EnhancedConfirmationService extends SystemService {
return trustedPackageMap;
}
+ void addOngoingCall(Call call) {
+ if (!Flags.unknownCallPackageInstallBlockingEnabled()) {
+ return;
+ }
+ if (call.getDetails() == null) {
+ return;
+ }
+ mOngoingCalls.put(call.getDetails().getId(), getCallType(call));
+ }
+
+ void removeOngoingCall(String callId) {
+ if (!Flags.unknownCallPackageInstallBlockingEnabled()) {
+ return;
+ }
+ Integer returned = mOngoingCalls.remove(callId);
+ if (returned == null) {
+ // TODO b/379941144: Capture a bug report whenever this happens.
+ }
+ }
+
+ void clearOngoingCalls() {
+ mOngoingCalls.clear();
+ }
+
+ private @CallType int getCallType(Call call) {
+ String number = getPhoneNumber(call);
+ try {
+ if (number != null && mTelephonyManager.isEmergencyNumber(number)) {
+ return CALL_TYPE_EMERGENCY;
+ }
+ } catch (IllegalStateException | UnsupportedOperationException e) {
+ // If either of these are thrown, the telephony service is not available on the current
+ // device, either because the device lacks telephony calling, or the telephony service
+ // is unavailable.
+ }
+ if (number != null) {
+ return hasContactWithPhoneNumber(number) ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED;
+ } else {
+ return hasContactWithDisplayName(call.getDetails().getCallerDisplayName())
+ ? CALL_TYPE_TRUSTED : CALL_TYPE_UNTRUSTED;
+ }
+ }
+
+ private String getPhoneNumber(Call call) {
+ Uri handle = call.getDetails().getHandle();
+ if (handle == null || handle.getScheme() == null) {
+ return null;
+ }
+ if (!handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
+ return null;
+ }
+ return handle.getSchemeSpecificPart();
+ }
+
+ private boolean hasContactWithPhoneNumber(String phoneNumber) {
+ if (phoneNumber == null) {
+ return false;
+ }
+ Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
+ Uri.encode(phoneNumber));
+ String[] projection = new String[]{
+ PhoneLookup.DISPLAY_NAME,
+ ContactsContract.PhoneLookup._ID
+ };
+ try (Cursor res = mContentResolver.query(uri, projection, null, null)) {
+ return res != null && res.getCount() > 0;
+ }
+ }
+
+ private boolean hasContactWithDisplayName(String displayName) {
+ if (displayName == null) {
+ return false;
+ }
+ Uri uri = ContactsContract.Data.CONTENT_URI;
+ String[] projection = new String[]{PhoneLookup._ID};
+ String selection = StructuredName.DISPLAY_NAME + " = ?";
+ String[] selectionArgs = new String[]{displayName};
+ try (Cursor res = mContentResolver.query(uri, projection, selection, selectionArgs, null)) {
+ return res != null && res.getCount() > 0;
+ }
+ }
+
+ private boolean hasCallOfType(@CallType int callType) {
+ for (int ongoingCallType : mOngoingCalls.values()) {
+ if (ongoingCallType == callType) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private class Stub extends IEnhancedConfirmationManager.Stub {
/** A map of ECM states to their corresponding app op states */
@@ -113,28 +247,40 @@ public class EnhancedConfirmationService extends SystemService {
int ECM_STATE_IMPLICIT = AppOpsManager.MODE_DEFAULT;
}
- private static final ArraySet<String> PROTECTED_SETTINGS = new ArraySet<>();
+ private static final ArraySet<String> PER_PACKAGE_PROTECTED_SETTINGS = new ArraySet<>();
+
+ // Settings restricted when an untrusted call is ongoing. These must also be added to
+ // PROTECTED_SETTINGS
+ private static final ArraySet<String> UNTRUSTED_CALL_RESTRICTED_SETTINGS = new ArraySet<>();
static {
// Runtime permissions
- PROTECTED_SETTINGS.add(Manifest.permission.SEND_SMS);
- PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_SMS);
- PROTECTED_SETTINGS.add(Manifest.permission.READ_SMS);
- PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_MMS);
- PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_WAP_PUSH);
- PROTECTED_SETTINGS.add(Manifest.permission.READ_CELL_BROADCASTS);
- PROTECTED_SETTINGS.add(Manifest.permission_group.SMS);
-
- PROTECTED_SETTINGS.add(Manifest.permission.BIND_DEVICE_ADMIN);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.SEND_SMS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_SMS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.READ_SMS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_MMS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_WAP_PUSH);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.READ_CELL_BROADCASTS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission_group.SMS);
+
+ PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.BIND_DEVICE_ADMIN);
// App ops
- PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
- PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
- PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
- PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_GET_USAGE_STATS);
- PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_GET_USAGE_STATS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
// Default application roles.
- PROTECTED_SETTINGS.add(RoleManager.ROLE_DIALER);
- PROTECTED_SETTINGS.add(RoleManager.ROLE_SMS);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(RoleManager.ROLE_DIALER);
+ PER_PACKAGE_PROTECTED_SETTINGS.add(RoleManager.ROLE_SMS);
+
+ if (Flags.unknownCallPackageInstallBlockingEnabled()) {
+ // Requesting package installs, limited during phone calls
+ UNTRUSTED_CALL_RESTRICTED_SETTINGS.add(
+ AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES);
+ UNTRUSTED_CALL_RESTRICTED_SETTINGS.add(
+ AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
+ }
}
private final @NonNull Context mContext;
@@ -152,10 +298,16 @@ public class EnhancedConfirmationService extends SystemService {
public boolean isRestricted(@NonNull String packageName, @NonNull String settingIdentifier,
@UserIdInt int userId) {
+ return getRestrictionReason(packageName, settingIdentifier, userId) != null;
+ }
+
+ public String getRestrictionReason(@NonNull String packageName,
+ @NonNull String settingIdentifier,
+ @UserIdInt int userId) {
enforcePermissions("isRestricted", userId);
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
- return false;
+ return null;
}
Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty");
@@ -163,8 +315,18 @@ public class EnhancedConfirmationService extends SystemService {
"settingIdentifier cannot be null or empty");
try {
- return isSettingEcmProtected(settingIdentifier) && isPackageEcmGuarded(packageName,
- userId);
+ if (!isSettingEcmProtected(settingIdentifier)) {
+ return null;
+ }
+ if (isSettingEcmGuardedForPackage(settingIdentifier, packageName, userId)) {
+ return REASON_PACKAGE_RESTRICTED;
+ }
+ String globalProtectionReason =
+ getGlobalProtectionReason(settingIdentifier, packageName, userId);
+ if (globalProtectionReason != null) {
+ return globalProtectionReason;
+ }
+ return null;
} catch (NameNotFoundException e) {
throw new IllegalArgumentException(e);
}
@@ -227,8 +389,21 @@ public class EnhancedConfirmationService extends SystemService {
}
}
+ private boolean isUntrustedCallOngoing() {
+ if (!Flags.unknownCallPackageInstallBlockingEnabled()) {
+ return false;
+ }
+
+ if (hasCallOfType(CALL_TYPE_EMERGENCY)) {
+ // If we have an emergency call, return false always.
+ return false;
+ }
+ return hasCallOfType(CALL_TYPE_UNTRUSTED);
+ }
+
private void enforcePermissions(@NonNull String methodName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, methodName, mContext);
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, methodName, mContext);
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_ENHANCED_CONFIRMATION_STATES, methodName);
}
@@ -283,6 +458,14 @@ public class EnhancedConfirmationService extends SystemService {
|| isAllowlistedInstaller(installingPackageName));
}
+ private boolean isSettingEcmGuardedForPackage(@NonNull String settingIdentifier,
+ @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException {
+ if (!PER_PACKAGE_PROTECTED_SETTINGS.contains(settingIdentifier)) {
+ return false;
+ }
+ return isPackageEcmGuarded(packageName, userId);
+ }
+
private boolean isAllowlistedPackage(String packageName) {
return isPackageSignedWithAnyOf(packageName,
mTrustedPackageCertDigests.get(packageName));
@@ -322,6 +505,7 @@ public class EnhancedConfirmationService extends SystemService {
return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
+ @SuppressLint("WrongConstant")
private void setAppEcmState(@NonNull String packageName, @EcmState int ecmState,
@UserIdInt int userId) throws NameNotFoundException {
int packageUid = getPackageUid(packageName, userId);
@@ -352,13 +536,56 @@ public class EnhancedConfirmationService extends SystemService {
return false;
}
- if (PROTECTED_SETTINGS.contains(settingIdentifier)) {
+ if (PER_PACKAGE_PROTECTED_SETTINGS.contains(settingIdentifier)) {
+ return true;
+ }
+ if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) {
return true;
}
// TODO(b/310218979): Add role selections as protected settings
return false;
}
+ private String getGlobalProtectionReason(@NonNull String settingIdentifier,
+ @NonNull String packageName, @UserIdInt int userId) {
+ if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)
+ && isUntrustedCallOngoing()) {
+ if (!AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE.equals(settingIdentifier)) {
+ return REASON_PHONE_STATE;
+ }
+ if (!isAccessibilityTool(packageName, userId)) {
+ return REASON_PHONE_STATE;
+ }
+ return null;
+ }
+ return null;
+ }
+
+ private boolean isAccessibilityTool(@NonNull String packageName, @UserIdInt int userId) {
+ AccessibilityManager am;
+ synchronized (mUserAccessibilityManagers) {
+ if (!mUserAccessibilityManagers.containsKey(userId)) {
+ Context userContext =
+ getContext().createContextAsUser(UserHandle.of(userId), 0);
+ mUserAccessibilityManagers.put(userId, userContext.getSystemService(
+ AccessibilityManager.class));
+ }
+ am = mUserAccessibilityManagers.get(userId);
+ }
+ List<AccessibilityServiceInfo> infos = am.getInstalledAccessibilityServiceList();
+ for (int i = 0; i < infos.size(); i++) {
+ AccessibilityServiceInfo info = infos.get(i);
+ String servicePackageName = null;
+ if (info.getResolveInfo() != null && info.getResolveInfo().serviceInfo != null) {
+ servicePackageName = info.getResolveInfo().serviceInfo.packageName;
+ }
+ if (packageName.equals(servicePackageName)) {
+ return info.isAccessibilityTool();
+ }
+ }
+ return false;
+ }
+
@Nullable
private ApplicationInfo getApplicationInfoAsUser(@Nullable String packageName,
@UserIdInt int userId) {
diff --git a/service/java/com/android/permission/util/UserUtils.java b/service/java/com/android/permission/util/UserUtils.java
index 33389a88f..ae8e794b6 100644
--- a/service/java/com/android/permission/util/UserUtils.java
+++ b/service/java/com/android/permission/util/UserUtils.java
@@ -17,18 +17,21 @@
package com.android.permission.util;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.os.Binder;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.internal.compat.UserHandleCompat;
import com.android.internal.util.Preconditions;
import com.android.modules.utils.build.SdkLevel;
-import com.android.permission.compat.UserHandleCompat;
import com.android.permission.flags.Flags;
+import java.util.ArrayList;
import java.util.List;
/** Utility class to deal with Android users. */
@@ -40,11 +43,12 @@ public final class UserUtils {
public static void enforceCrossUserPermission(
@UserIdInt int userId,
boolean allowAll,
+ boolean enforceForProfileGroup,
@NonNull String message,
@NonNull Context context) {
final int callingUid = Binder.getCallingUid();
final int callingUserId = UserHandleCompat.getUserId(callingUid);
- if (userId == callingUserId) {
+ if (userId == callingUserId && !enforceForProfileGroup) {
return;
}
Preconditions.checkArgument(
@@ -53,13 +57,39 @@ public final class UserUtils {
"Invalid user " + userId);
context.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
- if (callingUid != Process.SHELL_UID || userId < UserHandleCompat.USER_SYSTEM) {
+ if (callingUid != Process.SHELL_UID || userId == UserHandleCompat.USER_ALL) {
return;
}
+
+ if (enforceForProfileGroup) {
+ DevicePolicyManager devicePolicyManager =
+ context.getSystemService(DevicePolicyManager.class);
+ if (!devicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) {
+ // For profileGroup exclusive roles users on BYOD are free to choose personal or
+ // work profile app regardless of DISALLOW_DEBUGGING_FEATURES
+ return;
+ }
+
+ List<UserHandle> profiles = getUserProfiles(userId, context, true);
+ final int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; i++) {
+ int profileId = profiles.get(i).getIdentifier();
+ if (profileId == callingUserId) {
+ continue;
+ }
+ enforceShellRestriction(profileId, context);
+ }
+ } else {
+ enforceShellRestriction(userId, context);
+ }
+ }
+
+ private static void enforceShellRestriction(int userId, @NonNull Context context) {
UserManager userManager = context.getSystemService(UserManager.class);
if (userManager.hasUserRestrictionForUser(
UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.of(userId))) {
- throw new SecurityException("Shell does not have permission to access user " + userId);
+ throw new SecurityException(
+ "Shell does not have permission to access user " + userId);
}
}
@@ -81,6 +111,71 @@ public final class UserUtils {
}
}
+ /** Returns all the enabled user profiles on the device for a specified user. */
+ @NonNull
+ public static List<UserHandle> getUserProfiles(@UserIdInt int userId,
+ @NonNull Context context) {
+ return getUserProfiles(userId, context, false);
+ }
+
+ /**
+ * Returns all the enabled user profiles on the device for a specified user
+ *
+ * @param userId the user id to check
+ * @param context the {@link Context}
+ * @param excludePrivate {@code true} to exclude private profiles from returned list of users
+ */
+ @NonNull
+ public static List<UserHandle> getUserProfiles(@UserIdInt int userId, @NonNull Context context,
+ boolean excludePrivate) {
+ // This call requires the QUERY_USERS permission.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ Context userContext = getUserContext(userId, context);
+ UserManager userUserManager = userContext.getSystemService(UserManager.class);
+ List<UserHandle> profiles = userUserManager.getUserProfiles();
+ if (!excludePrivate) {
+ return profiles;
+ }
+ List<UserHandle> filteredProfiles = new ArrayList<>();
+ final int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; i++) {
+ UserHandle user = profiles.get(i);
+ if (!isPrivateProfile(user.getIdentifier(), userContext)) {
+ filteredProfiles.add(user);
+ }
+ }
+ return filteredProfiles;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
+ * Returns the parent of a given user, or userId if it has no parent (e.g. it is the primary
+ * profile)
+ */
+ @UserIdInt
+ public static int getProfileParentIdOrSelf(@UserIdInt int userId, @NonNull Context context) {
+ UserHandle profileParent = getProfileParent(userId, context);
+ // If profile parent userhandle is null, then original user id is the parent
+ return profileParent != null ? profileParent.getIdentifier() : userId;
+ }
+
+ /** Returns the parent of a given user. */
+ @Nullable
+ private static UserHandle getProfileParent(@UserIdInt int userId, @NonNull Context context) {
+ Context userContext = getUserContext(userId, context);
+ UserManager userUserManager = userContext.getSystemService(UserManager.class);
+ // This call requires the INTERACT_ACROSS_USERS permission.
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return userUserManager.getProfileParent(UserHandle.of(userId));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
/** Returns whether a given {@code userId} corresponds to a managed profile. */
public static boolean isManagedProfile(@UserIdInt int userId, @NonNull Context context) {
UserManager userManager = context.getSystemService(UserManager.class);
@@ -107,10 +202,9 @@ public final class UserUtils {
// MANAGE_USERS, QUERY_USERS, or INTERACT_ACROSS_USERS.
final long identity = Binder.clearCallingIdentity();
try {
- Context userContext = context
- .createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
- UserManager userManager = userContext.getSystemService(UserManager.class);
- return userManager != null && userManager.isPrivateProfile();
+ Context userContext = getUserContext(userId, context);
+ UserManager userUserManager = userContext.getSystemService(UserManager.class);
+ return userUserManager != null && userUserManager.isPrivateProfile();
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -141,4 +235,13 @@ public final class UserUtils {
Binder.restoreCallingIdentity(identity);
}
}
+
+ @NonNull
+ public static Context getUserContext(@UserIdInt int userId, @NonNull Context context) {
+ if (SdkLevel.isAtLeastS() && context.getUser().getIdentifier() == userId) {
+ return context;
+ } else {
+ return context.createContextAsUser(UserHandle.of(userId), 0);
+ }
+ }
}
diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java
index 20250b4f6..e5d20ef9d 100644
--- a/service/java/com/android/role/RoleService.java
+++ b/service/java/com/android/role/RoleService.java
@@ -43,9 +43,11 @@ import android.os.ParcelFileDescriptor;
import android.os.RemoteCallback;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.flags.Flags;
+import android.permission.internal.compat.UserHandleCompat;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -62,13 +64,16 @@ import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.Preconditions;
import com.android.internal.util.dump.DualDumpOutputStream;
import com.android.modules.utils.build.SdkLevel;
-import com.android.permission.compat.UserHandleCompat;
import com.android.permission.util.ArrayUtils;
import com.android.permission.util.CollectionUtils;
import com.android.permission.util.ForegroundThread;
import com.android.permission.util.PackageUtils;
import com.android.permission.util.ThrottledRunnable;
import com.android.permission.util.UserUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
+import com.android.role.controller.service.RoleControllerServiceImpl;
+import com.android.role.controller.util.RoleFlags;
import com.android.server.LocalManagerRegistry;
import com.android.server.SystemService;
import com.android.server.role.RoleServicePlatformHelper;
@@ -77,6 +82,7 @@ import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -95,6 +101,7 @@ import java.util.concurrent.TimeoutException;
@RequiresApi(Build.VERSION_CODES.S)
public class RoleService extends SystemService implements RoleUserState.Callback {
private static final String LOG_TAG = RoleService.class.getSimpleName();
+ private static final String TRACE_TAG = RoleService.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -114,9 +121,24 @@ public class RoleService extends SystemService implements RoleUserState.Callback
if (SdkLevel.isAtLeastV()) {
defaultApplicationRoles.add(RoleManager.ROLE_WALLET);
}
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ defaultApplicationRoles.add(
+ RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY);
+ }
DEFAULT_APPLICATION_ROLES = defaultApplicationRoles.toArray(new String[0]);
}
+ private static final String[] TEST_ROLES;
+
+ static {
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ TEST_ROLES =
+ new String[] {RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY};
+ } else {
+ TEST_ROLES = new String[0];
+ }
+ }
+
@NonNull
private final AppOpsManager mAppOpsManager;
@@ -165,6 +187,11 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public RoleService(@NonNull Context context) {
super(context);
+ if (RoleFlags.isProfileGroupExclusivityAvailable()) {
+ RoleControllerServiceImpl.sSetActiveUserForRoleMethod =
+ this::setActiveUserForRoleFromController;
+ }
+
mPlatformHelper = LocalManagerRegistry.getManager(RoleServicePlatformHelper.class);
RoleControllerManager.initializeRemoteServiceComponentName(context);
@@ -176,6 +203,7 @@ public class RoleService extends SystemService implements RoleUserState.Callback
registerUserRemovedReceiver();
}
+ // TODO(b/375029649): enforce single active user for all cross-user roles
private void registerUserRemovedReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
@@ -259,11 +287,16 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public void onUserStarting(@NonNull TargetUser user) {
- if (SdkLevel.isAtLeastV() && Flags.systemServerRoleControllerEnabled()) {
- upgradeLegacyFallbackEnabledRolesIfNeeded(user);
- }
+ Trace.beginSection(TRACE_TAG + "_onUserStarting");
+ try {
+ if (SdkLevel.isAtLeastV() && Flags.systemServerRoleControllerEnabled()) {
+ upgradeLegacyFallbackEnabledRolesIfNeeded(user);
+ }
- maybeGrantDefaultRolesSync(user.getUserHandle().getIdentifier());
+ maybeGrantDefaultRolesSync(user.getUserHandle().getIdentifier());
+ } finally {
+ Trace.endSection();
+ }
}
private void upgradeLegacyFallbackEnabledRolesIfNeeded(@NonNull TargetUser user) {
@@ -406,6 +439,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
private void onRemoveUser(@UserIdInt int userId) {
RemoteCallbackList<IOnRoleHoldersChangedListener> listeners;
RoleUserState userState;
+ // UserManager still knows the user until ACTION_USER_REMOVED broadcasts are processed
+ int profileParentId = UserUtils.getProfileParentIdOrSelf(userId, getContext());
+ List<String> activeRoleNames = null;
synchronized (mLock) {
mGrantDefaultRolesThrottledRunnables.remove(userId);
listeners = mListeners.get(userId);
@@ -413,7 +449,29 @@ public class RoleService extends SystemService implements RoleUserState.Callback
mControllers.remove(userId);
userState = mUserStates.get(userId);
mUserStates.remove(userId);
+
+ if (RoleFlags.isProfileGroupExclusivityAvailable() && userId != profileParentId) {
+ RoleUserState profileParentState = mUserStates.get(profileParentId);
+ activeRoleNames = profileParentState.getActiveRolesForUser(userId);
+ }
}
+ if (RoleFlags.isProfileGroupExclusivityAvailable() && userId != profileParentId
+ && !CollectionUtils.isEmpty(activeRoleNames)) {
+ int activeRoleNamesSize = activeRoleNames.size();
+ for (int i = 0; i < activeRoleNamesSize; i++) {
+ String roleName = activeRoleNames.get(i);
+
+ // If the previous active user had a set role holder, attempt to fallback for
+ // the profile parent.
+ Log.i(LOG_TAG, "User " + userId + " removed, falling back to profile parent "
+ + profileParentId + " for role " + roleName);
+ // Use profileParentId instead of userId here, since userId is in a state of removal
+ // and might be excluded from UserManager#getUserHandles with excludeDying=true
+ setActiveUserForRoleAsUserInternal(roleName, profileParentId, 0, true,
+ profileParentId);
+ }
+ }
+
if (listeners != null) {
listeners.kill();
}
@@ -460,12 +518,99 @@ public class RoleService extends SystemService implements RoleUserState.Callback
}
}
+ private void enforceProfileGroupExclusiveRole(@NonNull String roleName) {
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkArgument(isProfileGroupExclusiveRole(roleName, getContext()),
+ roleName + " is not a profile-group exclusive role");
+ }
+
+ /**
+ * Returns whether the given role has profile group exclusivity
+ *
+ * @param roleName The name of role to check
+ * @param context The context
+ * @return {@code true} if the role is profile group exclusive, {@code false} otherwise
+ */
+ private static boolean isProfileGroupExclusiveRole(String roleName, Context context) {
+ if (!RoleFlags.isProfileGroupExclusivityAvailable()) {
+ return false;
+ }
+ Role role = Roles.get(context).get(roleName);
+ return role != null && role.getExclusivity() == Role.EXCLUSIVITY_PROFILE_GROUP;
+ }
+
+ private void setActiveUserForRoleFromController(@NonNull String roleName, @UserIdInt int userId,
+ @RoleManager.ManageHoldersFlags int flags) {
+ setActiveUserForRoleAsUserInternal(roleName, userId, flags, false, userId);
+ }
+
+ private void setActiveUserForRoleAsUserInternal(@NonNull String roleName,
+ @UserIdInt int activeUserId, @RoleManager.ManageHoldersFlags int flags,
+ boolean clearRoleHoldersForActiveUser, @UserIdInt int userId) {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "setActiveUserForRoleAsUser not available");
+ enforceProfileGroupExclusiveRole(roleName);
+
+ if (!UserUtils.isUserExistent(userId, getContext())) {
+ Log.e(LOG_TAG, "user " + userId + " does not exist");
+ return;
+ }
+ if (!UserUtils.isUserExistent(activeUserId, getContext())) {
+ Log.e(LOG_TAG, "user " + activeUserId + " does not exist");
+ return;
+ }
+ if (UserUtils.isPrivateProfile(activeUserId, getContext())) {
+ Log.e(LOG_TAG, "Cannot set private profile " + activeUserId + " as active user"
+ + " for role");
+ return;
+ }
+ List<UserHandle> profiles = UserUtils.getUserProfiles(userId, getContext(), true);
+ if (!profiles.contains(UserHandle.of(activeUserId))) {
+ Log.e(LOG_TAG, "User " + activeUserId + " is not in the same profile-group as "
+ + userId);
+ return;
+ }
+
+ int profileParentId = UserUtils.getProfileParentIdOrSelf(userId, getContext());
+ RoleUserState userState = getOrCreateUserState(profileParentId);
+
+ if (!userState.setActiveUserForRole(roleName, activeUserId)) {
+ Log.i(LOG_TAG, "User " + activeUserId + " is already the active user for role");
+ return;
+ }
+
+ final int profilesSize = profiles.size();
+ for (int i = 0; i < profilesSize; i++) {
+ int profilesUserId = profiles.get(i).getIdentifier();
+ if (!clearRoleHoldersForActiveUser && profilesUserId == activeUserId) {
+ continue;
+ }
+ final AndroidFuture<Void> future = new AndroidFuture<>();
+ final RemoteCallback callback = new RemoteCallback(result -> {
+ boolean successful = result != null;
+ if (successful) {
+ future.complete(null);
+ } else {
+ future.completeExceptionally(new RuntimeException());
+ }
+ });
+ getOrCreateController(profilesUserId)
+ .onClearRoleHolders(roleName, flags, callback);
+ try {
+ future.get(5, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ Log.e(LOG_TAG, "Exception while clearing role holders for non-active user: "
+ + profilesUserId, e);
+ }
+ }
+ }
+
private class Stub extends IRoleManager.Stub {
@Override
public boolean isRoleAvailableAsUser(@NonNull String roleName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "isRoleAvailableAsUser",
- getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "isRoleAvailableAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -481,7 +626,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@UserIdInt int userId) {
mAppOpsManager.checkPackage(getCallingUid(), packageName);
- UserUtils.enforceCrossUserPermission(userId, false, "isRoleHeldAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "isRoleHeldAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -500,8 +646,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@NonNull
@Override
public List<String> getRoleHoldersAsUser(@NonNull String roleName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "getRoleHoldersAsUser",
- getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "getRoleHoldersAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return Collections.emptyList();
@@ -523,8 +669,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public void addRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
@RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
@NonNull RemoteCallback callback) {
- UserUtils.enforceCrossUserPermission(userId, false, "addRoleHolderAsUser",
- getContext());
+ boolean enforceForProfileGroup = isProfileGroupExclusiveRole(roleName, getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ enforceForProfileGroup, "addRoleHolderAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return;
@@ -544,8 +691,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public void removeRoleHolderAsUser(@NonNull String roleName, @NonNull String packageName,
@RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
@NonNull RemoteCallback callback) {
- UserUtils.enforceCrossUserPermission(userId, false, "removeRoleHolderAsUser",
- getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "removeRoleHolderAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return;
@@ -566,8 +713,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public void clearRoleHoldersAsUser(@NonNull String roleName,
@RoleManager.ManageHoldersFlags int flags, @UserIdInt int userId,
@NonNull RemoteCallback callback) {
- UserUtils.enforceCrossUserPermission(userId, false, "clearRoleHoldersAsUser",
- getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "clearRoleHoldersAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return;
@@ -585,7 +732,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
@Nullable
public String getDefaultApplicationAsUser(@NonNull String roleName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "getDefaultApplicationAsUser",
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "getDefaultApplicationAsUser",
getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
@@ -610,8 +758,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
public void setDefaultApplicationAsUser(@NonNull String roleName,
@Nullable String packageName, @RoleManager.ManageHoldersFlags int flags,
@UserIdInt int userId, @NonNull RemoteCallback callback) {
- UserUtils.enforceCrossUserPermission(userId, false, "setDefaultApplicationAsUser",
- getContext());
+ boolean enforceForProfileGroup = isProfileGroupExclusiveRole(roleName, getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ enforceForProfileGroup, "setDefaultApplicationAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return;
@@ -633,10 +782,59 @@ public class RoleService extends SystemService implements RoleUserState.Callback
}
@Override
+ public int getActiveUserForRoleAsUser(@NonNull String roleName, @UserIdInt int userId) {
+ Trace.beginSection(TRACE_TAG + "_getActiveUserForRoleAsUser");
+ try {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "getActiveUserForRoleAsUser not available");
+ enforceProfileGroupExclusiveRole(roleName);
+
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ true, "getActiveUserForRole", getContext());
+ if (!UserUtils.isUserExistent(userId, getContext())) {
+ Log.e(LOG_TAG, "user " + userId + " does not exist");
+ return UserHandleCompat.USER_NULL;
+ }
+
+ enforceCallingOrSelfAnyPermissions(new String[] {
+ Manifest.permission.MANAGE_DEFAULT_APPLICATIONS,
+ Manifest.permission.MANAGE_ROLE_HOLDERS
+ }, "getActiveUserForRole");
+
+ int profileParentId = UserUtils.getProfileParentIdOrSelf(userId, getContext());
+ RoleUserState userState = getOrCreateUserState(profileParentId);
+ return userState.getActiveUserForRole(roleName);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ @Override
+ public void setActiveUserForRoleAsUser(@NonNull String roleName,
+ @UserIdInt int activeUserId, @RoleManager.ManageHoldersFlags int flags,
+ @UserIdInt int userId) {
+ Trace.beginSection(TRACE_TAG + "_setActiveUserForRoleAsUser");
+ try {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "setActiveUserForRoleAsUser not available");
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ true, "setActiveUserForRole", getContext());
+ enforceCallingOrSelfAnyPermissions(new String[] {
+ Manifest.permission.MANAGE_DEFAULT_APPLICATIONS,
+ Manifest.permission.MANAGE_ROLE_HOLDERS
+ }, "setActiveUserForRoleAsUser");
+ setActiveUserForRoleAsUserInternal(roleName, activeUserId, flags, true, userId);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ @Override
public void addOnRoleHoldersChangedListenerAsUser(
@NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, true,
- "addOnRoleHoldersChangedListenerAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ true,
+ /* enforceForProfileGroup= */ false, "addOnRoleHoldersChangedListenerAsUser",
+ getContext());
if (userId != UserHandleCompat.USER_ALL && !UserUtils.isUserExistent(userId,
getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
@@ -656,7 +854,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public void removeOnRoleHoldersChangedListenerAsUser(
@NonNull IOnRoleHoldersChangedListener listener, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, true,
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ true,
+ /* enforceForProfileGroup= */ false,
"removeOnRoleHoldersChangedListenerAsUser", getContext());
if (userId != UserHandleCompat.USER_ALL && !UserUtils.isUserExistent(userId,
getContext())) {
@@ -709,7 +908,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public boolean isRoleFallbackEnabledAsUser(@NonNull String roleName,
@UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "isRoleFallbackEnabledAsUser",
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "isRoleFallbackEnabledAsUser",
getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
@@ -727,7 +927,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public void setRoleFallbackEnabledAsUser(@NonNull String roleName, boolean fallbackEnabled,
@UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "setRoleFallbackEnabledAsUser",
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "setRoleFallbackEnabledAsUser",
getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
@@ -745,7 +946,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public void setRoleNamesFromControllerAsUser(@NonNull List<String> roleNames,
@UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "setRoleNamesFromControllerAsUser",
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "setRoleNamesFromControllerAsUser",
getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
@@ -764,8 +966,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public boolean addRoleHolderFromControllerAsUser(@NonNull String roleName,
@NonNull String packageName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false,
- "addRoleHolderFromControllerAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "addRoleHolderFromControllerAsUser",
+ getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -784,8 +987,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public boolean removeRoleHolderFromControllerAsUser(@NonNull String roleName,
@NonNull String packageName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false,
- "removeRoleHolderFromControllerAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "removeRoleHolderFromControllerAsUser",
+ getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -804,8 +1008,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public List<String> getHeldRolesFromControllerAsUser(@NonNull String packageName,
@UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false,
- "getHeldRolesFromControllerAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "getHeldRolesFromControllerAsUser",
+ getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return Collections.emptyList();
@@ -914,7 +1119,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public String getSmsRoleHolder(int userId) {
final Context context = getContext();
- UserUtils.enforceCrossUserPermission(userId, false, "getSmsRoleHolder", context);
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "getSmsRoleHolder", context);
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return null;
@@ -938,7 +1144,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public String getEmergencyRoleHolder(int userId) {
final Context context = getContext();
- UserUtils.enforceCrossUserPermission(userId, false, "getEmergencyRoleHolder", context);
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "getEmergencyRoleHolder", context);
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return null;
@@ -964,8 +1171,8 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public boolean isRoleVisibleAsUser(@NonNull String roleName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false, "isRoleVisibleAsUser",
- getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "isRoleVisibleAsUser", getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -982,8 +1189,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
@Override
public boolean isApplicationVisibleForRoleAsUser(@NonNull String roleName,
@NonNull String packageName, @UserIdInt int userId) {
- UserUtils.enforceCrossUserPermission(userId, false,
- "isApplicationVisibleForRoleAsUser", getContext());
+ UserUtils.enforceCrossUserPermission(userId, /* allowAll= */ false,
+ /* enforceForProfileGroup= */ false, "isApplicationVisibleForRoleAsUser",
+ getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.e(LOG_TAG, "user " + userId + " does not exist");
return false;
@@ -998,6 +1206,59 @@ public class RoleService extends SystemService implements RoleUserState.Callback
return getOrCreateController(userId).isApplicationVisibleForRole(roleName, packageName);
}
+ @NonNull
+ @Override
+ public List<String> getDefaultHoldersForTestAsUser(@NonNull String roleName,
+ @UserIdInt int userId) {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "getDefaultHoldersForTest not available");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "getDefaultHoldersForTest");
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkArgumentIsSupported(TEST_ROLES, roleName);
+
+ return getOrCreateUserState(userId).getDefaultHoldersForTest(roleName);
+ }
+
+ @Override
+ public void setDefaultHoldersForTestAsUser(@NonNull String roleName,
+ @NonNull List<String> packageNames, @UserIdInt int userId) {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "setDefaultHoldersForTest not available");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "setDefaultHoldersForTest");
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkArgumentIsSupported(TEST_ROLES, roleName);
+ Objects.requireNonNull(packageNames, "packageNames cannot be null");
+
+ getOrCreateUserState(userId).setDefaultHoldersForTest(roleName, packageNames);
+ }
+
+ @Override
+ public boolean isRoleVisibleForTestAsUser(@NonNull String roleName, @UserIdInt int userId) {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "isRoleVisibleForTest not available");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "isRoleVisibleForTest");
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkArgumentIsSupported(TEST_ROLES, roleName);
+
+ return getOrCreateUserState(userId).isRoleVisibleForTest(roleName);
+ }
+
+ @Override
+ public void setRoleVisibleForTestAsUser(@NonNull String roleName, boolean visible,
+ @UserIdInt int userId) {
+ Preconditions.checkState(RoleFlags.isProfileGroupExclusivityAvailable(),
+ "setRoleVisibleForTest not available");
+ getContext().enforceCallingOrSelfPermission(Manifest.permission.MANAGE_ROLE_HOLDERS,
+ "setRoleVisibleForTest");
+ Preconditions.checkStringNotEmpty(roleName, "roleName cannot be null or empty");
+ Preconditions.checkArgumentIsSupported(TEST_ROLES, roleName);
+
+ getOrCreateUserState(userId).setRoleVisibleForTest(roleName, visible);
+ }
+
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {
@@ -1040,6 +1301,20 @@ public class RoleService extends SystemService implements RoleUserState.Callback
return true;
}
}
+
+ private void enforceCallingOrSelfAnyPermissions(@NonNull String[] permissions,
+ @NonNull String message) {
+ for (String permission : permissions) {
+ if (getContext().checkCallingOrSelfPermission(permission)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+
+ throw new SecurityException(message + ": Neither user " + Binder.getCallingUid()
+ + " nor current process has at least one of" + Arrays.toString(permissions)
+ + ".");
+ }
}
private class Local implements RoleManagerLocal {
diff --git a/service/java/com/android/role/RoleShellCommand.java b/service/java/com/android/role/RoleShellCommand.java
index 808a64cb4..538e62f47 100644
--- a/service/java/com/android/role/RoleShellCommand.java
+++ b/service/java/com/android/role/RoleShellCommand.java
@@ -22,11 +22,11 @@ import android.app.role.IRoleManager;
import android.os.Build;
import android.os.RemoteCallback;
import android.os.RemoteException;
+import android.permission.internal.compat.UserHandleCompat;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.BasicShellCommandHandler;
-import com.android.permission.compat.UserHandleCompat;
import java.io.PrintWriter;
import java.util.List;
@@ -87,6 +87,10 @@ class RoleShellCommand extends BasicShellCommandHandler {
return runClearRoleHolders();
case "set-bypassing-role-qualification":
return runSetBypassingRoleQualification();
+ case "get-active-user-for-role":
+ return runGetActiveUserForRole();
+ case "set-active-user-for-role":
+ return runSetActiveUserForRole();
default:
return handleDefaultCommands(cmd);
}
@@ -162,6 +166,27 @@ class RoleShellCommand extends BasicShellCommandHandler {
return 0;
}
+ private int runGetActiveUserForRole() throws RemoteException {
+ int userId = getUserIdMaybe();
+ String roleName = getNextArgRequired();
+
+ int activeUserId = mRoleManager.getActiveUserForRoleAsUser(roleName, userId);
+ if (activeUserId != UserHandleCompat.USER_NULL) {
+ getOutPrintWriter().println(activeUserId);
+ }
+ return 0;
+ }
+
+ private int runSetActiveUserForRole() throws RemoteException {
+ int userId = getUserIdMaybe();
+ String roleName = getNextArgRequired();
+ int activeUserId = Integer.parseInt(getNextArgRequired());
+ int flags = getFlagsMaybe();
+
+ mRoleManager.setActiveUserForRoleAsUser(roleName, activeUserId, flags, userId);
+ return 0;
+ }
+
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -174,6 +199,8 @@ class RoleShellCommand extends BasicShellCommandHandler {
pw.println(" remove-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]");
pw.println(" clear-role-holders [--user USER_ID] ROLE [FLAGS]");
pw.println(" set-bypassing-role-qualification true|false");
+ pw.println(" get-active-user-for-role [--user USER_ID] ROLE");
+ pw.println(" set-active-user-for-role [--user USER_ID] ROLE ACTIVE_USER_ID [FLAGS]");
pw.println();
}
}
diff --git a/service/java/com/android/role/RoleUserState.java b/service/java/com/android/role/RoleUserState.java
index 81007d65e..18574a6fc 100644
--- a/service/java/com/android/role/RoleUserState.java
+++ b/service/java/com/android/role/RoleUserState.java
@@ -24,6 +24,7 @@ import android.annotation.WorkerThread;
import android.os.Build;
import android.os.Handler;
import android.os.UserHandle;
+import android.permission.internal.compat.UserHandleCompat;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -39,6 +40,7 @@ import com.android.role.persistence.RolesState;
import com.android.server.role.RoleServicePlatformHelper;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -96,6 +98,18 @@ class RoleUserState {
private ArraySet<String> mFallbackEnabledRoles = new ArraySet<>();
@GuardedBy("mLock")
+ @NonNull
+ private ArrayMap<String, Integer> mActiveUserIds = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ @NonNull
+ private final Map<String, List<String>> mDefaultHoldersForTest = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ @NonNull
+ private final Set<String> mRolesVisibleForTest = new ArraySet<>();
+
+ @GuardedBy("mLock")
private boolean mWriteScheduled;
@GuardedBy("mLock")
@@ -423,6 +437,88 @@ class RoleUserState {
}
/**
+ * Return the active user for the role
+ *
+ * @param roleName the name of the role to get the active user for
+ */
+ public int getActiveUserForRole(@NonNull String roleName) {
+ synchronized (mLock) {
+ return mActiveUserIds.getOrDefault(roleName, UserHandleCompat.USER_NULL);
+ }
+ }
+
+ @NonNull
+ public List<String> getActiveRolesForUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ List<String> activeRoleNames = new ArrayList<>();
+ int activeUserIdsSize = mActiveUserIds.size();
+ for (int i = 0; i < activeUserIdsSize; i++) {
+ int activeUserId = mActiveUserIds.valueAt(i);
+ if (activeUserId == userId) {
+ String roleName = mActiveUserIds.keyAt(i);
+ activeRoleNames.add(roleName);
+ }
+ }
+ return activeRoleNames;
+ }
+ }
+
+ /**
+ * Set the active user for the role
+ *
+ * @param roleName the name of the role to set the active user for
+ * @param userId User id to set as active for this role
+ * @return whether any changes were made
+ */
+ public boolean setActiveUserForRole(@NonNull String roleName, @UserIdInt int userId) {
+ if (!com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ return false;
+ }
+ synchronized (mLock) {
+ Integer currentActiveUserId = mActiveUserIds.get(roleName);
+ // If we have pre-existing roles that weren't profile group exclusive and don't have an
+ // active user, ensure we set and write value, and return modified, otherwise other
+ // users might not have role holder revoked.
+ if (currentActiveUserId != null && currentActiveUserId == userId) {
+ return false;
+ }
+ mActiveUserIds.put(roleName, userId);
+ scheduleWriteFileLocked();
+ return true;
+ }
+ }
+
+ @NonNull
+ public List<String> getDefaultHoldersForTest(@NonNull String roleName) {
+ synchronized (mLock) {
+ return mDefaultHoldersForTest.getOrDefault(roleName, Collections.emptyList());
+ }
+ }
+
+ public void setDefaultHoldersForTest(@NonNull String roleName,
+ @NonNull List<String> packageNames) {
+ synchronized (mLock) {
+ mDefaultHoldersForTest.put(roleName, packageNames);
+ }
+ }
+
+ public boolean isRoleVisibleForTest(@NonNull String roleName) {
+ synchronized (mLock) {
+ return mRolesVisibleForTest.contains(roleName);
+ }
+ }
+
+ public void setRoleVisibleForTest(@NonNull String roleName, boolean visible) {
+ synchronized (mLock) {
+ if (visible) {
+ mRolesVisibleForTest.add(roleName);
+ } else {
+ mRolesVisibleForTest.remove(roleName);
+ }
+ }
+ }
+
+ /**
* Schedule writing the state to file.
*/
@GuardedBy("mLock")
@@ -449,9 +545,15 @@ class RoleUserState {
// Force a reconciliation on next boot if we are bypassing role qualification now.
String packagesHash = mBypassingRoleQualification ? null : mPackagesHash;
- roles = new RolesState(mVersion, packagesHash,
- (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(),
- snapshotFallbackEnabledRoles());
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ roles = new RolesState(mVersion, packagesHash,
+ (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(),
+ snapshotFallbackEnabledRoles(), snapshotActiveUserIds());
+ } else {
+ roles = new RolesState(mVersion, packagesHash,
+ (Map<String, Set<String>>) (Map<String, ?>) snapshotRolesLocked(),
+ snapshotFallbackEnabledRoles());
+ }
}
mPersistence.writeForUser(roles, UserHandle.of(mUserId));
@@ -463,14 +565,17 @@ class RoleUserState {
Map<String, Set<String>> roles;
Set<String> fallbackEnabledRoles;
+ Map<String, Integer> activeUserIds;
if (roleState != null) {
mVersion = roleState.getVersion();
mPackagesHash = roleState.getPackagesHash();
roles = roleState.getRoles();
fallbackEnabledRoles = roleState.getFallbackEnabledRoles();
+ activeUserIds = roleState.getActiveUserIds();
} else {
roles = mPlatformHelper.getLegacyRoleState(mUserId);
fallbackEnabledRoles = roles.keySet();
+ activeUserIds = Collections.emptyMap();
}
mRoles.clear();
for (Map.Entry<String, Set<String>> entry : roles.entrySet()) {
@@ -480,6 +585,10 @@ class RoleUserState {
}
mFallbackEnabledRoles.clear();
mFallbackEnabledRoles.addAll(fallbackEnabledRoles);
+ mActiveUserIds.clear();
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ mActiveUserIds.putAll(activeUserIds);
+ }
if (roleState == null) {
scheduleWriteFileLocked();
}
@@ -496,12 +605,14 @@ class RoleUserState {
int version;
String packagesHash;
ArrayMap<String, ArraySet<String>> roles;
+ ArrayMap<String, Integer> activeUserIds;
ArraySet<String> fallbackEnabledRoles;
synchronized (mLock) {
version = mVersion;
packagesHash = mPackagesHash;
roles = snapshotRolesLocked();
fallbackEnabledRoles = snapshotFallbackEnabledRoles();
+ activeUserIds = snapshotActiveUserIds();
}
long fieldToken = dumpOutputStream.start(fieldName, fieldId);
@@ -514,10 +625,14 @@ class RoleUserState {
String roleName = roles.keyAt(rolesIndex);
ArraySet<String> roleHolders = roles.valueAt(rolesIndex);
boolean fallbackEnabled = fallbackEnabledRoles.contains(roleName);
+ Integer activeUserId = activeUserIds.get(roleName);
long rolesToken = dumpOutputStream.start("roles", RoleUserStateProto.ROLES);
dumpOutputStream.write("name", RoleProto.NAME, roleName);
dumpOutputStream.write("fallback_enabled", RoleProto.FALLBACK_ENABLED, fallbackEnabled);
+ if (activeUserId != null) {
+ dumpOutputStream.write("active_user_id", RoleProto.ACTIVE_USER_ID, activeUserId);
+ }
int roleHoldersSize = roleHolders.size();
for (int roleHoldersIndex = 0; roleHoldersIndex < roleHoldersSize; roleHoldersIndex++) {
String roleHolder = roleHolders.valueAt(roleHoldersIndex);
@@ -563,6 +678,12 @@ class RoleUserState {
return new ArraySet<>(mFallbackEnabledRoles);
}
+ @GuardedBy("mLock")
+ @NonNull
+ private ArrayMap<String, Integer> snapshotActiveUserIds() {
+ return new ArrayMap<>(mActiveUserIds);
+ }
+
/**
* Destroy this user state and delete the corresponding file. Any pending writes to the file
* will be cancelled, and any future interaction with this state will throw an exception.
diff --git a/service/java/com/android/role/TEST_MAPPING b/service/java/com/android/role/TEST_MAPPING
index e0e1160d8..720330f82 100644
--- a/service/java/com/android/role/TEST_MAPPING
+++ b/service/java/com/android/role/TEST_MAPPING
@@ -15,6 +15,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases"
}
],
"mainline-presubmit": [
@@ -32,6 +35,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]"
}
],
"permission-mainline-presubmit": [
@@ -49,6 +55,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases"
}
],
"postsubmit": [
diff --git a/service/java/com/android/role/persistence/RolesPersistenceImpl.java b/service/java/com/android/role/persistence/RolesPersistenceImpl.java
index 220a8440b..8382d3632 100644
--- a/service/java/com/android/role/persistence/RolesPersistenceImpl.java
+++ b/service/java/com/android/role/persistence/RolesPersistenceImpl.java
@@ -67,6 +67,7 @@ public class RolesPersistenceImpl implements RolesPersistence {
private static final String ATTRIBUTE_VERSION = "version";
private static final String ATTRIBUTE_NAME = "name";
private static final String ATTRIBUTE_FALLBACK_ENABLED = "fallbackEnabled";
+ private static final String ATTRIBUTE_ACTIVE_USER_ID = "activeUserId";
private static final String ATTRIBUTE_PACKAGES_HASH = "packagesHash";
@VisibleForTesting
@@ -144,6 +145,7 @@ public class RolesPersistenceImpl implements RolesPersistence {
Map<String, Set<String>> roles = new ArrayMap<>();
Set<String> fallbackEnabledRoles = new ArraySet<>();
+ Map<String, Integer> activeUserIds = new ArrayMap<>();
int type;
int depth;
int innerDepth = parser.getDepth() + 1;
@@ -159,12 +161,23 @@ public class RolesPersistenceImpl implements RolesPersistence {
if (Boolean.parseBoolean(fallbackEnabled)) {
fallbackEnabledRoles.add(roleName);
}
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ String activeUserId = parser.getAttributeValue(null, ATTRIBUTE_ACTIVE_USER_ID);
+ if (activeUserId != null) {
+ activeUserIds.put(roleName, Integer.parseInt(activeUserId));
+ }
+ }
Set<String> roleHolders = parseRoleHolders(parser);
roles.put(roleName, roleHolders);
}
}
- return new RolesState(version, packagesHash, roles, fallbackEnabledRoles);
+ if (com.android.permission.flags.Flags.crossUserRoleEnabled()) {
+ return new RolesState(version, packagesHash, roles, fallbackEnabledRoles,
+ activeUserIds);
+ } else {
+ return new RolesState(version, packagesHash, roles, fallbackEnabledRoles);
+ }
}
@NonNull
@@ -244,15 +257,22 @@ public class RolesPersistenceImpl implements RolesPersistence {
}
Set<String> fallbackEnabledRoles = roles.getFallbackEnabledRoles();
+ Map<String, Integer> activeUserIds = roles.getActiveUserIds();
for (Map.Entry<String, Set<String>> entry : roles.getRoles().entrySet()) {
String roleName = entry.getKey();
Set<String> roleHolders = entry.getValue();
boolean isFallbackEnabled = fallbackEnabledRoles.contains(roleName);
+ Integer activeUserId = com.android.permission.flags.Flags.crossUserRoleEnabled()
+ ? activeUserIds.get(roleName) : null;
serializer.startTag(null, TAG_ROLE);
serializer.attribute(null, ATTRIBUTE_NAME, roleName);
serializer.attribute(null, ATTRIBUTE_FALLBACK_ENABLED,
Boolean.toString(isFallbackEnabled));
+ if (activeUserId != null) {
+ serializer.attribute(
+ null, ATTRIBUTE_ACTIVE_USER_ID, Integer.toString(activeUserId));
+ }
serializeRoleHolders(serializer, roleHolders);
serializer.endTag(null, TAG_ROLE);
}
diff --git a/service/java/com/android/role/persistence/RolesState.java b/service/java/com/android/role/persistence/RolesState.java
index a189dd4c2..f1b3d8dfa 100644
--- a/service/java/com/android/role/persistence/RolesState.java
+++ b/service/java/com/android/role/persistence/RolesState.java
@@ -23,12 +23,13 @@ import android.annotation.SystemApi;
import android.annotation.SystemApi.Client;
import android.permission.flags.Flags;
+import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
- * State of all roles.
+ * State of all roles for a user.
*
* TODO(b/147914847): Remove @hide when it becomes the default.
* @hide
@@ -59,6 +60,12 @@ public final class RolesState {
private final Set<String> mFallbackEnabledRoles;
/**
+ * The active users for cross user roles.
+ */
+ @NonNull
+ private final Map<String, Integer> mActiveUserIds;
+
+ /**
* Create a new instance of this class.
*
* @param version the version of the roles
@@ -81,10 +88,27 @@ public final class RolesState {
@FlaggedApi(Flags.FLAG_SYSTEM_SERVER_ROLE_CONTROLLER_ENABLED)
public RolesState(int version, @Nullable String packagesHash,
@NonNull Map<String, Set<String>> roles, @NonNull Set<String> fallbackEnabledRoles) {
+ this(version, packagesHash, roles, fallbackEnabledRoles, Collections.emptyMap());
+ }
+
+ /**
+ * Create a new instance of this class.
+ *
+ * @param version the version of the roles
+ * @param packagesHash the hash of all packages in the system
+ * @param roles the roles
+ * @param fallbackEnabledRoles the roles with fallback enabled
+ * @param activeUserIds the active users for cross user roles
+ * @hide
+ */
+ public RolesState(int version, @Nullable String packagesHash,
+ @NonNull Map<String, Set<String>> roles, @NonNull Set<String> fallbackEnabledRoles,
+ @NonNull Map<String, Integer> activeUserIds) {
mVersion = version;
mPackagesHash = packagesHash;
mRoles = roles;
mFallbackEnabledRoles = fallbackEnabledRoles;
+ mActiveUserIds = activeUserIds;
}
/**
@@ -127,6 +151,17 @@ public final class RolesState {
return mFallbackEnabledRoles;
}
+ /**
+ * Get the active users for cross user roles.
+ *
+ * @return active users for cross user roles
+ * @hide
+ */
+ @NonNull
+ public Map<String, Integer> getActiveUserIds() {
+ return mActiveUserIds;
+ }
+
@Override
public boolean equals(Object object) {
if (this == object) {
@@ -139,11 +174,12 @@ public final class RolesState {
return mVersion == that.mVersion
&& Objects.equals(mPackagesHash, that.mPackagesHash)
&& Objects.equals(mRoles, that.mRoles)
- && Objects.equals(mFallbackEnabledRoles, that.mFallbackEnabledRoles);
+ && Objects.equals(mFallbackEnabledRoles, that.mFallbackEnabledRoles)
+ && Objects.equals(mActiveUserIds, that.mActiveUserIds);
}
@Override
public int hashCode() {
- return Objects.hash(mVersion, mPackagesHash, mRoles, mFallbackEnabledRoles);
+ return Objects.hash(mVersion, mPackagesHash, mRoles, mFallbackEnabledRoles, mActiveUserIds);
}
}
diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java
index 250be5f25..6df4184e1 100644
--- a/service/java/com/android/safetycenter/SafetyCenterService.java
+++ b/service/java/com/android/safetycenter/SafetyCenterService.java
@@ -694,7 +694,8 @@ public final class SafetyCenterService extends SystemService {
/** Enforces cross user permission and returns whether the user is valid. */
private boolean enforceCrossUserPermission(String message, @UserIdInt int userId) {
UserUtils.enforceCrossUserPermission(
- userId, /* allowAll= */ false, message, getContext());
+ userId, /* allowAll= */ false, /* enforceForProfileGroup= */ false, message,
+ getContext());
if (!UserUtils.isUserExistent(userId, getContext())) {
Log.w(
TAG,
diff --git a/service/java/com/android/safetycenter/UserProfileGroup.java b/service/java/com/android/safetycenter/UserProfileGroup.java
index 46a440bf7..44cb8d3d6 100644
--- a/service/java/com/android/safetycenter/UserProfileGroup.java
+++ b/service/java/com/android/safetycenter/UserProfileGroup.java
@@ -21,15 +21,12 @@ import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.UserIdInt;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Binder;
-import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
+import android.permission.internal.compat.UserHandleCompat;
import android.util.Log;
-import androidx.annotation.Nullable;
-
import com.android.permission.util.UserUtils;
import java.lang.annotation.Retention;
@@ -49,8 +46,6 @@ import java.util.Objects;
public final class UserProfileGroup {
private static final String TAG = "UserProfileGroup";
- // UserHandle#USER_NULL is a @TestApi so it cannot be accessed from the mainline module.
- public static final @UserIdInt int USER_NULL = -10000;
@UserIdInt private final int mProfileParentUserId;
private final int[] mManagedProfilesUserIds;
@@ -134,20 +129,15 @@ public final class UserProfileGroup {
* is disabled.
*/
public static UserProfileGroup fromUser(Context context, @UserIdInt int userId) {
- UserManager userManager = getUserManagerForUser(userId, context);
- List<UserHandle> userProfiles = getEnabledUserProfiles(userManager);
- UserHandle profileParent = getProfileParent(userManager, userId);
- int profileParentUserId = userId;
- if (profileParent != null) {
- profileParentUserId = profileParent.getIdentifier();
- }
+ List<UserHandle> userProfiles = UserUtils.getUserProfiles(userId, context);
+ int profileParentUserId = UserUtils.getProfileParentIdOrSelf(userId, context);
int[] managedProfilesUserIds = new int[userProfiles.size()];
int[] managedRunningProfilesUserIds = new int[userProfiles.size()];
int managedProfilesUserIdsLen = 0;
int managedRunningProfilesUserIdsLen = 0;
- int privateProfileUserId = USER_NULL;
+ int privateProfileUserId = UserHandleCompat.USER_NULL;
boolean privateProfileRunning = false;
for (int i = 0; i < userProfiles.size(); i++) {
@@ -192,23 +182,10 @@ public final class UserProfileGroup {
}
private static UserManager getUserManagerForUser(@UserIdInt int userId, Context context) {
- Context userContext = getUserContext(context, UserHandle.of(userId));
+ Context userContext = UserUtils.getUserContext(userId, context);
return requireNonNull(userContext.getSystemService(UserManager.class));
}
- private static Context getUserContext(Context context, UserHandle userHandle) {
- if (Process.myUserHandle().equals(userHandle)) {
- return context;
- } else {
- try {
- return context.createPackageContextAsUser(
- context.getPackageName(), /* flags= */ 0, userHandle);
- } catch (PackageManager.NameNotFoundException doesNotHappen) {
- throw new IllegalStateException(doesNotHappen);
- }
- }
- }
-
private static boolean isProfile(@UserIdInt int userId, Context context) {
// This call requires the INTERACT_ACROSS_USERS permission.
final long callingId = Binder.clearCallingIdentity();
@@ -220,27 +197,6 @@ public final class UserProfileGroup {
}
}
- private static List<UserHandle> getEnabledUserProfiles(UserManager userManager) {
- // This call requires the QUERY_USERS permission.
- final long callingId = Binder.clearCallingIdentity();
- try {
- return userManager.getUserProfiles();
- } finally {
- Binder.restoreCallingIdentity(callingId);
- }
- }
-
- @Nullable
- private static UserHandle getProfileParent(UserManager userManager, @UserIdInt int userId) {
- // This call requires the INTERACT_ACROSS_USERS permission.
- final long callingId = Binder.clearCallingIdentity();
- try {
- return userManager.getProfileParent(UserHandle.of(userId));
- } finally {
- Binder.restoreCallingIdentity(callingId);
- }
- }
-
/** Returns the profile parent user id of the {@link UserProfileGroup}. */
public int getProfileParentUserId() {
return mProfileParentUserId;
@@ -262,7 +218,7 @@ public final class UserProfileGroup {
/* destPos= */ 1,
mManagedProfilesUserIds.length);
- if (mPrivateProfileUserId != USER_NULL) {
+ if (mPrivateProfileUserId != UserHandleCompat.USER_NULL) {
allProfileIds[allProfileIds.length - 1] = mPrivateProfileUserId;
}
@@ -303,7 +259,7 @@ public final class UserProfileGroup {
case PROFILE_TYPE_MANAGED:
return mManagedProfilesUserIds;
case PROFILE_TYPE_PRIVATE:
- return mPrivateProfileUserId != USER_NULL
+ return mPrivateProfileUserId != UserHandleCompat.USER_NULL
? new int[]{mPrivateProfileUserId} : new int[]{};
default:
Log.w(TAG, "profiles requested for unexpected profile type " + profileType);
@@ -342,7 +298,7 @@ public final class UserProfileGroup {
private int getNumProfiles() {
return 1
+ mManagedProfilesUserIds.length
- + (mPrivateProfileUserId == USER_NULL ? 0 : 1);
+ + (mPrivateProfileUserId == UserHandleCompat.USER_NULL ? 0 : 1);
}
/**
@@ -395,7 +351,8 @@ public final class UserProfileGroup {
}
}
- return USER_NULL != mPrivateProfileUserId && userId == mPrivateProfileUserId;
+ return UserHandleCompat.USER_NULL != mPrivateProfileUserId
+ && userId == mPrivateProfileUserId;
}
@Override
diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml
index b10928320..6aaf3da8c 100644
--- a/service/lint-baseline.xml
+++ b/service/lint-baseline.xml
@@ -35,17 +35,6 @@
</issue>
<issue
- id="NewApi"
- message="Call requires API level 31 (current min is 30): `android.os.UserHandle#getUid`"
- errorLine1=" return UserHandle.of(userId).getUid(appId);"
- errorLine2=" ~~~~~~">
- <location
- file="packages/modules/Permission/service/java/com/android/permission/compat/UserHandleCompat.java"
- line="57"
- column="38"/>
- </issue>
-
- <issue
id="FlaggedApi"
message="Method `RolesState()` is a flagged API and should be inside an `if (Flags.systemServerRoleControllerEnabled())` check (or annotate the surrounding method `writeFile` with `@FlaggedApi(Flags.FLAG_SYSTEM_SERVER_ROLE_CONTROLLER_ENABLED) to transfer requirement to caller`)"
errorLine1=" roles = new RolesState(mVersion, packagesHash,"
diff --git a/service/proto/role_service.proto b/service/proto/role_service.proto
index f982ead5b..fb1866d83 100644
--- a/service/proto/role_service.proto
+++ b/service/proto/role_service.proto
@@ -56,4 +56,7 @@ message RoleProto {
// Whether fallback holders are enabled for this role.
optional bool fallback_enabled = 3;
+
+ // The active user id of this cross user role.
+ optional int32 active_user_id = 4;
}
diff --git a/tests/apex/Android.bp b/tests/apex/Android.bp
index 18f1bea75..9dfbdf589 100644
--- a/tests/apex/Android.bp
+++ b/tests/apex/Android.bp
@@ -31,6 +31,8 @@ android_test {
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.test.ext.truth",
+ "com.android.permission.flags-aconfig-java",
+ "flag-junit",
"mockito-target-extended-minus-junit4",
],
jni_libs: [
diff --git a/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt b/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
index 6500b3926..e9c93a33a 100644
--- a/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
+++ b/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
@@ -20,12 +20,18 @@ import android.content.ApexEnvironment
import android.content.Context
import android.os.Process
import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.platform.app.InstrumentationRegistry
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.permission.flags.Flags
import com.google.common.truth.Truth.assertThat
import java.io.File
import org.junit.After
+import org.junit.Assume.assumeFalse
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@@ -49,11 +55,22 @@ class RolesPersistenceTest {
private val persistence = RolesPersistenceImpl {}
private val defaultRoles = mapOf(ROLE_NAME to setOf(HOLDER_1, HOLDER_2))
+ private val activeUserIds = mapOf(ROLE_NAME to USER_ID)
private val stateVersionUndefined = RolesState(VERSION_UNDEFINED, PACKAGE_HASH, defaultRoles)
private val stateVersionFallbackMigrated =
RolesState(VERSION_FALLBACK_MIGRATED, PACKAGE_HASH, defaultRoles, setOf(ROLE_NAME))
+ private val stateVersionActiveUserIds =
+ RolesState(
+ VERSION_ACTIVE_USER_IDS,
+ PACKAGE_HASH,
+ defaultRoles,
+ setOf(ROLE_NAME),
+ activeUserIds,
+ )
private val user = Process.myUserHandle()
+ @get:Rule val flagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
@Before
fun setUp() {
createMockDataDirectory()
@@ -84,16 +101,41 @@ class RolesPersistenceTest {
mockitoSession.finishMocking()
}
+ @RequiresFlagsDisabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
@Test
fun testWriteRead() {
+ assumeFalse(stateVersion == StateVersion.VERSION_ACTIVE_USER_IDS)
persistence.writeForUser(state, user)
val persistedState = persistence.readForUser(user)
assertThat(persistedState).isEqualTo(state)
}
+ @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ fun testWriteRead_supportsActiveUser() {
+ persistence.writeForUser(state, user)
+ val persistedState = persistence.readForUser(user)
+
+ assertThat(persistedState).isEqualTo(state)
+ }
+
+ @RequiresFlagsDisabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
@Test
fun testWriteCorruptReadFromReserveCopy() {
+ assumeFalse(stateVersion == StateVersion.VERSION_ACTIVE_USER_IDS)
+ persistence.writeForUser(state, user)
+ // Corrupt the primary file.
+ RolesPersistenceImpl.getFile(user)
+ .writeText("<roles version=\"-1\"><role name=\"com.foo.bar\"><holder")
+ val persistedState = persistence.readForUser(user)
+
+ assertThat(persistedState).isEqualTo(state)
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @Test
+ fun testWriteCorruptReadFromReserveCopy_supportsActiveUser() {
persistence.writeForUser(state, user)
// Corrupt the primary file.
RolesPersistenceImpl.getFile(user)
@@ -116,11 +158,13 @@ class RolesPersistenceTest {
when (stateVersion) {
StateVersion.VERSION_UNDEFINED -> stateVersionUndefined
StateVersion.VERSION_FALLBACK_MIGRATED -> stateVersionFallbackMigrated
+ StateVersion.VERSION_ACTIVE_USER_IDS -> stateVersionActiveUserIds
}
enum class StateVersion {
VERSION_UNDEFINED,
- VERSION_FALLBACK_MIGRATED
+ VERSION_FALLBACK_MIGRATED,
+ VERSION_ACTIVE_USER_IDS,
}
companion object {
@@ -130,10 +174,12 @@ class RolesPersistenceTest {
private const val VERSION_UNDEFINED = -1
private const val VERSION_FALLBACK_MIGRATED = 1
+ private const val VERSION_ACTIVE_USER_IDS = 2
private const val APEX_MODULE_NAME = "com.android.permission"
private const val PACKAGE_HASH = "packagesHash"
private const val ROLE_NAME = "roleName"
private const val HOLDER_1 = "holder1"
private const val HOLDER_2 = "holder2"
+ private const val USER_ID = 10
}
}
diff --git a/tests/cts/permission/AndroidManifest.xml b/tests/cts/permission/AndroidManifest.xml
index bb027b5f2..43fd97bb2 100644
--- a/tests/cts/permission/AndroidManifest.xml
+++ b/tests/cts/permission/AndroidManifest.xml
@@ -51,9 +51,6 @@
android:label="@string/perm_group_c"
android:name="android.permission.cts.groupC"/>
- <!-- for android.permission.cts.DevicePermissionsTest -->
- <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE"/>
-
<uses-permission android:name="android.permission.INJECT_EVENTS"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<application>
diff --git a/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
index aabdd8565..55c2d9f31 100644
--- a/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
@@ -384,7 +384,7 @@ public class PermissionUtils {
simulateReboot(packageName, intentAction, broadcastReceiver);
while ((System.currentTimeMillis() - startTime) < timeout
- && !jobStatus.contains("waiting")) {
+ && !isJobScheduled(jobStatus)) {
String cmd =
"cmd jobscheduler get-job-state -u " + Process.myUserHandle().getIdentifier()
+ " " + packageName + " " + jobId;
@@ -396,11 +396,16 @@ public class PermissionUtils {
// ignore interrupt
}
}
- if (!jobStatus.contains("waiting")) {
+ if (!isJobScheduled(jobStatus)) {
throw new IllegalStateException("The job didn't get scheduled in time.");
}
}
+ private static boolean isJobScheduled(String jobStatus) throws Exception {
+ return jobStatus.contains("waiting") || jobStatus.contains("pending")
+ || jobStatus.contains("ready") || jobStatus.contains("active");
+ }
+
private static void simulateReboot(@NonNull String packageName, @NonNull String intentAction,
@NonNull String broadcastReceiver) {
Intent jobSetupReceiverIntent = new Intent(intentAction);
diff --git a/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
index 48ccbe79f..96b3c4cc4 100644
--- a/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
+++ b/tests/cts/permission/permissionTestUtilLib/src/android/permission/cts/TestUtils.java
@@ -19,7 +19,10 @@ package android.permission.cts;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import android.app.UiAutomation;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.os.Process;
+import android.util.DisplayMetrics;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -202,4 +205,47 @@ public class TestUtils {
throw new RuntimeException(e);
}
}
+
+ /**
+ * This method checks for the minimum screen size described in <a href="https://source.android.com/docs/compatibility/14/android-14-cdd#7111_screen_size_and_shape">CDD</a>
+ */
+ public static boolean isCddCompliantScreenSize() {
+ if ((Resources.getSystem().getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK)
+ == Configuration.UI_MODE_TYPE_WATCH) {
+ Log.d(LOG_TAG, "UI mode is UI_MODE_TYPE_WATCH, skipping the min dp check");
+ return true;
+ }
+
+ int screenSize = Resources.getSystem().getConfiguration().screenLayout
+ & Configuration.SCREENLAYOUT_SIZE_MASK;
+ return switch (screenSize) {
+ case Configuration.SCREENLAYOUT_SIZE_SMALL -> hasMinScreenSize(426, 320);
+ case Configuration.SCREENLAYOUT_SIZE_NORMAL -> hasMinScreenSize(480, 320);
+ case Configuration.SCREENLAYOUT_SIZE_LARGE -> hasMinScreenSize(640, 480);
+ case Configuration.SCREENLAYOUT_SIZE_XLARGE -> hasMinScreenSize(960, 720);
+ default -> {
+ Log.e(LOG_TAG, "Unknown screen size: " + screenSize);
+ yield true;
+ }
+ };
+ }
+
+ private static boolean hasMinScreenSize(int minWidthDp, int minHeightDp) {
+ DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
+ float widthDp = (160f / metrics.densityDpi) * metrics.widthPixels;
+ float heightDp = (160f / metrics.densityDpi) * metrics.heightPixels;
+
+ // CDD does not seem to follow width & height convention correctly, hence checking both ways
+ boolean hasMinScreenSize = (widthDp >= minWidthDp && heightDp >= minHeightDp)
+ || (widthDp >= minHeightDp && heightDp >= minWidthDp);
+ if (!hasMinScreenSize) {
+ Log.d(LOG_TAG,
+ "Does not meet min screen size criteria, actual width/height = "
+ + metrics.widthPixels + "/" + metrics.heightPixels
+ + " expected minimum width/height = " + minWidthDp + "/" + minHeightDp
+ + " dpi="
+ + metrics.densityDpi);
+ }
+ return hasMinScreenSize;
+ }
}
diff --git a/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java b/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java
index f3f47631c..f4bed4ada 100644
--- a/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java
+++ b/tests/cts/permission/src/android/permission/cts/BackgroundPermissionsTest.java
@@ -24,6 +24,7 @@ import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_INTERNAL;
+import static android.health.connect.HealthPermissions.HEALTH_PERMISSION_GROUP;
import static android.permission.cts.PermissionUtils.getAppOp;
import static android.permission.cts.PermissionUtils.grantPermission;
import static android.permission.cts.PermissionUtils.install;
@@ -31,6 +32,7 @@ import static android.permission.cts.PermissionUtils.uninstallApp;
import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertNotEquals;
@@ -43,14 +45,21 @@ import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.permission.flags.Flags;
import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.ArrayMap;
import android.util.Log;
+import androidx.test.filters.SdkSuppress;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -72,6 +81,9 @@ public class BackgroundPermissionsTest {
private static final UiAutomation sUiAutomation =
InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@After
public void uninstallTestApp() {
uninstallApp(APP_PKG);
@@ -79,9 +91,24 @@ public class BackgroundPermissionsTest {
@Test
@AppModeFull(reason = "Instant apps cannot read properties of other packages")
- public void verifybackgroundPermissionsProperties() throws Exception {
+ public void verifyBackgroundPropertiesForPlatformPermissions() throws Exception {
+ verifyBackgroundPermissionsProperties("android");
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled({Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED})
+ @Test
+ @AppModeFull(reason = "Instant apps cannot read properties of other packages")
+ public void verifyBackgroundPropertiesForHealthPermissions() throws Exception {
+ String healthPackageName = sContext.getPackageManager().getPermissionGroupInfo(
+ HEALTH_PERMISSION_GROUP, /* flags= */ 0).packageName;
+ verifyBackgroundPermissionsProperties(healthPackageName);
+ }
+
+ private void verifyBackgroundPermissionsProperties(String packageName)
+ throws Exception {
PackageInfo pkg = sContext.getPackageManager().getPackageInfo(
- "android", PackageManager.GET_PERMISSIONS);
+ packageName, PackageManager.GET_PERMISSIONS);
ArrayMap<String, String> potentialBackgroundPermissionsToGroup = new ArrayMap<>();
int numPermissions = pkg.permissions.length;
@@ -97,11 +124,13 @@ public class BackgroundPermissionsTest {
}
}
+ int backgroundPermissionCount = 0;
for (int i = 0; i < numPermissions; i++) {
PermissionInfo permission = pkg.permissions[i];
String backgroundPermissionName = permission.backgroundPermission;
if (backgroundPermissionName != null) {
+ backgroundPermissionCount += 1;
Log.i(LOG_TAG, permission.name + "->" + backgroundPermissionName);
// foreground permissions must be dangerous
@@ -115,6 +144,8 @@ public class BackgroundPermissionsTest {
.containsKey(backgroundPermissionName));
}
}
+ // Tested packages must have at least one permission linked with a background permission.
+ assertThat(backgroundPermissionCount).isGreaterThan(0);
}
/**
diff --git a/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt b/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt
index ff333c6a0..145936382 100644
--- a/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt
+++ b/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt
@@ -22,6 +22,10 @@ import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_S
import android.app.Instrumentation
import android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
import android.companion.virtual.VirtualDeviceManager.VirtualDevice
+import android.companion.virtual.VirtualDeviceParams
+import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM
+import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT
+import android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME
@@ -33,10 +37,7 @@ import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.os.Build
import android.os.UserHandle
import android.permission.PermissionManager
-import android.permission.flags.Flags
import android.platform.test.annotations.AppModeFull
-import android.platform.test.annotations.RequiresFlagsDisabled
-import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.virtualdevice.cts.common.VirtualDeviceRule
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -66,18 +67,25 @@ class DevicePermissionsTest {
private lateinit var permissionManager: PermissionManager
@get:Rule
- var mVirtualDeviceRule = VirtualDeviceRule.withAdditionalPermissions(
+ var mVirtualDeviceRule =
+ VirtualDeviceRule.withAdditionalPermissions(
Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
- Manifest.permission.GET_RUNTIME_PERMISSIONS
+ Manifest.permission.GET_RUNTIME_PERMISSIONS,
)
@Rule @JvmField val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@Before
fun setup() {
- virtualDevice = mVirtualDeviceRule.createManagedVirtualDevice()
+ virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice(
+ // Without custom audio policy, the RECORD_AUDIO permission won't be device aware.
+ VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_CUSTOM)
+ .build()
+ )
virtualDeviceContext = defaultDeviceContext.createDeviceContext(virtualDevice.deviceId)
permissionManager = virtualDeviceContext.getSystemService(PermissionManager::class.java)!!
persistentDeviceId = virtualDevice.persistentDeviceId!!
@@ -89,43 +97,47 @@ class DevicePermissionsTest {
runShellCommandOrThrow("pm uninstall $TEST_PACKAGE_NAME")
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testDeviceAwareRuntimePermissionIsGranted() {
- grantPermissionAndAssertGranted(Manifest.permission.CAMERA, virtualDeviceContext)
+ fun virtualDeviceDefaultPolicy_deviceAwarePermissionFallsBackToDefaultDevice() {
+ virtualDevice =
+ mVirtualDeviceRule.createManagedVirtualDevice(
+ // With default audio policy, the RECORD_AUDIO permission won't be device aware.
+ VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_DEFAULT)
+ .build()
+ )
+ virtualDeviceContext = defaultDeviceContext.createDeviceContext(virtualDevice.deviceId)
+
+ grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, defaultDeviceContext)
+ assertPermission(DEVICE_AWARE_PERMISSION, PERMISSION_GRANTED, virtualDeviceContext)
}
- @RequiresFlagsDisabled(Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED)
@Test
- fun testDeviceAwareRuntimePermissionGrantIsInherited() {
- grantPermissionAndAssertGranted(Manifest.permission.CAMERA, defaultDeviceContext)
+ fun virtualDeviceCustomPolicy_deviceAwarePermissionGrantedOnVirtualDevice() {
+ // When a device aware permission is granted on the default device, it's not automatically
+ // granted on the virtual device.
+ grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, defaultDeviceContext)
+ assertPermission(DEVICE_AWARE_PERMISSION, PERMISSION_DENIED, virtualDeviceContext)
- assertPermission(Manifest.permission.CAMERA, PERMISSION_GRANTED, virtualDeviceContext)
+ grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
}
@Test
- fun testNonDeviceAwareRuntimePermissionGrantIsInherited() {
+ fun normalPermissionGrantedOnDefaultDevice_isGrantedOnVirtualDevice() {
grantPermissionAndAssertGranted(NON_DEVICE_AWARE_PERMISSION, defaultDeviceContext)
assertPermission(NON_DEVICE_AWARE_PERMISSION, PERMISSION_GRANTED, virtualDeviceContext)
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testDeviceAwareRuntimePermissionIsRevoked() {
+ fun virtualDeviceCustomPolicy_deviceAwarePermissionIsRevoked() {
grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
revokePermissionAndAssertDenied(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
}
@Test
- fun testNonDeviceAwareRuntimePermissionIsRevokedForDefaultDevice() {
+ fun normalPermissionRevokedFromVirtualDevice_isAlsoRevokedOnDefaultDevice() {
grantPermissionAndAssertGranted(NON_DEVICE_AWARE_PERMISSION, defaultDeviceContext)
assertPermission(NON_DEVICE_AWARE_PERMISSION, PERMISSION_GRANTED, virtualDeviceContext)
// Revoke call from virtualDeviceContext should revoke for default device as well.
@@ -134,24 +146,24 @@ class DevicePermissionsTest {
}
@Test
- fun testNormalPermissionGrantIsInherited() {
+ fun normalPermission_isInheritedOnVirtualDevice() {
assertPermission(Manifest.permission.INTERNET, PERMISSION_GRANTED, virtualDeviceContext)
}
@Test
- fun testSignaturePermissionGrantIsInherited() {
+ fun signaturePermission_isInheritedOnVirtualDevice() {
assertPermission(CUSTOM_SIGNATURE_PERMISSION, PERMISSION_GRANTED, virtualDeviceContext)
}
@Test
- fun testOneTimePermissionIsRevoked() {
+ fun virtualDeviceCustomPolicy_oneTimePermissionIsRevoked() {
grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
virtualDeviceContext.packageManager.updatePermissionFlags(
DEVICE_AWARE_PERMISSION,
TEST_PACKAGE_NAME,
FLAG_PERMISSION_ONE_TIME,
FLAG_PERMISSION_ONE_TIME,
- UserHandle.of(virtualDeviceContext.userId)
+ UserHandle.of(virtualDeviceContext.userId),
)
permissionManager.startOneTimePermissionSession(
@@ -159,19 +171,15 @@ class DevicePermissionsTest {
0,
0,
IMPORTANCE_FOREGROUND,
- IMPORTANCE_FOREGROUND_SERVICE
+ IMPORTANCE_FOREGROUND_SERVICE,
)
eventually {
assertPermission(DEVICE_AWARE_PERMISSION, PERMISSION_DENIED, virtualDeviceContext)
}
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testRevokeSelfPermissionOnKill() {
+ fun virtualDeviceCustomPolicy_revokeSelfPermissionOnKill_permissionIsRevoked() {
grantPermissionAndAssertGranted(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
revokeSelfPermission(DEVICE_AWARE_PERMISSION, virtualDeviceContext)
@@ -180,105 +188,90 @@ class DevicePermissionsTest {
}
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testGrantAndRevokeDeviceAwarePermissionByPersistentDeviceId() {
- val deviceAwarePermission = DEVICE_AWARE_PERMISSION
-
+ fun usePersistentDeviceIdToRevokeDeviceAwarePermission_permissionIsRevoked() {
permissionManager.grantRuntimePermission(
TEST_PACKAGE_NAME,
- deviceAwarePermission,
- persistentDeviceId
+ DEVICE_AWARE_PERMISSION,
+ persistentDeviceId,
)
assertThat(
permissionManager.checkPermission(
- deviceAwarePermission,
+ DEVICE_AWARE_PERMISSION,
TEST_PACKAGE_NAME,
- virtualDevice.persistentDeviceId!!
+ virtualDevice.persistentDeviceId!!,
)
)
.isEqualTo(PERMISSION_GRANTED)
assertThat(
permissionManager.checkPermission(
- deviceAwarePermission,
+ DEVICE_AWARE_PERMISSION,
TEST_PACKAGE_NAME,
- PERSISTENT_DEVICE_ID_DEFAULT
+ PERSISTENT_DEVICE_ID_DEFAULT,
)
)
.isEqualTo(PERMISSION_DENIED)
permissionManager.revokeRuntimePermission(
TEST_PACKAGE_NAME,
- deviceAwarePermission,
+ DEVICE_AWARE_PERMISSION,
persistentDeviceId,
- "test"
+ "test",
)
assertThat(
permissionManager.checkPermission(
- deviceAwarePermission,
+ DEVICE_AWARE_PERMISSION,
TEST_PACKAGE_NAME,
- virtualDevice.persistentDeviceId!!
+ virtualDevice.persistentDeviceId!!,
)
)
.isEqualTo(PERMISSION_DENIED)
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testUpdateAndGetPermissionFlagsByPersistentDeviceId() {
- val deviceAwarePermission = DEVICE_AWARE_PERMISSION
+ fun updateAndGetPermissionFlagsByPersistentDeviceId() {
val flagMask = FLAG_PERMISSION_USER_SET or FLAG_PERMISSION_USER_FIXED
val flag = FLAG_PERMISSION_USER_SET
assertThat(
permissionManager.getPermissionFlags(
TEST_PACKAGE_NAME,
- deviceAwarePermission,
- persistentDeviceId
+ DEVICE_AWARE_PERMISSION,
+ persistentDeviceId,
)
)
.isEqualTo(0)
permissionManager.updatePermissionFlags(
TEST_PACKAGE_NAME,
- deviceAwarePermission,
+ DEVICE_AWARE_PERMISSION,
persistentDeviceId,
flagMask,
- flag
+ flag,
)
assertThat(
permissionManager.getPermissionFlags(
TEST_PACKAGE_NAME,
- deviceAwarePermission,
- persistentDeviceId
+ DEVICE_AWARE_PERMISSION,
+ persistentDeviceId,
)
)
.isEqualTo(FLAG_PERMISSION_USER_SET)
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testAllPermissionStatesApiGrantForVirtualDevice() {
+ fun permissionGrantedOnVirtualDevice_reflectedInGetAllPermissionStatesApi() {
// Setting a flag explicitly so that the permission consistently stays in the state
permissionManager.updatePermissionFlags(
TEST_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
PERSISTENT_DEVICE_ID_DEFAULT,
FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED,
- FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+ FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED,
)
assertThat(
@@ -291,7 +284,7 @@ class DevicePermissionsTest {
permissionManager.grantRuntimePermission(
TEST_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
- persistentDeviceId
+ persistentDeviceId,
)
val permissionStateMap =
@@ -312,7 +305,7 @@ class DevicePermissionsTest {
TEST_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
persistentDeviceId,
- "test"
+ "test",
)
assertThat(
@@ -323,12 +316,8 @@ class DevicePermissionsTest {
.isFalse()
}
- @RequiresFlagsEnabled(
- Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
- )
@Test
- fun testAllPermissionStatesApiFlagsForVirtualDevice() {
+ fun setPermissionFlagOnVirtualDevice_reflectedInGetAllPermissionStatesApi() {
val flagMask = FLAG_PERMISSION_USER_SET or FLAG_PERMISSION_USER_FIXED
val flag = FLAG_PERMISSION_USER_SET
@@ -340,7 +329,7 @@ class DevicePermissionsTest {
DEVICE_AWARE_PERMISSION,
persistentDeviceId,
flagMask,
- flag
+ flag,
)
assertThat(
@@ -349,7 +338,7 @@ class DevicePermissionsTest {
.getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)[
DEVICE_AWARE_PERMISSION]!!
.flags,
- flag
+ flag,
)
)
.isTrue()
@@ -360,15 +349,14 @@ class DevicePermissionsTest {
.getAllPermissionStates(TEST_PACKAGE_NAME, persistentDeviceId)[
DEVICE_AWARE_PERMISSION]!!
.flags,
- FLAG_PERMISSION_USER_FIXED
+ FLAG_PERMISSION_USER_FIXED,
)
)
.isFalse()
}
- @RequiresFlagsEnabled(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
@Test
- fun testAllPermissionStatesApiGrantForDefaultDevice() {
+ fun permissionGrantedOnDefaultDevice_reflectedInGetAllPermissionStatesApi() {
// Setting a flag explicitly so that the permission consistently stays in the state upon
// revoke
permissionManager.updatePermissionFlags(
@@ -376,13 +364,13 @@ class DevicePermissionsTest {
DEVICE_AWARE_PERMISSION,
PERSISTENT_DEVICE_ID_DEFAULT,
FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED,
- FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+ FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED,
)
permissionManager.grantRuntimePermission(
TEST_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
- PERSISTENT_DEVICE_ID_DEFAULT
+ PERSISTENT_DEVICE_ID_DEFAULT,
)
assertThat(
@@ -404,7 +392,7 @@ class DevicePermissionsTest {
TEST_PACKAGE_NAME,
DEVICE_AWARE_PERMISSION,
PERSISTENT_DEVICE_ID_DEFAULT,
- "test"
+ "test",
)
assertThat(
@@ -416,9 +404,8 @@ class DevicePermissionsTest {
.isFalse()
}
- @RequiresFlagsEnabled(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
@Test
- fun testAllPermissionStatesApiFlagsForDefaultDevice() {
+ fun setPermissionFlagOnDefaultDevice_reflectedInGetAllPermissionStatesApi() {
val flagMask = FLAG_PERMISSION_USER_SET or FLAG_PERMISSION_USER_FIXED
val flag = FLAG_PERMISSION_USER_SET
@@ -434,7 +421,7 @@ class DevicePermissionsTest {
DEVICE_AWARE_PERMISSION,
PERSISTENT_DEVICE_ID_DEFAULT,
flagMask,
- flag
+ flag,
)
assertThat(
@@ -443,7 +430,7 @@ class DevicePermissionsTest {
.getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)[
DEVICE_AWARE_PERMISSION]!!
.flags,
- flag
+ flag,
)
)
.isTrue()
@@ -454,19 +441,18 @@ class DevicePermissionsTest {
.getAllPermissionStates(TEST_PACKAGE_NAME, PERSISTENT_DEVICE_ID_DEFAULT)[
DEVICE_AWARE_PERMISSION]!!
.flags,
- FLAG_PERMISSION_USER_FIXED
+ FLAG_PERMISSION_USER_FIXED,
)
)
.isFalse()
}
- @RequiresFlagsEnabled(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED)
@Test
- fun testAllPermissionStatesApiThatNonDeviceAwareRuntimePermissionGrantIsNotInherited() {
+ fun getAllPermissionStates_normalPermissionIsNotInherited() {
permissionManager.grantRuntimePermission(
TEST_PACKAGE_NAME,
NON_DEVICE_AWARE_PERMISSION,
- PERSISTENT_DEVICE_ID_DEFAULT
+ PERSISTENT_DEVICE_ID_DEFAULT,
)
assertThat(
@@ -501,7 +487,7 @@ class DevicePermissionsTest {
context.packageManager.grantRuntimePermission(
TEST_PACKAGE_NAME,
permissionName,
- UserHandle.of(context.userId)
+ UserHandle.of(context.userId),
)
assertPermission(permissionName, PERMISSION_GRANTED, context)
}
@@ -510,18 +496,14 @@ class DevicePermissionsTest {
context.packageManager.revokeRuntimePermission(
TEST_PACKAGE_NAME,
permissionName,
- UserHandle.of(context.userId)
+ UserHandle.of(context.userId),
)
assertPermission(permissionName, PERMISSION_DENIED, context)
}
- private fun assertPermission(
- permissionName: String,
- permissionState: Int,
- context: Context,
- ) {
- assertThat(context.packageManager.checkPermission(permissionName, TEST_PACKAGE_NAME))
- .isEqualTo(permissionState)
+ private fun assertPermission(permissionName: String, permissionState: Int, context: Context) {
+ val uid = defaultDeviceContext.packageManager.getApplicationInfo(TEST_PACKAGE_NAME, 0).uid
+ assertThat(context.checkPermission(permissionName, -1, uid)).isEqualTo(permissionState)
}
companion object {
diff --git a/tests/cts/permission/src/android/permission/cts/NoWifiStatePermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoWifiStatePermissionTest.java
index a0637827c..9fff22747 100644
--- a/tests/cts/permission/src/android/permission/cts/NoWifiStatePermissionTest.java
+++ b/tests/cts/permission/src/android/permission/cts/NoWifiStatePermissionTest.java
@@ -30,9 +30,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.UserHelper;
-
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -217,11 +214,6 @@ public class NoWifiStatePermissionTest {
*/
@Test(expected = SecurityException.class)
public void testSetWifiEnabled() {
- // Skip the test for passenger on Multi-user-multi-display devices for Automotive
- UserHelper userHelper = new UserHelper(sContext);
- Assume.assumeFalse(
- "Skipped for visible background User as wifi is disabled for visible background "
- + "user.", userHelper.isVisibleBackgroundUser());
mWifiManager.setWifiEnabled(true);
}
}
diff --git a/tests/cts/permission/src/android/permission/cts/OneTimePermissionTest.java b/tests/cts/permission/src/android/permission/cts/OneTimePermissionTest.java
index 2692c6e7c..291633aab 100644
--- a/tests/cts/permission/src/android/permission/cts/OneTimePermissionTest.java
+++ b/tests/cts/permission/src/android/permission/cts/OneTimePermissionTest.java
@@ -290,34 +290,18 @@ public class OneTimePermissionTest {
}
private void exitApp() {
- boolean[] hasExited = {false};
- try {
- new Thread(() -> {
- while (!hasExited[0]) {
- DreamManager mDreamManager = mContext.getSystemService(DreamManager.class);
- mUiDevice.pressBack();
- runWithShellPermissionIdentity(() -> {
- if (mDreamManager.isDreaming()) {
- mDreamManager.stopDream();
- }
- });
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- }
+ eventually(() -> {
+ mUiDevice.pressBack();
+ runWithShellPermissionIdentity(() -> {
+ DreamManager mDreamManager = mContext.getSystemService(DreamManager.class);
+ if (mDreamManager.isDreaming()) {
+ mDreamManager.stopDream();
}
- }).start();
- eventually(() -> {
- runWithShellPermissionIdentity(() -> {
- if (mActivityManager.getPackageImportance(APP_PKG_NAME)
- <= IMPORTANCE_FOREGROUND) {
- throw new AssertionError("Unable to exit application");
- }
- });
+ Assert.assertFalse("Unable to exit application",
+ mActivityManager.getPackageImportance(APP_PKG_NAME)
+ <= IMPORTANCE_FOREGROUND);
});
- } finally {
- hasExited[0] = true;
- }
+ });
}
private void clickOneTimeButton() throws Throwable {
diff --git a/tests/cts/permission/src/android/permission/cts/PermissionUpdateListenerTest.java b/tests/cts/permission/src/android/permission/cts/PermissionUpdateListenerTest.java
index 4325ace79..86b8fa895 100644
--- a/tests/cts/permission/src/android/permission/cts/PermissionUpdateListenerTest.java
+++ b/tests/cts/permission/src/android/permission/cts/PermissionUpdateListenerTest.java
@@ -18,10 +18,10 @@ package android.permission.cts;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.google.common.truth.Truth.assertThat;
+import android.Manifest;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceManager.VirtualDevice;
import android.content.Context;
@@ -30,8 +30,6 @@ import android.content.pm.PackageManager.OnPermissionsChangedListener;
import android.permission.flags.Flags;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.virtualdevice.cts.common.VirtualDeviceRule;
import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
@@ -69,10 +67,10 @@ public class PermissionUpdateListenerTest {
private int mTestAppUid;
@Rule
- public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.createDefault();
-
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+ public VirtualDeviceRule mVirtualDeviceRule = VirtualDeviceRule.withAdditionalPermissions(
+ "android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS",
+ Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+ Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
@Before
public void setup() throws PackageManager.NameNotFoundException, InterruptedException {
@@ -99,15 +97,11 @@ public class PermissionUpdateListenerTest {
}
};
- runWithShellPermissionIdentity(() -> {
- mPackageManager.addOnPermissionsChangeListener(permissionsChangedListener);
- mPackageManager.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
- mDefaultContext.getUser());
- });
+ mPackageManager.addOnPermissionsChangeListener(permissionsChangedListener);
+ mPackageManager.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
+ mDefaultContext.getUser());
countDownLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
- runWithShellPermissionIdentity(() -> {
- mPackageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
- });
+ mPackageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
assertThat(countDownLatch.getCount()).isEqualTo(0);
}
@@ -132,16 +126,12 @@ public class PermissionUpdateListenerTest {
final PackageManager packageManager = context.getPackageManager();
TestOnPermissionsChangedListener permissionsChangedListener =
new TestOnPermissionsChangedListener(1);
- runWithShellPermissionIdentity(() -> {
- packageManager.addOnPermissionsChangeListener(permissionsChangedListener);
- packageManager.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
- mDefaultContext.getUser());
- });
+ packageManager.addOnPermissionsChangeListener(permissionsChangedListener);
+ packageManager.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
+ mDefaultContext.getUser());
permissionsChangedListener.waitForPermissionChangedCallbacks();
- runWithShellPermissionIdentity(() -> {
- packageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
- });
+ packageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
String deviceId = permissionsChangedListener.getNotifiedDeviceId(mTestAppUid);
assertThat(deviceId).isEqualTo(expectedDeviceId);
@@ -168,17 +158,14 @@ public class PermissionUpdateListenerTest {
final PackageManager packageManager = context.getPackageManager();
TestOnPermissionsChangedListener permissionsChangedListener =
new TestOnPermissionsChangedListener(1);
- runWithShellPermissionIdentity(() -> {
- packageManager.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
- mDefaultContext.getUser());
- packageManager.addOnPermissionsChangeListener(permissionsChangedListener);
- packageManager.revokeRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
- mDefaultContext.getUser());
- });
+ packageManager.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
+ mDefaultContext.getUser());
+ packageManager.addOnPermissionsChangeListener(permissionsChangedListener);
+ packageManager.revokeRuntimePermission(PACKAGE_NAME, PERMISSION_NAME,
+ mDefaultContext.getUser());
+
permissionsChangedListener.waitForPermissionChangedCallbacks();
- runWithShellPermissionIdentity(() -> {
- packageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
- });
+ packageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
String deviceId = permissionsChangedListener.getNotifiedDeviceId(mTestAppUid);
assertThat(deviceId).isEqualTo(expectedDeviceId);
@@ -205,16 +192,12 @@ public class PermissionUpdateListenerTest {
TestOnPermissionsChangedListener permissionsChangedListener =
new TestOnPermissionsChangedListener(1);
final PackageManager packageManager = context.getPackageManager();
- runWithShellPermissionIdentity(() -> {
- packageManager.addOnPermissionsChangeListener(permissionsChangedListener);
- int flag = PackageManager.FLAG_PERMISSION_USER_SET;
- packageManager.updatePermissionFlags(PERMISSION_NAME, PACKAGE_NAME, flag, flag,
- mDefaultContext.getUser());
- });
+ packageManager.addOnPermissionsChangeListener(permissionsChangedListener);
+ int flag = PackageManager.FLAG_PERMISSION_USER_SET;
+ packageManager.updatePermissionFlags(PERMISSION_NAME, PACKAGE_NAME, flag, flag,
+ mDefaultContext.getUser());
permissionsChangedListener.waitForPermissionChangedCallbacks();
- runWithShellPermissionIdentity(() -> {
- packageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
- });
+ packageManager.removeOnPermissionsChangeListener(permissionsChangedListener);
String deviceId = permissionsChangedListener.getNotifiedDeviceId(mTestAppUid);
assertThat(deviceId).isEqualTo(expectedDeviceId);
diff --git a/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java b/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
index 776a1065e..f2d59cbe7 100755
--- a/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
+++ b/tests/cts/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
@@ -45,9 +45,15 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
+import android.health.connect.HealthPermissions;
import android.os.Build;
import android.permission.PermissionManager;
import android.permission.PermissionManager.SplitPermissionInfo;
+import android.permission.flags.Flags;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SdkSuppress;
@@ -56,6 +62,7 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.ApiLevelUtil;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -71,6 +78,9 @@ public class SplitPermissionsSystemTest {
private List<SplitPermissionInfo> mSplitPermissions;
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Before
public void before() {
Context context = InstrumentationRegistry.getContext();
@@ -87,9 +97,14 @@ public class SplitPermissionsSystemTest {
for (SplitPermissionInfo split : mSplitPermissions) {
String splitPermission = split.getSplitPermission();
- boolean isAndroid = splitPermission.startsWith("android");
- if (!isAndroid) {
+ // Due to limitation with accessing flag values in tests, BODY_SENSORS relevant splits
+ // are handled in its dedicated tests.
+ boolean shouldSkip =
+ !splitPermission.startsWith("android")
+ || splitPermission.equals(BODY_SENSORS)
+ || splitPermission.equals(BODY_SENSORS_BACKGROUND);
+ if (shouldSkip) {
continue;
}
@@ -149,9 +164,6 @@ public class SplitPermissionsSystemTest {
case BLUETOOTH_SCAN:
assertSplit(split, Build.VERSION_CODES.S, BLUETOOTH, BLUETOOTH_ADMIN);
break;
- case BODY_SENSORS:
- assertSplit(split, Build.VERSION_CODES.TIRAMISU, BODY_SENSORS_BACKGROUND);
- break;
case ACCESS_MEDIA_LOCATION:
case READ_MEDIA_IMAGES:
case READ_MEDIA_VIDEO:
@@ -160,7 +172,56 @@ public class SplitPermissionsSystemTest {
}
}
- assertEquals(24, seenSplits.size());
+ assertEquals(23, seenSplits.size());
+ }
+
+ @RequiresFlagsDisabled({Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED})
+ @Test
+ public void
+ validateBodySensors_beforeGranularHealthPermissions_isSplitToBodySensorsBackground() {
+ assumeTrue(ApiLevelUtil.isAtLeast(Build.VERSION_CODES.Q));
+
+ mSplitPermissions.stream()
+ .filter(split -> split.getSplitPermission().equals(BODY_SENSORS))
+ .findFirst()
+ .ifPresent(
+ split ->
+ assertSplit(
+ split,
+ Build.VERSION_CODES.TIRAMISU,
+ BODY_SENSORS_BACKGROUND));
+ }
+
+ @RequiresFlagsEnabled({Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED})
+ @Test
+ public void validateBodySensors_afterGranularHealthPermissions_isSplitToReadHeartRate() {
+ // TODO: Change this to Baklava when available.
+ assumeTrue(ApiLevelUtil.isAtLeast(36));
+
+ SplitPermissionInfo legacyBodySensorPermissionInfo = null;
+ SplitPermissionInfo readHeartRatePermissionInfo = null;
+ SplitPermissionInfo bodySensorBackgroundPermissionInfo = null;
+ for (SplitPermissionInfo split : mSplitPermissions) {
+ if (split.getSplitPermission().equals(BODY_SENSORS)
+ && split.getNewPermissions().contains(BODY_SENSORS_BACKGROUND)) {
+ legacyBodySensorPermissionInfo = split;
+ } else if (split.getSplitPermission().equals(BODY_SENSORS)
+ && split.getNewPermissions().contains(HealthPermissions.READ_HEART_RATE)) {
+ readHeartRatePermissionInfo = split;
+ } else if (split.getSplitPermission().equals(BODY_SENSORS_BACKGROUND)) {
+ bodySensorBackgroundPermissionInfo = split;
+ }
+ }
+ // Assert BODY_SENSORS is split to BODY_SENSORS_BACKGROUND and READ_HEART_RATE.
+ assertSplit(
+ legacyBodySensorPermissionInfo,
+ Build.VERSION_CODES.TIRAMISU,
+ BODY_SENSORS_BACKGROUND);
+ assertSplit(readHeartRatePermissionInfo, HealthPermissions.READ_HEART_RATE);
+ // Assert BODY_SENSORS_BACKGROUND is split to READ_HEALTH_DATA_IN_BACKGROUND.
+ assertSplit(
+ bodySensorBackgroundPermissionInfo,
+ HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
}
private void assertSplit(SplitPermissionInfo split, int targetSdk, String... permission) {
diff --git a/tests/cts/permissionmultidevice/AndroidManifest.xml b/tests/cts/permissionmultidevice/AndroidManifest.xml
index 7d04d140e..9bad85813 100644
--- a/tests/cts/permissionmultidevice/AndroidManifest.xml
+++ b/tests/cts/permissionmultidevice/AndroidManifest.xml
@@ -24,8 +24,6 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
- <uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
- <uses-permission android:name="android.permission.ADD_TRUSTED_DISPLAY" />
<application>
<uses-library android:name="android.test.runner" />
diff --git a/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PermissionUtils.kt b/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PermissionUtils.kt
index 8a6290212..dd2d3e645 100644
--- a/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PermissionUtils.kt
+++ b/tests/cts/permissionmultidevice/TestUtils/src/android/permissionmultidevice/cts/PermissionUtils.kt
@@ -18,9 +18,6 @@ package android.permissionmultidevice.cts
import android.content.Context
import android.content.pm.PackageManager
-import android.content.res.Configuration
-import android.content.res.Resources
-import android.util.Log
object PermissionUtils {
private val TAG = PermissionUtils::class.java.getSimpleName()
@@ -33,42 +30,4 @@ object PermissionUtils {
fun isWatch(context: Context): Boolean =
context.packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
-
- /**
- * This method checks for the minimum screen size described in CDD {@see
- * https://source.android.com/docs/compatibility/14/android-14-cdd#7111_screen_size_and_shape}
- */
- fun isCddCompliantScreenSize(): Boolean {
- if (
- Resources.getSystem().configuration.uiMode and Configuration.UI_MODE_TYPE_MASK ==
- Configuration.UI_MODE_TYPE_WATCH
- ) {
- Log.d(TAG, "UI mode is UI_MODE_TYPE_WATCH, skipping the min dp check")
- return true
- }
-
- val screenSize =
- Resources.getSystem().configuration.screenLayout and
- Configuration.SCREENLAYOUT_SIZE_MASK
- return when (screenSize) {
- Configuration.SCREENLAYOUT_SIZE_SMALL -> hasMinScreenSize(426, 320)
- Configuration.SCREENLAYOUT_SIZE_NORMAL -> hasMinScreenSize(480, 320)
- Configuration.SCREENLAYOUT_SIZE_LARGE -> hasMinScreenSize(640, 480)
- Configuration.SCREENLAYOUT_SIZE_XLARGE -> hasMinScreenSize(960, 720)
- else -> {
- Log.e(TAG, "Unknown screen size: $screenSize")
- true
- }
- }
- }
-
- private fun hasMinScreenSize(minWidthDp: Int, minHeightDp: Int): Boolean {
- val dpi = Resources.getSystem().displayMetrics.densityDpi
- val widthDp = (160f / dpi) * Resources.getSystem().displayMetrics.widthPixels
- val heightDp = (160f / dpi) * Resources.getSystem().displayMetrics.heightPixels
-
- // CDD does seem to follow width & height convention correctly, hence checking both ways
- return (widthDp >= minWidthDp && heightDp >= minHeightDp) ||
- (widthDp >= minHeightDp && heightDp >= minWidthDp)
- }
}
diff --git a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt
index ac5564e37..907917f6f 100644
--- a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt
+++ b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/AppPermissionsTest.kt
@@ -26,8 +26,8 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.permission.PermissionManager
+import android.permission.cts.TestUtils
import android.permission.flags.Flags
-import android.permissionmultidevice.cts.PermissionUtils.isCddCompliantScreenSize
import android.platform.test.annotations.RequiresFlagsEnabled
import android.virtualdevice.cts.common.VirtualDeviceRule
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -79,7 +79,7 @@ class AppPermissionsTest {
assumeFalse(PermissionUtils.isAutomotive(defaultDeviceContext))
assumeFalse(PermissionUtils.isTv(defaultDeviceContext))
assumeFalse(PermissionUtils.isWatch(defaultDeviceContext))
- assumeTrue(isCddCompliantScreenSize())
+ assumeTrue(TestUtils.isCddCompliantScreenSize())
PackageManagementUtils.installPackage(APP_APK_PATH_STREAMING)
diff --git a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
index f0a0e3fc1..5c7573a0b 100644
--- a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
+++ b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
@@ -21,6 +21,7 @@ import android.app.Instrumentation
import android.companion.virtual.VirtualDeviceManager
import android.companion.virtual.VirtualDeviceParams
import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM
+import android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT
import android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CAMERA
import android.content.ComponentName
import android.content.Intent
@@ -32,6 +33,7 @@ import android.hardware.display.VirtualDisplay
import android.os.Build
import android.os.Bundle
import android.os.RemoteCallback
+import android.permission.PermissionManager
import android.permission.flags.Flags
import android.permissionmultidevice.cts.PackageManagementUtils.installPackage
import android.permissionmultidevice.cts.PackageManagementUtils.uninstallPackage
@@ -39,7 +41,9 @@ import android.permissionmultidevice.cts.UiAutomatorUtils.click
import android.permissionmultidevice.cts.UiAutomatorUtils.findTextForView
import android.permissionmultidevice.cts.UiAutomatorUtils.waitFindObject
import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.annotations.RequiresFlagsEnabled
+import android.provider.Settings
import android.view.Display
import android.virtualdevice.cts.common.VirtualDeviceRule
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -47,7 +51,6 @@ import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import com.android.compatibility.common.util.SystemUtil
-import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
@@ -69,8 +72,12 @@ class DeviceAwarePermissionGrantTest {
private lateinit var virtualDevice: VirtualDeviceManager.VirtualDevice
private lateinit var virtualDisplay: VirtualDisplay
private lateinit var deviceDisplayName: String
+ private val permissionManager =
+ defaultDeviceContext.getSystemService(PermissionManager::class.java)!!
- @get:Rule var virtualDeviceRule = VirtualDeviceRule.createDefault()
+ @get:Rule
+ var virtualDeviceRule: VirtualDeviceRule =
+ VirtualDeviceRule.withAdditionalPermissions(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
@Before
fun setup() {
@@ -91,7 +98,7 @@ class DeviceAwarePermissionGrantTest {
val displayConfigBuilder =
VirtualDeviceRule.createDefaultVirtualDisplayConfigBuilder(
DISPLAY_WIDTH,
- DISPLAY_HEIGHT
+ DISPLAY_HEIGHT,
)
.setFlags(
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC or
@@ -108,46 +115,95 @@ class DeviceAwarePermissionGrantTest {
@After
fun cleanup() {
uninstallPackage(APP_PACKAGE_NAME, requireSuccess = false)
+ Thread.sleep(2000)
}
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
)
@Test
fun onHostDevice_requestPermissionForHostDevice_shouldGrantPermission() {
+ assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
+ assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+
testGrantPermissionForDevice(
Display.DEFAULT_DISPLAY,
DEVICE_ID_DEFAULT,
false,
"",
expectPermissionGrantedOnDefaultDevice = true,
- expectPermissionGrantedOnRemoteDevice = false
+ expectPermissionGrantedOnRemoteDevice = false,
)
}
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
)
@Test
fun onHostDevice_requestPermissionForRemoteDevice_shouldGrantPermission() {
+ assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
+ assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+
testGrantPermissionForDevice(
Display.DEFAULT_DISPLAY,
virtualDevice.deviceId,
true,
deviceDisplayName,
expectPermissionGrantedOnDefaultDevice = false,
- expectPermissionGrantedOnRemoteDevice = true
+ expectPermissionGrantedOnRemoteDevice = true,
+ )
+ }
+
+ @Test
+ fun onHostDevice_requestPermissionForRemoteDeviceAfterPermissionGrantedToHostDevice() {
+ instrumentation.uiAutomation.grantRuntimePermission(APP_PACKAGE_NAME, PERMISSION)
+
+ assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, true)
+ assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+
+ testGrantPermissionForDevice(
+ Display.DEFAULT_DISPLAY,
+ virtualDevice.deviceId,
+ true,
+ deviceDisplayName,
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnRemoteDevice = true,
+ )
+ }
+
+ @Test
+ fun onHostDevice_requestPermissionForHostDeviceAfterPermissionGrantedToRemoteDevice() {
+ permissionManager.grantRuntimePermission(
+ APP_PACKAGE_NAME,
+ PERMISSION,
+ virtualDevice.persistentDeviceId!!,
+ )
+
+ assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
+ assertAppHasPermissionForDevice(virtualDevice.deviceId, true)
+
+ testGrantPermissionForDevice(
+ Display.DEFAULT_DISPLAY,
+ DEVICE_ID_DEFAULT,
+ false,
+ "",
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnRemoteDevice = true,
)
}
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
)
+ @RequiresFlagsDisabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES)
@Test
fun onRemoteDevice_requestPermissionForHostDevice_shouldShowWarningDialog() {
+ assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
+ assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+
requestPermissionOnDevice(virtualDisplay.display.displayId, DEVICE_ID_DEFAULT)
val displayId = virtualDisplay.display.displayId
@@ -156,17 +212,51 @@ class DeviceAwarePermissionGrantTest {
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
- Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
+ Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES,
+ )
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ fun onRemoteDevice_requestPermissionForHostDevice_shouldGrantPermission() {
+ assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
+ assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+ // Create a virtual device with default policy, so that camera permission request will
+ // correspond to default device camera access.
+ virtualDevice =
+ virtualDeviceRule.createManagedVirtualDevice(
+ VirtualDeviceParams.Builder()
+ .setDevicePolicy(POLICY_TYPE_CAMERA, DEVICE_POLICY_DEFAULT)
+ .build()
+ )
+ testGrantPermissionForDevice(
+ virtualDisplay.display.displayId,
+ virtualDevice.deviceId,
+ true,
+ Settings.Global.getString(
+ defaultDeviceContext.contentResolver,
+ Settings.Global.DEVICE_NAME,
+ ),
+ expectPermissionGrantedOnDefaultDevice = true,
+ expectPermissionGrantedOnRemoteDevice = false,
+ )
+ }
+
+ @RequiresFlagsEnabled(
+ Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
+ Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
)
@Test
fun onRemoteDevice_requestPermissionForRemoteDevice_shouldGrantPermission() {
+ assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
+ assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
+
testGrantPermissionForDevice(
virtualDisplay.display.displayId,
virtualDevice.deviceId,
true,
deviceDisplayName,
expectPermissionGrantedOnDefaultDevice = false,
- expectPermissionGrantedOnRemoteDevice = true
+ expectPermissionGrantedOnRemoteDevice = true,
)
}
@@ -176,14 +266,9 @@ class DeviceAwarePermissionGrantTest {
showDeviceName: Boolean,
expectedDeviceNameInDialog: String,
expectPermissionGrantedOnDefaultDevice: Boolean,
- expectPermissionGrantedOnRemoteDevice: Boolean
+ expectPermissionGrantedOnRemoteDevice: Boolean,
) {
- // Assert no permission granted to either default device or virtual device at the beginning
- assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false)
- assertAppHasPermissionForDevice(virtualDevice.deviceId, false)
-
val future = requestPermissionOnDevice(displayId, targetDeviceId)
- virtualDeviceRule.waitAndAssertActivityResumed(getPermissionDialogComponentName())
if (showDeviceName) {
assertPermissionMessageContainsDeviceName(displayId, expectedDeviceNameInDialog)
@@ -199,7 +284,7 @@ class DeviceAwarePermissionGrantTest {
TestConstants.PERMISSION_RESULT_KEY_PERMISSIONS
)
)
- .isEqualTo(arrayOf(DEVICE_AWARE_PERMISSION))
+ .isEqualTo(arrayOf(PERMISSION))
assertThat(
grantPermissionResult.getIntArray(TestConstants.PERMISSION_RESULT_KEY_GRANT_RESULTS)
)
@@ -211,13 +296,13 @@ class DeviceAwarePermissionGrantTest {
assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, expectPermissionGrantedOnDefaultDevice)
assertAppHasPermissionForDevice(
virtualDevice.deviceId,
- expectPermissionGrantedOnRemoteDevice
+ expectPermissionGrantedOnRemoteDevice,
)
}
private fun requestPermissionOnDevice(
displayId: Int,
- targetDeviceId: Int
+ targetDeviceId: Int,
): CompletableFuture<Bundle> {
val future = CompletableFuture<Bundle>()
val callback = RemoteCallback { result: Bundle? -> future.complete(result) }
@@ -229,7 +314,9 @@ class DeviceAwarePermissionGrantTest {
.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_DEVICE_ID, targetDeviceId)
.putExtra(EXTRA_RESULT_RECEIVER, callback)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+
virtualDeviceRule.sendIntentToDisplay(intent, displayId)
+ virtualDeviceRule.waitAndAssertActivityResumed(getPermissionDialogComponentName())
return future
}
@@ -237,7 +324,7 @@ class DeviceAwarePermissionGrantTest {
private fun assertPermissionMessageContainsDeviceName(displayId: Int, deviceName: String) {
waitFindObject(By.displayId(displayId).res(PERMISSION_MESSAGE_ID))
val text = findTextForView(By.displayId(displayId).res(PERMISSION_MESSAGE_ID))
- Truth.assertThat(text).contains(deviceName)
+ assertThat(text).contains(deviceName)
}
private fun assertAppHasPermissionForDevice(deviceId: Int, expectPermissionGranted: Boolean) {
@@ -245,7 +332,7 @@ class DeviceAwarePermissionGrantTest {
defaultDeviceContext
.createDeviceContext(deviceId)
.packageManager
- .checkPermission(DEVICE_AWARE_PERMISSION, APP_PACKAGE_NAME)
+ .checkPermission(PERMISSION, APP_PACKAGE_NAME)
if (expectPermissionGranted) {
Assert.assertEquals(PackageManager.PERMISSION_GRANTED, checkPermissionResult)
@@ -269,7 +356,7 @@ class DeviceAwarePermissionGrantTest {
"com.android.permissioncontroller:id/permission_allow_foreground_only_button"
const val DEVICE_ID_DEFAULT = 0
const val PERSISTENT_DEVICE_ID_DEFAULT = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT
- const val DEVICE_AWARE_PERMISSION = Manifest.permission.CAMERA
+ const val PERMISSION = Manifest.permission.CAMERA
const val TIMEOUT = 5000L
private const val DISPLAY_HEIGHT = 1920
private const val DISPLAY_WIDTH = 1080
diff --git a/tests/cts/permissionmultiuser/Android.bp b/tests/cts/permissionmultiuser/Android.bp
index b86b02205..23aafb7e0 100644
--- a/tests/cts/permissionmultiuser/Android.bp
+++ b/tests/cts/permissionmultiuser/Android.bp
@@ -33,6 +33,7 @@ android_test {
"compatibility-device-util-axt",
"ctstestrunner-axt",
"Harrier",
+ "bedstead-multiuser",
"modules-utils-build_system",
"Nene",
],
diff --git a/tests/cts/permissionmultiuser/AndroidTest.xml b/tests/cts/permissionmultiuser/AndroidTest.xml
index 10fd4e7a5..f6834036b 100644
--- a/tests/cts/permissionmultiuser/AndroidTest.xml
+++ b/tests/cts/permissionmultiuser/AndroidTest.xml
@@ -63,8 +63,8 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.permissionmultiuser.cts" />
- <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
- <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+ <option name="exclude-annotation" value="com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.multiuser.annotations.RequireRunOnSecondaryUser" />
<option name="runtime-hint" value="5m" />
</test>
diff --git a/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt b/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt
index 2169f0f72..f3309bd3c 100644
--- a/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt
+++ b/tests/cts/permissionmultiuser/src/android/permissionmultiuser/cts/AppDataSharingUpdatesTest.kt
@@ -50,15 +50,16 @@ import android.support.test.uiautomator.UiObject2
import android.util.Log
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile
import com.android.bedstead.harrier.BedsteadJUnit4
import com.android.bedstead.harrier.DeviceState
import com.android.bedstead.permissions.annotations.EnsureHasPermission
import com.android.bedstead.harrier.annotations.EnsureSecureSettingSet
import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature
import com.android.bedstead.harrier.annotations.RequireNotWatch
-import com.android.bedstead.harrier.annotations.RequireRunOnAdditionalUser
-import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile
import com.android.bedstead.harrier.annotations.RequireSdkVersion
+import com.android.bedstead.multiuser.additionalUser
+import com.android.bedstead.multiuser.annotations.RequireRunOnAdditionalUser
import com.android.bedstead.permissions.CommonPermissions.INTERACT_ACROSS_USERS
import com.android.compatibility.common.util.ApiTest
import com.android.compatibility.common.util.DeviceConfigStateChangerRule
diff --git a/tests/cts/permissionpolicy/Android.bp b/tests/cts/permissionpolicy/Android.bp
index 4249f3c9d..07fde8bff 100644
--- a/tests/cts/permissionpolicy/Android.bp
+++ b/tests/cts/permissionpolicy/Android.bp
@@ -37,6 +37,7 @@ android_test {
"permission-test-util-lib",
"androidx.test.rules",
"flag-junit",
+ "android.app.flags-aconfig",
"android.permission.flags-aconfig-java-export",
],
srcs: [
diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
index d820ce10a..2547b9786 100644
--- a/tests/cts/permissionpolicy/res/raw/android_manifest.xml
+++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
@@ -154,6 +154,8 @@
<protected-broadcast android:name="android.app.backup.intent.INIT" />
<protected-broadcast android:name="android.bluetooth.intent.DISCOVERABLE_TIMEOUT" />
+ <protected-broadcast android:name="android.bluetooth.action.AUTO_ON_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.adapter.action.SCAN_MODE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.adapter.action.DISCOVERY_STARTED" />
@@ -177,6 +179,8 @@
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REPLY" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL" />
<protected-broadcast android:name="android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST" />
+ <protected-broadcast android:name="android.bluetooth.device.action.KEY_MISSING" />
+ <protected-broadcast android:name="android.bluetooth.device.action.ENCRYPTION_CHANGE" />
<protected-broadcast android:name="android.bluetooth.device.action.SDP_RECORD" />
<protected-broadcast android:name="android.bluetooth.device.action.BATTERY_LEVEL_CHANGED" />
<protected-broadcast android:name="android.bluetooth.device.action.REMOTE_ISSUE_OCCURRED" />
@@ -238,6 +242,8 @@
<protected-broadcast
android:name="android.bluetooth.avrcp-controller.profile.action.FOLDER_LIST" />
<protected-broadcast
+ android:name="android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING" />
+ <protected-broadcast
android:name="android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT" />
<protected-broadcast
android:name="android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED" />
@@ -264,6 +270,7 @@
<protected-broadcast
android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.action.HAP_CONNECTION_STATE_CHANGED" />
+ <protected-broadcast android:name="android.bluetooth.action.HAP_DEVICE_AVAILABLE" />
<protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED" />
<protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONF_CHANGED" />
@@ -297,6 +304,9 @@
<protected-broadcast android:name="android.hardware.display.action.WIFI_DISPLAY_STATUS_CHANGED" />
+ <protected-broadcast android:name="android.hardware.hdmi.action.OSD_MESSAGE" />
+ <protected-broadcast android:name="android.hardware.hdmi.action.ON_ACTIVE_SOURCE_RECOVERED_DISMISS_UI" />
+
<protected-broadcast android:name="android.hardware.usb.action.USB_STATE" />
<protected-broadcast android:name="android.hardware.usb.action.USB_PORT_CHANGED" />
<protected-broadcast android:name="android.hardware.usb.action.USB_PORT_COMPLIANCE_CHANGED" />
@@ -787,6 +797,7 @@
<protected-broadcast android:name="com.android.cellbroadcastreceiver.GET_LATEST_CB_AREA_INFO" />
<protected-broadcast android:name="com.android.internal.telephony.ACTION_CARRIER_CERTIFICATE_DOWNLOAD" />
<protected-broadcast android:name="com.android.internal.telephony.action.COUNTRY_OVERRIDE" />
+ <protected-broadcast android:name="com.android.internal.telephony.action.SILENCE_WIFI_CALLING_NOTIFICATION"/>
<protected-broadcast android:name="com.android.internal.telephony.OPEN_DEFAULT_SMS_APP" />
<protected-broadcast android:name="com.android.internal.telephony.ACTION_TEST_OVERRIDE_CARRIER_ID" />
<protected-broadcast android:name="android.telephony.action.SIM_CARD_STATE_CHANGED" />
@@ -837,6 +848,12 @@
<protected-broadcast android:name="android.intent.action.PROFILE_UNAVAILABLE" />
<protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" />
<protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" />
+ <protected-broadcast android:name="com.android.uwb.uwbcountrycode.GEOCODE_RETRY" />
+ <protected-broadcast android:name="android.telephony.satellite.action.SATELLITE_SUBSCRIBER_ID_LIST_CHANGED" />
+ <protected-broadcast android:name="android.service.ondeviceintelligence.MODEL_LOADED" />
+ <protected-broadcast android:name="android.service.ondeviceintelligence.MODEL_UNLOADED" />
+ <protected-broadcast android:name="android.telephony.satellite.action.SATELLITE_START_NON_EMERGENCY_SESSION" />
+
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
@@ -898,13 +915,26 @@
android:featureFlag="android.provider.user_keys" />
<!-- Allows an application to set default account for new contacts.
- <p> This permission is only granted to system applications fulfilling the Contacts app role.
+ <p>This permission is only granted to system applications fulfilling the Contacts app role.
<p>Protection level: internal|role
@SystemApi
@hide
-->
<permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS"
- android:protectionLevel="internal|role" />
+ android:protectionLevel="internal|role"
+ android:featureFlag="!android.provider.new_default_account_api_enabled"/>
+
+ <!-- Allows an application to set default account for new contacts.
+ <p>This permission is only granted to system applications fulfilling the Contacts app role
+ and the application with known signers.
+ <p>Protection level: internal|role|knownSigner
+ @SystemApi
+ @hide
+ -->
+ <permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS"
+ android:protectionLevel="internal|role|knownSigner"
+ android:knownCerts="@array/config_setContactsDefaultAccountKnownSigners"
+ android:featureFlag="android.provider.new_default_account_api_enabled"/>
<!-- ====================================================================== -->
<!-- Permissions for accessing user's calendar -->
@@ -1064,6 +1094,62 @@
<permission android:name="android.permission.SATELLITE_COMMUNICATION"
android:protectionLevel="role|signature|privileged" />
+ <!-- ================================== -->
+ <!-- Permissions associated with picture and sound profiles -->
+ <!-- ================================== -->
+ <eat-comment />
+
+ <!-- @FlaggedApi(android.media.tv.flags.Flags.FLAG_APPLY_PICTURE_PROFILES)
+ Allows an app to apply a {@link MediaQualityManager.PictureProfile} to a layer via
+ {@link MediaCodec.PARAMETER_KEY_PICTURE_PROFILE} and, additionally, system apps via
+ {@link SurfaceControl.Transaction#setPictureProfileHandle}.
+ -->
+ <permission android:name="android.permission.APPLY_PICTURE_PROFILE"
+ android:protectionLevel="normal"
+ android:featureFlag="android.media.tv.flags.apply_picture_profiles"/>
+
+ <!-- @hide
+ Allows MediaQualityService to observe any {@link MediaQualityManager.PictureProfile}
+ applied to any layer in the system by apps via
+ {@link MediaCodec.PARAMETER_KEY_PICTURE_PROFILE} and by system apps via
+ {@link SurfaceControl.Transaction#setPictureProfileHandle}.
+ -->
+ <permission android:name="android.permission.OBSERVE_PICTURE_PROFILES"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.media.tv.flags.apply_picture_profiles"/>
+
+ <!--
+ @SystemApi
+ @FlaggedApi("android.media.tv.flags.media_quality_fw")
+ Allows an application to access its picture profile from the media quality database.
+ <p> Protection level: signature|privileged|vendor privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
+ <!--
+ @SystemApi
+ @FlaggedApi("android.media.tv.flags.media_quality_fw")
+ Allows an application to access its sound profile from the media quality database.
+ <p> Protection level: signature|privileged|vendor privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
+ <!--
+ @FlaggedApi(android.media.tv.flags.Flags.FLAG_MEDIA_QUALITY_FW)
+ Allows an application to read the aggregated color zones on the screen for use cases like
+ TV ambient backlight usages.
+ <p> Protection level: normal
+ -->
+ <permission android:name="android.permission.READ_COLOR_ZONES"
+ android:protectionLevel="normal"
+ android:featureFlag="android.media.tv.flags.media_quality_fw"/>
+
<!-- ====================================================================== -->
<!-- Permissions for accessing external storage -->
<!-- ====================================================================== -->
@@ -1479,8 +1565,8 @@
android:description="@string/permdesc_readBasicPhoneState"
android:protectionLevel="normal" />
- <!-- Allows read access to the device's phone number(s). This is a subset of the capabilities
- granted by {@link #READ_PHONE_STATE} but is exposed to instant applications.
+ <!-- Allows read access to the device's phone number(s),
+ which is exposed to instant applications.
<p>Protection level: dangerous-->
<permission android:name="android.permission.READ_PHONE_NUMBERS"
android:permissionGroup="android.permission-group.UNDEFINED"
@@ -2070,6 +2156,21 @@
<permission android:name="android.permission.CONTROL_AUTOMOTIVE_GNSS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi @hide Allows an application to bind to a
+ android.service.PopulationDensityProviderService for the purpose of
+ querying population density. This prevents arbitrary clients connecting
+ to the service. The system server checks that the provider's intent
+ service explicitly sets this permission via the android:permission
+ attribute of the service.
+ This is only expected to be possessed by the system server outside of
+ tests.
+ @FlaggedApi(android.location.flags.Flags.FLAG_POPULATION_DENSITY_PROVIDER)
+ <p>Protection level: signature
+ -->
+ <permission android:name="android.permission.BIND_POPULATION_DENSITY_PROVIDER_SERVICE"
+ android:featureFlag="android.location.flags.population_density_provider"
+ android:protectionLevel="signature" />
+
<!-- ======================================= -->
<!-- Permissions for accessing networks -->
<!-- ======================================= -->
@@ -2396,7 +2497,7 @@
android:label="@string/permlab_nearby_wifi_devices"
android:protectionLevel="dangerous" />
- <!-- Required to be able to range to devices using any ranging technology.
+ <!-- Required to be able to range to devices using generic ranging module.
@FlaggedApi("android.permission.flags.ranging_permission_enabled")
<p>Protection level: dangerous -->
<permission android:name="android.permission.RANGING"
@@ -2404,7 +2505,7 @@
android:description="@string/permdesc_ranging"
android:label="@string/permlab_ranging"
android:protectionLevel="dangerous"
- android:featureFlag="android.permission.flags.ranging_permission_enabled" />
+ android:featureFlag="android.permission.flags.ranging_permission_enabled"/>
<!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
user from using them until they are unsuspended.
@@ -2574,6 +2675,22 @@
android:label="@string/permlab_getAccounts" />
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
+ <!-- @SystemApi Allows access to remove an account.
+ @FlaggedApi(android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.REMOVE_ACCOUNTS"
+ android:protectionLevel="signature|role"
+ android:featureFlag="android.app.admin.flags.split_create_managed_profile_enabled" />
+
+ <!-- @SystemApi Allows access to copy an account to another user.
+ @FlaggedApi(android.app.admin.flags.Flags.FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.COPY_ACCOUNTS"
+ android:protectionLevel="signature|role"
+ android:featureFlag="android.app.admin.flags.split_create_managed_profile_enabled" />
+
<!-- Allows applications to call into AccountAuthenticators.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.ACCOUNT_MANAGER"
@@ -2616,12 +2733,22 @@
<!-- @SystemApi Allows access to perform vendor effects in the vibrator.
<p>Protection level: signature
+ @FlaggedApi(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
@hide
-->
<permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS"
android:protectionLevel="signature|privileged"
android:featureFlag="android.os.vibrator.vendor_vibration_effects" />
+ <!-- @SystemApi Allows access to start a vendor vibration session.
+ <p>Protection level: signature
+ @FlaggedApi(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+ @hide
+ -->
+ <permission android:name="android.permission.START_VIBRATION_SESSIONS"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.os.vibrator.vendor_vibration_effects" />
+
<!-- @SystemApi Allows access to the vibrator state.
<p>Protection level: signature
@hide
@@ -3261,16 +3388,20 @@
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
android:protectionLevel="signature|appop" />
- <!-- Allows applications to access profiles with ACCESS_HIDDEN_PROFILES user property
- <p>Protection level: normal
- @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
+ <!-- Allows applications to access profiles with
+ {@code android.content.pm.UserProperties#PROFILE_API_VISIBILITY_HIDDEN} user property, e.g.
+ {@link android.os.UserManager#USER_TYPE_PROFILE_PRIVATE}.
+ <p>Protection level: normal
+ @FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
<permission android:name="android.permission.ACCESS_HIDDEN_PROFILES"
android:label="@string/permlab_accessHiddenProfile"
android:description="@string/permdesc_accessHiddenProfile"
android:protectionLevel="normal" />
- <!-- @SystemApi @hide Allows privileged applications to get details about hidden profile
- users.
+ <!-- @SystemApi @hide Allows privileged applications to get details about profiles with
+ {@code android.content.pm.UserProperties#PROFILE_API_VISIBILITY_HIDDEN} user property, e.g.
+ {@link android.os.UserManager#USER_TYPE_PROFILE_PRIVATE}. Removes extra requirements such
+ as having {@link android.app.role.RoleManager#ROLE_HOME} role for LauncherApps APIs.
@FlaggedApi("android.multiuser.enable_permission_to_access_hidden_profiles") -->
<permission
android:name="android.permission.ACCESS_HIDDEN_PROFILES_FULL"
@@ -3334,13 +3465,18 @@
<!-- Allows an application to manage device policy relating to time.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
- APIs protected by this permission on users different to the calling user.-->
+ APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
+ -->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_TIME"
android:protectionLevel="internal|role" />
<!-- Allows an application to set the grant state of runtime permissions on packages.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS"
android:protectionLevel="internal|role" />
@@ -3348,6 +3484,8 @@
<!-- Allows an application to manage the identity of the managing organization.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY"
android:protectionLevel="internal|role" />
@@ -3356,6 +3494,8 @@
active policy.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE"
android:protectionLevel="internal|role" />
@@ -3363,6 +3503,8 @@
<!-- Allows an application to manage backup service policy.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_BACKUP_SERVICE"
android:protectionLevel="internal|role" />
@@ -3370,6 +3512,8 @@
<!-- Allows an application to manage lock task policy.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK_TASK"
android:protectionLevel="internal|role" />
@@ -3377,6 +3521,8 @@
<!-- Allows an application to manage policy regarding modifying applications.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_APPS_CONTROL"
android:protectionLevel="internal|role" />
@@ -3384,6 +3530,8 @@
<!-- Allows an application to manage installing from unknown sources policy.
<p>MANAGE_SECURITY_CRITICAL_DEVICE_POLICY_ACROSS_USERS is required to call APIs protected
by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES"
android:protectionLevel="internal|role" />
@@ -3391,6 +3539,8 @@
<!-- Allows an application to manage application restrictions.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_RESTRICTIONS"
android:protectionLevel="internal|role" />
@@ -3398,6 +3548,8 @@
<!-- Allows an application to manage calling policy.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_CALLS"
android:protectionLevel="internal|role" />
@@ -3405,6 +3557,8 @@
<!-- Allows an application to manage debugging features policy.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES"
android:protectionLevel="internal|role" />
@@ -3412,6 +3566,8 @@
<!-- Allows an application to manage policy preventing users from modifying users.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS"
android:protectionLevel="internal|role" />
@@ -3419,6 +3575,8 @@
<!-- Allows an application to manage safe boot policy.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT"
android:protectionLevel="internal|role" />
@@ -3427,6 +3585,8 @@
enable and disable the microphone.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_MICROPHONE"
android:protectionLevel="internal|role" />
@@ -3435,6 +3595,8 @@
enable and disable the camera.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_CAMERA"
android:protectionLevel="internal|role" />
@@ -3442,6 +3604,8 @@
<!-- Allows an application to manage policy related to keyguard.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_KEYGUARD"
android:protectionLevel="internal|role" />
@@ -3449,6 +3613,8 @@
<!-- Allows an application to set policy related to account management.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT"
android:protectionLevel="internal|role" />
@@ -3456,6 +3622,8 @@
<!-- Allows an application to set policy related to hiding and suspending packages.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_PACKAGE_STATE"
android:protectionLevel="internal|role" />
@@ -3464,17 +3632,24 @@
challenge on current user.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD"
android:protectionLevel="internal|role" />
- <!-- Allows an application to set policy related to the status bar.-->
+ <!-- Allows an application to set policy related to the status bar.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
+ -->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_STATUS_BAR"
android:protectionLevel="internal|role" />
<!-- Allows an application to set policy related to bluetooth.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_BLUETOOTH"
android:protectionLevel="internal|role" />
@@ -3482,6 +3657,8 @@
<!-- Allows an application to set policy related to fun.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_FUN"
android:protectionLevel="internal|role" />
@@ -3489,6 +3666,8 @@
<!-- Allows an application to set policy related to airplane mode.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_AIRPLANE_MODE"
android:protectionLevel="internal|role" />
@@ -3496,6 +3675,8 @@
<!-- Allows an application to set policy related to mobile networks.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK"
android:protectionLevel="internal|role" />
@@ -3503,6 +3684,8 @@
<!-- Allows an application to set policy related to physical media.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA"
android:protectionLevel="internal|role" />
@@ -3510,6 +3693,8 @@
<!-- Allows an application to set policy related to sms.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SMS"
android:protectionLevel="internal|role" />
@@ -3517,6 +3702,8 @@
<!-- Allows an application to set policy related to usb file transfers.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER"
android:protectionLevel="internal|role" />
@@ -3524,6 +3711,8 @@
<!-- Allows an application to set policy related to lock credentials.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS"
android:protectionLevel="internal|role" />
@@ -3531,6 +3720,8 @@
<!-- Allows an application to set policy related to Wifi.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_WIFI"
android:protectionLevel="internal|role" />
@@ -3538,6 +3729,8 @@
<!-- Allows an application to set policy related to screen capture.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE"
android:protectionLevel="internal|role" />
@@ -3545,6 +3738,8 @@
<!-- Allows an application to set policy related to input methods.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_INPUT_METHODS"
android:protectionLevel="internal|role" />
@@ -3553,6 +3748,8 @@
private DNS.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS"
android:protectionLevel="internal|role" />
@@ -3560,6 +3757,8 @@
<!-- Allows an application to set policy related to the default sms application.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_DEFAULT_SMS"
android:protectionLevel="internal|role" />
@@ -3567,6 +3766,8 @@
<!-- Allows an application to set policy related to profiles.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_PROFILES"
android:protectionLevel="internal|role" />
@@ -3575,6 +3776,8 @@
cross-profile copy and paste).
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTION"
android:protectionLevel="internal|role" />
@@ -3582,6 +3785,8 @@
<!-- Allows an application to set policy related to VPNs.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_VPN"
android:protectionLevel="internal|role" />
@@ -3589,6 +3794,8 @@
<!-- Allows an application to set policy related to audio output.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIO_OUTPUT"
android:protectionLevel="internal|role" />
@@ -3596,6 +3803,8 @@
<!-- Allows an application to set policy related to the display.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_DISPLAY"
android:protectionLevel="internal|role" />
@@ -3603,6 +3812,8 @@
<!-- Allows an application to set policy related to location.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCATION"
android:protectionLevel="internal|role" />
@@ -3610,6 +3821,8 @@
<!-- Allows an application to set policy related to factory reset.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_FACTORY_RESET"
android:protectionLevel="internal|role" />
@@ -3617,6 +3830,8 @@
<!-- Allows an application to set policy related to the wallpaper.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_WALLPAPER"
android:protectionLevel="internal|role" />
@@ -3624,6 +3839,8 @@
<!-- Allows an application to set policy related to the usage of the contents of the screen.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SCREEN_CONTENT"
android:protectionLevel="internal|role" />
@@ -3631,6 +3848,8 @@
<!-- Allows an application to set policy related to system dialogs.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS"
android:protectionLevel="internal|role" />
@@ -3638,6 +3857,8 @@
<!-- Allows an application to set policy related to users running in the background.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND"
android:protectionLevel="internal|role" />
@@ -3645,6 +3866,8 @@
<!-- Allows an application to set policy related to printing.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_PRINTING"
android:protectionLevel="internal|role" />
@@ -3653,12 +3876,16 @@
nearby streaming).
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION"
android:protectionLevel="internal|role" />
<!-- Allows an application to set policy related to <a
href="https://www.threadgroup.org">Thread</a> network.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
@FlaggedApi("com.android.net.thread.platform.flags.thread_user_restriction_enabled")
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_THREAD_NETWORK"
@@ -3666,6 +3893,8 @@
<!-- Allows an application to set policy related to sending assist content to a
privileged app such as the Assistant app.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ASSIST_CONTENT"
android:protectionLevel="internal|role" />
@@ -3673,6 +3902,8 @@
<!-- Allows an application to set policy related to windows.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_WINDOWS"
android:protectionLevel="internal|role" />
@@ -3680,6 +3911,8 @@
<!-- Allows an application to set policy related to locale.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCALE"
android:protectionLevel="internal|role" />
@@ -3687,6 +3920,8 @@
<!-- Allows an application to set policy related to autofill.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUTOFILL"
android:protectionLevel="internal|role" />
@@ -3694,6 +3929,8 @@
<!-- Allows an application to set policy related to users.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_USERS"
android:protectionLevel="internal|role" />
@@ -3701,6 +3938,8 @@
<!-- Allows an application to set policy related to certificates.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_CERTIFICATES"
android:protectionLevel="internal|role" />
@@ -3708,6 +3947,8 @@
<!-- Allows an application to set policy related to override APNs.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_OVERRIDE_APN"
android:protectionLevel="internal|role" />
@@ -3715,6 +3956,8 @@
<!-- Allows an application to set policy related to security logging.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SECURITY_LOGGING"
android:protectionLevel="internal|role" />
@@ -3729,6 +3972,8 @@
<!-- Allows an application to set policy related to system updates.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES"
android:protectionLevel="internal|role" />
@@ -3736,6 +3981,8 @@
<!-- Allows an application query system updates.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES"
android:protectionLevel="internal|role" />
@@ -3743,6 +3990,8 @@
<!-- Allows an application to set policy related to private DNS.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_PRIVATE_DNS"
android:protectionLevel="internal|role" />
@@ -3750,6 +3999,8 @@
<!-- Allows an application to set policy related to settings.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SETTINGS"
android:protectionLevel="internal|role" />
@@ -3757,17 +4008,24 @@
<!-- Allows an application to set policy related to network logging.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_NETWORK_LOGGING"
android:protectionLevel="internal|role" />
- <!-- Allows an application to set policy related to usb data signalling.-->
+ <!-- Allows an application to set policy related to usb data signalling.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
+ -->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING"
android:protectionLevel="internal|role" />
<!-- Allows an application to set policy related to suspending personal apps.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SUSPEND_PERSONAL_APPS"
android:protectionLevel="internal|role" />
@@ -3775,13 +4033,17 @@
<!-- Allows an application to set policy related to keeping uninstalled packages.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is
required to call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_KEEP_UNINSTALLED_PACKAGES"
android:protectionLevel="internal|role" />
<!-- Allows an application to manage policy related to accessibility.
- <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
- APIs protected by this permission on users different to the calling user.
+ <p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to
+ call APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACCESSIBILITY"
android:protectionLevel="internal|role" />
@@ -3789,6 +4051,8 @@
<!-- Allows an application to manage policy related to common criteria mode.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE"
android:protectionLevel="internal|role" />
@@ -3796,6 +4060,8 @@
<!-- Allows an application to manage policy related to metered data.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_METERED_DATA"
android:protectionLevel="internal|role" />
@@ -3803,6 +4069,8 @@
<!-- Allows an application to set a network-independent global HTTP proxy.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_PROXY"
android:protectionLevel="internal|role" />
@@ -3810,6 +4078,8 @@
<!-- Allows an application to request bugreports with user consent.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_BUGREPORT"
android:protectionLevel="internal|role" />
@@ -3817,6 +4087,8 @@
<!-- Allows an application to manage policy related to application user data.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_USER_DATA"
android:protectionLevel="internal|role" />
@@ -3825,6 +4097,8 @@
permission.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_LOCK"
android:protectionLevel="internal|role" />
@@ -3840,6 +4114,8 @@
<!-- Allows an application to manage policy related to system apps.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_SYSTEM_APPS"
android:protectionLevel="internal|role" />
@@ -3847,16 +4123,23 @@
<!-- Allows an application to manage policy related to wiping data.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS} is required to call
APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_WIPE_DATA"
android:protectionLevel="internal|role" />
<!-- Allows an application to manage policy related to the Memory Tagging Extension (MTE).
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_MTE"
android:protectionLevel="internal|role" />
- <!-- Allows an application to manage policy related to device identifiers. -->
+ <!-- Allows an application to manage policy related to device identifiers.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
+ -->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_DEVICE_IDENTIFIERS"
android:protectionLevel="internal|role" />
@@ -3867,38 +4150,62 @@
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_CONTENT_PROTECTION"
android:protectionLevel="internal|role" />
+ <!-- Allows an application to manage policy related to AppFunctions.
+ <p>Protection level: internal|role
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER)
+ -->
+ <permission android:name="android.permission.MANAGE_DEVICE_POLICY_APP_FUNCTIONS"
+ android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
+ android:protectionLevel="internal|role" />
+
<!-- Allows an application to set policy related to subscriptions downloaded by an admin.
<p>{@link Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL} is required to call
- APIs protected by this permission on users different to the calling user.
+ APIs protected by this permission on users different to the calling user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
-->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS"
android:protectionLevel="internal|role" />
- <!-- Allows an application to manage policy related to block package uninstallation. -->
+ <!-- Allows an application to manage policy related to block package uninstallation.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
+ -->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL"
android:protectionLevel="internal|role" />
- <!-- Allows an application to manage policy related to camera toggle. -->
+ <!-- Allows an application to manage policy related to camera toggle.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
+ -->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_CAMERA_TOGGLE"
android:protectionLevel="internal|role" />
- <!-- Allows an application to manage policy related to microphone toggle. -->
+ <!-- Allows an application to manage policy related to microphone toggle.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
+ -->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE"
android:protectionLevel="internal|role" />
<!-- Allows an application to set device policies outside the current user
that are critical for securing data within the current user.
<p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
- permissions across all users on the device provided they are required for securing data
- within the current user.-->
+ permissions across all users on the device provided they are required for securing data
+ within the current user.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
+ -->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL"
android:protectionLevel="internal|role" />
<!-- Allows an application to set device policies outside the current user
that are required for securing device ownership without accessing user data.
<p>Holding this permission allows the use of other held MANAGE_DEVICE_POLICY_*
- permissions across all users on the device provided they do not grant access to user
- data. -->
+ permissions across all users on the device provided they do not grant access to user data.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
+ -->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS"
android:protectionLevel="internal|role" />
@@ -3906,7 +4213,10 @@
<p>Fuller form of {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_ACROSS_USERS}
that removes the restriction on accessing user data.
<p>Holding this permission allows the use of any other held MANAGE_DEVICE_POLICY_*
- permissions across all users on the device.-->
+ permissions across all users on the device.
+ <p>Protection level: internal|role
+ <p>Intended for use by the DEVICE_POLICY_MANAGEMENT role only.
+ -->
<permission android:name="android.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL"
android:protectionLevel="internal|role" />
@@ -3925,6 +4235,56 @@
android:protectionLevel="signature|installer" />
<uses-permission android:name="android.permission.MANAGE_ENHANCED_CONFIRMATION_STATES" />
+ <!-- Allows an application to modify the device's advanced protection mode status, and query
+ the list of enabled features
+ @FlaggedApi(android.security.Flags.FLAG_AAPM_API)
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.MANAGE_ADVANCED_PROTECTION_MODE"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.security.aapm_api"/>
+ <uses-permission android:name="android.permission.MANAGE_ADVANCED_PROTECTION_MODE"
+ android:featureFlag="android.security.aapm_api"/>
+
+ <!-- Allows an application to query the device's advanced protection mode status.
+ @FlaggedApi(android.security.Flags.FLAG_AAPM_API) -->
+ <permission android:name="android.permission.QUERY_ADVANCED_PROTECTION_MODE"
+ android:protectionLevel="normal"
+ android:featureFlag="android.security.aapm_api"/>
+ <uses-permission android:name="android.permission.QUERY_ADVANCED_PROTECTION_MODE"
+ android:featureFlag="android.security.aapm_api"/>
+
+ <!-- Allows an application to read the state of the IntrusionDetectionService
+ @FlaggedApi(android.security.Flags.FLAG_AFL_API)
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.READ_INTRUSION_DETECTION_STATE"
+ android:featureFlag="android.security.afl_api"
+ android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.READ_INTRUSION_DETECTION_STATE"
+ android:featureFlag="android.security.afl_api"/>
+
+ <!-- Allows an application to change the state of the IntrusionDetectionService
+ @FlaggedApi(android.security.Flags.FLAG_AFL_API)
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.MANAGE_INTRUSION_DETECTION_STATE"
+ android:featureFlag="android.security.afl_api"
+ android:protectionLevel="signature|privileged" />
+ <uses-permission android:name="android.permission.MANAGE_INTRUSION_DETECTION_STATE"
+ android:featureFlag="android.security.afl_api"/>
+
+ <!-- Must be required by any IntrusionDetectionEventTransportService to ensure that
+ only the system can bind to it.
+ @FlaggedApi(android.security.Flags.FLAG_AFL_API)
+ @SystemApi
+ @hide -->
+ <permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE"
+ android:featureFlag="android.security.afl_api"
+ android:protectionLevel="signature" />
+ <uses-permission android:name="android.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE"
+ android:featureFlag="android.security.afl_api"/>
+
<!-- @SystemApi @hide Allows an application to set a device owner on retail demo devices.-->
<permission android:name="android.permission.PROVISION_DEMO_DEVICE"
android:protectionLevel="signature|setup|knownSigner"
@@ -4176,11 +4536,11 @@
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES"
android:protectionLevel="normal" />
- <!-- Allows application to request to be associated with a virtual display capable of streaming
+ <!-- Allows application to request to be associated with a virtual device capable of streaming
Android applications
({@link android.companion.AssociationRequest#DEVICE_PROFILE_APP_STREAMING})
by {@link android.companion.CompanionDeviceManager}.
- <p>Not for use by third-party applications.
+ <p>Not for use by third-party applications.
-->
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING"
android:protectionLevel="signature|privileged" />
@@ -4188,16 +4548,26 @@
<!-- Allows application to request to stream content from an Android host to a nearby device
({@link android.companion.AssociationRequest#DEVICE_PROFILE_NEARBY_DEVICE_STREAMING})
by {@link android.companion.CompanionDeviceManager}.
- <p>Not for use by third-party applications.
+ <p>Not for use by third-party applications.
-->
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING"
android:protectionLevel="signature|privileged" />
+ <!-- Allows application to request to stream content from an Android host to a nearby device
+ ({@link android.companion.AssociationRequest#DEVICE_PROFILE_SENSOR_DEVICE_STREAMING})
+ by {@link android.companion.CompanionDeviceManager}.
+ <p>Not for use by third-party applications.
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ENABLE_LIMITED_VDM_ROLE)
+ -->
+ <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_SENSOR_DEVICE_STREAMING"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.companion.virtualdevice.flags.enable_limited_vdm_role" />
+
<!-- Allows application to request to be associated with a vehicle head unit capable of
automotive projection
({@link android.companion.AssociationRequest#DEVICE_PROFILE_AUTOMOTIVE_PROJECTION})
by {@link android.companion.CompanionDeviceManager}.
- <p>Not for use by third-party applications.
+ <p>Not for use by third-party applications.
-->
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"
android:protectionLevel="internal|role" />
@@ -4206,7 +4576,7 @@
and/or data with other devices, such as notifications, photos and media
({@link android.companion.AssociationRequest#DEVICE_PROFILE_COMPUTER})
by {@link android.companion.CompanionDeviceManager}.
- <p>Not for use by third-party applications.
+ <p>Not for use by third-party applications.
-->
<permission android:name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER"
android:protectionLevel="signature|privileged" />
@@ -4244,6 +4614,18 @@
android:description="@string/permdesc_hideOverlayWindows"
android:protectionLevel="normal" />
+ <!-- Allows an app to enter Picture-in-Picture mode when the user is not explicitly requesting
+ it. This includes using {@link PictureInPictureParams.Builder#setAutoEnterEnabled} as well
+ as lifecycle methods such as {@link Activity#onUserLeaveHint} and {@link Activity#onPause}
+ to enter PiP when the user leaves the app.
+ This permission should only be used for certain PiP
+ <a href="{@docRoot}training/tv/get-started/multitasking#usage-types">usage types</a>.
+ @FlaggedApi(android.app.Flags.FLAG_ENABLE_TV_IMPLICIT_ENTER_PIP_RESTRICTION)
+ -->
+ <permission android:name="android.permission.TV_IMPLICIT_ENTER_PIP"
+ android:protectionLevel="normal"
+ android:featureFlag="android.app.enable_tv_implicit_enter_pip_restriction" />
+
<!-- ================================== -->
<!-- Permissions affecting the system wallpaper -->
<!-- ================================== -->
@@ -4706,7 +5088,7 @@
<permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION"
android:protectionLevel="signature" />
- <!-- Allows the caller to bind with Remote Key Provisioning service.
+ <!-- Allows an application to use the RemoteKeyProvisioningService.
@hide -->
<permission android:name="android.permission.BIND_RKP_SERVICE"
android:protectionLevel="signature" />
@@ -4750,6 +5132,27 @@
<permission android:name="android.permission.PROVIDE_REMOTE_CREDENTIALS"
android:protectionLevel="signature|privileged|role" />
+ <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_SETTINGS_CATALYST)
+ Allows an application to access the Settings Preference services to read settings exposed
+ by the system Settings app and system apps that contribute settings surfaced by the
+ Settings app.
+ <p>This allows the calling application to read settings values through the host
+ application, agnostic of underlying storage. -->
+ <permission android:name="android.permission.READ_SYSTEM_PREFERENCES"
+ android:protectionLevel="signature|privileged|role"
+ android:featureFlag="com.android.settingslib.flags.settings_catalyst" />
+
+ <!-- @FlaggedApi(com.android.settingslib.flags.Flags.FLAG_WRITE_SYSTEM_PREFERENCE_PERMISSION_ENABLED)
+ Allows an application to access the Settings Preference services to write settings
+ values exposed by the system Settings app and system apps that contribute settings surfaced
+ in the Settings app.
+ <p>This allows the calling application to write settings values
+ through the host application, agnostic of underlying storage.
+ <p>Protection Level: signature|privileged|appop -->
+ <permission android:name="android.permission.WRITE_SYSTEM_PREFERENCES"
+ android:protectionLevel="signature|privileged|appop"
+ android:featureFlag="com.android.settingslib.flags.write_system_preference_permission_enabled" />
+
<!-- ========================================= -->
<!-- Permissions for special development tools -->
<!-- ========================================= -->
@@ -4827,6 +5230,182 @@
android:protectionLevel="signature|privileged" />
<!-- ==================================== -->
+ <!-- Permissions for XR perception data -->
+ <!-- ==================================== -->
+ <eat-comment />
+
+ <!-- Used for permissions that are associated with accessing XR
+ tracked information about the person using the device and the
+ environment around them.
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission-group android:name="android.permission-group.XR_TRACKING"
+ android:label="@string/permgrouplab_xr_tracking"
+ android:description="@string/permgroupdesc_xr_tracking"
+ android:priority="100"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get approximate eye gaze.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.EYE_TRACKING_COARSE"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_eye_tracking_coarse"
+ android:description="@string/permdesc_eye_tracking_coarse"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get face tracking data.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.FACE_TRACKING"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_face_tracking"
+ android:description="@string/permdesc_face_tracking"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get hand tracking data.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.HAND_TRACKING"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_hand_tracking"
+ android:description="@string/permdesc_hand_tracking"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get data derived by sensing the
+ user's environment.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.SCENE_UNDERSTANDING_COARSE"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_scene_understanding_coarse"
+ android:label="@string/permlab_scene_understanding_coarse"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Used for permissions that are associated with accessing
+ particularly sensitive XR tracking data.
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission-group android:name="android.permission-group.XR_TRACKING_SENSITIVE"
+ android:label="@string/permgrouplab_xr_tracking_sensitive"
+ android:description="@string/permgroupdesc_xr_tracking_sensitive"
+ android:priority="100"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get precise eye gaze data.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.EYE_TRACKING_FINE"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_eye_tracking_fine"
+ android:description="@string/permdesc_eye_tracking_fine"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get head tracking data. Unmanaged
+ activities (OpenXR activities with the manifest property
+ "android.window.PROPERTY_XR_ACTIVITY_START_MODE" set to
+ "XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED") do not require
+ this permission to get head tracking data.
+
+ {@see https://developer.android.com/develop/xr/get-started#property_activity_xr_start_mode_property}
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.HEAD_TRACKING"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:label="@string/permlab_head_tracking"
+ android:description="@string/permdesc_head_tracking"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to get highly precise data derived by sensing the
+ user's environment, such as a depth map.
+
+ <p>Protection level: dangerous
+
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES) -->
+ <permission android:name="android.permission.SCENE_UNDERSTANDING_FINE"
+ android:protectionLevel="dangerous"
+ android:permissionGroup="android.permission-group.UNDEFINED"
+ android:description="@string/permdesc_scene_understanding_fine"
+ android:label="@string/permlab_scene_understanding_fine"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to trigger Eye Calibration, which
+ calibrates for IPD (inter-pupillary distance) adjustment and
+ eye tracking.
+
+ <p>Protection level: signature|privileged
+
+ @SystemApi
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ @hide -->
+ <permission android:name="android.permission.EYE_CALIBRATION"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to trigger Face Tracking Calibration.
+
+ <p>Protection level: signature|privileged
+
+ @SystemApi
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ @hide -->
+ <permission android:name="android.permission.FACE_TRACKING_CALIBRATION"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to import an anchor created and
+ exported by another application.
+
+ <p>Protection level: signature|privileged
+
+ @SystemApi
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ @hide -->
+ <permission android:name="android.permission.IMPORT_XR_ANCHOR"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- Allows an application to access XR tracking data while in the
+ background. Without this permission, XR tracking data such as
+ head tracking, hand tracking, eye tracking, or face tracking
+ is only available to an activity it is in the
+ foreground. With this permission, such data is also available
+ to services and to activities that are in the background.
+
+ <p>This permission must be granted in addition to the
+ corresponding permission such as {@link #HEAD_TRACKING} or
+ {@link #FACE_TRACKING} for the data being accessed.
+
+ <p>Protection level: normal|appop
+
+ @SystemApi
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ @hide -->
+ <permission android:name="android.permission.XR_TRACKING_IN_BACKGROUND"
+ android:protectionLevel="normal|appop"
+ android:description="@string/permdesc_xr_tracking_in_background"
+ android:label="@string/permlab_xr_tracking_in_background"
+ android:featureFlag="android.xr.xr_manifest_entries" />
+
+ <!-- ==================================== -->
<!-- Private permissions -->
<!-- ==================================== -->
<eat-comment />
@@ -5003,10 +5582,9 @@
<permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME"
android:protectionLevel="signature" />
- <!-- @FlaggedApi("com.android.server.accessibility.motion_event_observing")
- @hide
- @TestApi
- Allows an accessibility service to observe motion events without consuming them. -->
+ <!-- @TestApi Allows an accessibility service to observe motion events
+ without consuming them.
+ @hide -->
<permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING"
android:protectionLevel="signature" />
@@ -5245,12 +5823,23 @@
<permission android:name="android.permission.BIND_VISUAL_QUERY_DETECTION_SERVICE"
android:protectionLevel="signature" />
- <!-- Allows an application to subscribe to keyguard locked (i.e., showing) state.
- <p>Protection level: signature|role
- <p>Intended for use by ROLE_ASSISTANT and signature apps only.
+ <!-- Allows an application to subscribe to device locked and keyguard locked (i.e., showing)
+ state.
+ <p>Protection level: signature|module|role
+ <p>Intended for use by ROLE_ASSISTANT, VDM, and signature apps only.
+ -->
+ <permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
+ android:protectionLevel="signature|module|role"
+ android:featureFlag="!android.security.subscribe_to_keyguard_locked_state_perm_priv_flag"/>
+
+ <!-- Allows an application to subscribe to device locked and keyguard locked (i.e., showing)
+ state.
+ <p>Protection level: signature|privileged|module|role
+ <p>Intended for use by ROLE_ASSISTANT, VDM, and signature / privileged apps only.
-->
<permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
- android:protectionLevel="signature|module|role"/>
+ android:protectionLevel="signature|privileged|module|role"
+ android:featureFlag="android.security.subscribe_to_keyguard_locked_state_perm_priv_flag"/>
<!-- Must be required by a {@link android.service.autofill.AutofillService},
to ensure that only the system can bind to it.
@@ -5424,6 +6013,17 @@
<permission android:name="android.permission.BIND_TV_INPUT"
android:protectionLevel="signature|privileged" />
+ <!-- This permission is required among systems services to always keep the
+ binding with TvInputManagerService.
+ <p>This should only be used by the OEM TvInputService.
+ @FlaggedApi("android.media.tv.flags.tif_unbind_inactive_tis")
+ <p>Protection level: signature|privileged|vendorPrivileged
+ @hide
+ -->
+ <permission android:name="android.permission.ALWAYS_BOUND_TV_INPUT"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:featureFlag="android.media.tv.flags.tif_unbind_inactive_tis"/>
+
<!-- Must be required by a {@link android.media.tv.interactive.TvInteractiveAppService}
to ensure that only the system can bind to it.
<p>Protection level: signature|privileged
@@ -5481,15 +6081,6 @@
<permission android:name="android.permission.TUNER_RESOURCE_ACCESS"
android:protectionLevel="signature|privileged|vendorPrivileged" />
- <!-- This permission is required among systems services to always keep the
- binding with TvInputManagerService.
- <p>Protection level: signature|privileged|vendorPrivileged
- <p>This should only be used by the OEM TvInputService.
- @hide -->
- <permission android:name="android.permission.ALWAYS_BOUND_TV_INPUT"
- android:protectionLevel="signature|privileged|vendorPrivileged"
- android:featureFlag="android.media.tv.flags.tif_unbind_inactive_tis"/>
-
<!-- @SystemApi This permission is required by Media Resource Manager Service when
system services create MediaCodecs on behalf of other processes and apps.
<p>Protection level: signature|privileged|vendorPrivileged
@@ -5917,7 +6508,7 @@
<!-- Allows an application to subscribe to notifications about the nearby devices' presence
status change base on the UUIDs.
<p>Not for use by third-party applications.</p>
- @FlaggedApi("android.companion.flags.device_presence")
+ @FlaggedApi("android.companion.device_presence")
-->
<permission android:name="android.permission.REQUEST_OBSERVE_DEVICE_UUID_PRESENCE"
android:protectionLevel="signature|privileged" />
@@ -5930,8 +6521,7 @@
android:protectionLevel="normal" />
<!-- Allows an application to send and receive messages via CDM transports.
- @hide
- -->
+ @hide -->
<permission android:name="android.permission.USE_COMPANION_TRANSPORTS"
android:protectionLevel="signature" />
@@ -6148,6 +6738,15 @@
<permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT"
android:protectionLevel="signature|privileged|role" />
+ <!-- @SystemApi Allows an application to bypass concurrency restrictions while
+ recording audio. For example, apps with this permission can continue to record
+ while a voice call is active.</p>
+ @FlaggedApi(android.media.audio.Flags.FLAG_CONCURRENT_AUDIO_RECORD_BYPASS_PERMISSION)
+ @hide -->
+ <permission android:name="android.permission.BYPASS_CONCURRENT_RECORD_AUDIO_RESTRICTION"
+ android:featureFlag="android.media.audio.concurrent_audio_record_bypass_permission"
+ android:protectionLevel="signature|privileged" />
+
<!-- @SystemApi Allows an application to capture audio for hotword detection.
<p>Not for use by third-party applications.</p>
@hide -->
@@ -6450,6 +7049,13 @@
<permission android:name="android.permission.BATTERY_STATS"
android:protectionLevel="signature|privileged|development" />
+ <!-- @SystemApi @hide Allows an application to collect high-precision PowerMonitor readings
+ <p>Protection level: signature|privileged|development
+ @FlaggedApi(android.permission.flags.Flags.FLAG_FINE_POWER_MONITOR_PERMISSION) -->
+ <permission android:name="android.permission.ACCESS_FINE_POWER_MONITORS"
+ android:protectionLevel="signature|privileged|development"
+ android:featureFlag="android.permission.flags.fine_power_monitor_permission" />
+
<!--Allows an application to manage statscompanion.
<p>Not for use by third-party applications.
@hide -->
@@ -6827,9 +7433,8 @@
android:protectionLevel="signature" />
<!-- Allows an application to set the advanced features on BiometricDialog (SystemUI), including
- logo, logo description.
+ logo, logo description, and content view with more options button.
<p>Not for use by third-party applications.
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt")
-->
<permission android:name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED"
android:protectionLevel="signature|privileged" />
@@ -7154,7 +7759,17 @@
@FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
@hide -->
<permission android:name="android.permission.READ_BLOCKED_NUMBERS"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature"
+ android:featureFlag="!android.permission.flags.grant_read_blocked_numbers_to_system_ui_intelligence" />
+
+ <!-- Allows the holder to read blocked numbers. See
+ {@link android.provider.BlockedNumberContract}.
+ @SystemApi
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
+ @hide -->
+ <permission android:name="android.permission.READ_BLOCKED_NUMBERS"
+ android:protectionLevel="signature|role"
+ android:featureFlag="android.permission.flags.grant_read_blocked_numbers_to_system_ui_intelligence" />
<!-- Allows the holder to write blocked numbers. See
{@link android.provider.BlockedNumberContract}.
@@ -7259,9 +7874,9 @@
<permission android:name="android.permission.ACCESS_SMARTSPACE"
android:protectionLevel="signature|privileged|development" />
- <!-- @SystemApi Allows an application to access the contextual search
- service as a client.
- @hide <p>Not for use by third-party applications.</p> -->
+ <!-- @SystemApi Allows an application to start a contextual search.
+ @FlaggedApi("android.app.contextualsearch.flags.enable_service")
+ @hide <p>Not for use by third-party applications.</p> -->
<permission android:name="android.permission.ACCESS_CONTEXTUAL_SEARCH"
android:protectionLevel="signature|privileged"
android:featureFlag="android.app.contextualsearch.flags.enable_service"/>
@@ -7478,7 +8093,8 @@
<permission android:name="android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE"
android:protectionLevel="signature" />
- <!-- Must be required by an {@link android.service.watchdog.ExplicitHealthCheckService} to
+ <!-- @FlaggedApi(android.crashrecovery.flags.Flags.FLAG_ENABLE_CRASHRECOVERY) @SystemApi
+ Must be required by an {@link android.service.watchdog.ExplicitHealthCheckService} to
ensure that only the system can bind to it.
@hide This is not a third-party API (intended for OEMs and system apps).
-->
@@ -7598,7 +8214,31 @@
<!-- @SystemApi Allows an application to access shared libraries.
@hide -->
<permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer"
+ android:featureFlag="!android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Allows an application to access shared libraries.
+ @hide -->
+ <permission android:name="android.permission.ACCESS_SHARED_LIBRARIES"
+ android:protectionLevel="signature|installer|role"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Permission held by the system to allow binding to the dependency installer role
+ holder.
+ @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide -->
+ <permission android:name="android.permission.BIND_DEPENDENCY_INSTALLER"
+ android:protectionLevel="signature"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
+
+ <!-- @SystemApi Allows an application to install shared libraries of types
+ {@link android.content.pm.SharedLibraryInfo#TYPE_STATIC} or
+ {@link android.content.pm.SharedLibraryInfo#TYPE_SDK_PACKAGE}.
+ @FlaggedApi(android.content.pm.Flags.FLAG_SDK_DEPENDENCY_INSTALLER)
+ @hide -->
+ <permission android:name="android.permission.INSTALL_DEPENDENCY_SHARED_LIBRARIES"
+ android:protectionLevel="signature|role"
+ android:featureFlag="android.content.pm.sdk_dependency_installer" />
<!-- Allows an app to log compat change usage.
@hide <p>Not for use by third-party applications.</p> -->
@@ -7673,6 +8313,13 @@
<permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY"
android:protectionLevel="signature|role"/>
+ <!-- Allows an application to create displays that mirror other displays' content.
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ENABLE_LIMITED_VDM_ROLE)
+ @hide @SystemApi -->
+ <permission android:name="android.permission.ADD_MIRROR_DISPLAY"
+ android:protectionLevel="internal|role"
+ android:featureFlag="android.companion.virtualdevice.flags.enable_limited_vdm_role" />
+
<!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
<permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
android:protectionLevel="signature|role" />
@@ -7847,32 +8494,14 @@
android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
android:protectionLevel="signature" />
- <!-- @SystemApi Allows a trusted application to perform actions on behalf of users inside of
- applications with privacy guarantees from the system.
- <p>This permission is currently only granted to system packages in the
- {@link android.app.role.SYSTEM_UI_INTELLIGENCE} role which complies with privacy
- requirements outlined in the Android CDD section "9.8.6 Content Capture".
- <p>Apps are not able to opt-out from caller having this permission.
- <p>Protection level: internal|role
- @hide
- @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") -->
- <permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED"
- android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
- android:protectionLevel="internal|role" />
-
- <!-- @SystemApi Allows an application to perform actions on behalf of users inside of
+ <!-- Allows an application to perform actions on behalf of users inside of
applications.
- <p>This permission is currently only granted to preinstalled / system apps having the
- {@link android.app.role.ASSISTANT} role.
- <p>Apps contributing app functions can opt to disallow callers with this permission,
- limiting to only callers with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}
- instead.
- <p>Protection level: internal|role
- @hide
- @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") -->
+ <p>This permission is currently only granted to privileged system apps.
+ <p>Protection level: internal|privileged
+ @FlaggedApi(android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) -->
<permission android:name="android.permission.EXECUTE_APP_FUNCTIONS"
android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
- android:protectionLevel="internal|role" />
+ android:protectionLevel="internal|privileged" />
<!-- Allows an application to display its suggestions using the autofill framework.
<p>For now, this permission is only granted to the Browser application.
@@ -7996,13 +8625,21 @@
<permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE"
android:protectionLevel="signature" />
- <!-- Allows low-level access to manage key gestures
+ <!-- Allows low-level access to manage key gestures.
<p>Not for use by third-party applications.
@hide -->
<permission android:name="android.permission.MANAGE_KEY_GESTURES"
android:protectionLevel="signature"
android:featureFlag="com.android.hardware.input.manage_key_gestures" />
+ <!-- Allows applications to register listeners for key activeness through
+ InputManagerService.
+ <p>Protection level: signature
+ @hide -->
+ <permission android:name="android.permission.LISTEN_FOR_KEY_ACTIVITY"
+ android:protectionLevel="signature"
+ android:featureFlag="com.android.hardware.input.key_event_activity_detection" />
+
<uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
<!-- Allows financed device kiosk apps to perform actions on the Device Lock service
@@ -8089,6 +8726,26 @@
android:protectionLevel="signature|knownSigner"
android:knownCerts="@array/config_healthConnectMigrationKnownSigners" />
+ <!-- @hide @SystemApi Allows permitted apps to back up Health Connect data and settings.
+ <p>Protection level: signature|knownSigner
+ @FlaggedApi("android.permission.flags.health_connect_backup_restore_permission_enabled")
+ -->
+ <permission
+ android:name="android.permission.BACKUP_HEALTH_CONNECT_DATA_AND_SETTINGS"
+ android:protectionLevel="signature|knownSigner"
+ android:knownCerts="@array/config_backupHealthConnectDataAndSettingsKnownSigners"
+ android:featureFlag="android.permission.flags.health_connect_backup_restore_permission_enabled" />
+
+ <!-- @hide @SystemApi Allows permitted apps to restore Health Connect data and settings.
+ <p>Protection level: signature|knownSigner
+ @FlaggedApi("android.permission.flags.health_connect_backup_restore_permission_enabled")
+ -->
+ <permission
+ android:name="android.permission.RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS"
+ android:protectionLevel="signature|knownSigner"
+ android:knownCerts="@array/config_restoreHealthConnectDataAndSettingsKnownSigners"
+ android:featureFlag="android.permission.flags.health_connect_backup_restore_permission_enabled" />
+
<!-- @SystemApi Allows an app to query apps in clone profile. The permission is
bidirectional in nature, i.e. cloned apps would be able to query apps in root user.
The permission is not meant for 3P apps as of now.
@@ -8203,16 +8860,8 @@
<permission android:name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW"
android:protectionLevel="signature|privileged" />
- <!-- Allows internal applications to restrict display modes
- <p>Protection level: signature
- @FlaggedApi("com.android.server.display.feature.flags.enable_restrict_display_modes")
- @hide
- -->
- <permission android:name="android.permission.RESTRICT_DISPLAY_MODES"
- android:protectionLevel="signature" />
-
<!-- @hide @SystemApi
- @FlaggedApi("com.android.server.notification.flags.redact_otp_notifications_from_untrusted_listeners")
+ @FlaggedApi("android.view.flags.sensitive_content_app_protection_api")
Allows apps with a NotificationListenerService to receive notifications with sensitive
information
<p>Apps with a NotificationListenerService without this permission will not be able
@@ -8252,6 +8901,14 @@
<permission android:name="android.permission.EMERGENCY_INSTALL_PACKAGES"
android:protectionLevel="signature|privileged"/>
+ <!-- Allows internal applications to restrict display modes
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.RESTRICT_DISPLAY_MODES"
+ android:protectionLevel="signature" />
+
<!-- Allows internal applications to override screen timeout temporarily
<p>Protection level: signature
<p>Not for use by third-party applications.
@@ -8270,6 +8927,49 @@
<permission android:name="android.permission.SETUP_FSVERITY"
android:protectionLevel="signature|privileged"/>
+ <!-- @SystemApi
+ @FlaggedApi(android.security.Flags.FLAG_SECURE_LOCKDOWN)
+ Allows an application to lock down the device into an enhanced security state.
+ <p>Not for use by third-party applications.
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.MANAGE_SECURE_LOCK_DEVICE"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.security.secure_lockdown" />
+
+ <!-- Allows app to enter trade-in-mode.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.ENTER_TRADE_IN_MODE"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="com.android.tradeinmode.flags.enable_trade_in_mode" />
+
+ <!-- @SystemApi
+ @FlaggedApi(com.android.art.flags.Flags.FLAG_EXECUTABLE_METHOD_FILE_OFFSETS)
+ Ability to read program metadata and attach dynamic instrumentation.
+ <p>Protection level: signature
+ @hide
+ -->
+ <permission android:name="android.permission.DYNAMIC_INSTRUMENTATION"
+ android:protectionLevel="signature"
+ android:featureFlag="com.android.art.flags.executable_method_file_offsets" />
+
+ <!--
+ @SystemApi
+ @FlaggedApi(android.content.pm.Flags.FLAG_UID_BASED_PROVIDER_LOOKUP)
+ Allows an app to resolve components (e.g ContentProviders) on behalf of
+ other UIDs
+ <p>Protection level: signature|privileged
+ @hide
+ -->
+ <permission
+ android:name="android.permission.RESOLVE_COMPONENT_FOR_UID"
+ android:protectionLevel="signature|privileged"
+ android:featureFlag="android.content.pm.uid_based_provider_lookup" />
+ <uses-permission android:name="android.permission.RESOLVE_COMPONENT_FOR_UID" />
+
<!--
@TestApi
Signature permission reserved for testing. This should never be used to
@@ -8281,6 +8981,29 @@
<permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
android:protectionLevel="signature"/>
+ <!-- @SystemApi
+ @FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing")
+ This permission is required when accessing information related to
+ singleUser-ed TIS session.
+ <p>This should only be used by OEM.
+ <p>Protection level: signature|privileged|vendorPrivileged
+ @hide
+ -->
+ <permission android:name="android.permission.SINGLE_USER_TIS_ACCESS"
+ android:protectionLevel="signature|privileged|vendorPrivileged"
+ android:featureFlag="android.media.tv.flags.kids_mode_tvdb_sharing"/>
+
+ <!-- @SystemApi
+ @FlaggedApi("android.permission.flags.text_classifier_choice_api_enabled")
+ This permission is required to access the specific text classifier from the
+ TextClassificationManager.
+ <p>Protection level: signature|role|privileged
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"
+ android:protectionLevel="signature|role|privileged"
+ android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/>
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
@@ -8334,7 +9057,7 @@
</activity>
<activity android:name="com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity"
android:exported="false"
- android:theme="@style/Theme.DeviceDefault.Resolver"
+ android:theme="@style/AccessibilityButtonChooserDialog"
android:finishOnCloseSystemDialogs="true"
android:excludeFromRecents="true"
android:documentLaunchMode="never"
@@ -8438,7 +9161,7 @@
<activity android:name="android.accounts.GrantCredentialsPermissionActivity"
android:excludeFromRecents="true"
android:exported="true"
- android:theme="@style/Theme.DeviceDefault.Light.DialogWhenLarge"
+ android:theme="@style/GrantCredentialsPermissionActivity"
android:process=":ui"
android:visibleToInstantApps="true">
</activity>
@@ -8514,9 +9237,11 @@
android:process=":ui">
</activity>
+ <!-- BlockedAppStreamingActivity is launched as the system user. -->
<activity android:name="com.android.internal.app.BlockedAppStreamingActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true"
+ android:showForAllUsers="true"
android:process=":ui">
</activity>
@@ -8614,15 +9339,6 @@
</intent-filter>
</receiver>
- <receiver android:name="com.android.server.updates.CertificateTransparencyLogInstallReceiver"
- android:exported="true"
- android:permission="android.permission.UPDATE_CONFIG">
- <intent-filter>
- <action android:name="android.intent.action.UPDATE_CT_LOGS" />
- <data android:scheme="content" android:host="*" android:mimeType="*/*" />
- </intent-filter>
- </receiver>
-
<receiver android:name="com.android.server.updates.LangIdInstallReceiver"
android:exported="true"
android:permission="android.permission.UPDATE_CONFIG">
@@ -8717,6 +9433,11 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.memory.ZramMaintenance"
+ android:exported="false"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service android:name="com.android.server.ZramWriteback"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" >
@@ -8873,6 +9594,7 @@
<service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncInCallService"
android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:enabled="@bool/config_enableContextSyncInCall"
android:exported="true">
<meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
android:value="true" />
@@ -8881,6 +9603,17 @@
</intent-filter>
</service>
+ <service android:name="com.android.ecm.EnhancedConfirmationCallTrackerService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:featureFlag="android.permission.flags.enhanced_confirmation_in_call_apis_enabled"
+ android:exported="true">
+ <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+ android:value="true" />
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
+
<service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
android:exported="true">
diff --git a/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml b/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml
index 783cd7f6b..2b40d3ed7 100644
--- a/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml
+++ b/tests/cts/permissionpolicy/res/raw/automotive_android_manifest.xml
@@ -80,6 +80,12 @@
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_control_car_seats"
android:description="@string/car_permission_desc_control_car_seats"/>
+ <permission android:name="android.car.permission.READ_CAR_SEATS"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_read_car_seats"
+ android:description="@string/car_permission_desc_read_car_seats"
+ android:featureFlag="android.car.feature.vehicle_property_25q2_3p_permissions"/>
<permission android:name="android.car.permission.CONTROL_CAR_AIRBAGS"
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_control_car_airbags"
@@ -88,14 +94,55 @@
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_mileage"
android:description="@string/car_permission_desc_mileage"/>
+ <permission android:name="android.car.permission.CAR_MILEAGE_3P"
+ android:permissionGroup="android.permission-group.LOCATION"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_mileage_3p"
+ android:description="@string/car_permission_desc_mileage_3p"
+ android:featureFlag="android.car.feature.android_b_vehicle_properties" />
+ <permission android:name="android.car.permission.READ_CAR_HORN"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_read_car_horn"
+ android:description="@string/car_permission_desc_read_car_horn"
+ android:featureFlag="android.car.feature.android_b_vehicle_properties" />
+ <permission android:name="android.car.permission.CONTROL_CAR_HORN"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_control_car_horn"
+ android:description="@string/car_permission_desc_control_car_horn"
+ android:featureFlag="android.car.feature.android_b_vehicle_properties" />
+ <permission android:name="android.car.permission.READ_CAR_PEDALS"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_read_car_pedals"
+ android:description="@string/car_permission_desc_read_car_pedals"
+ android:featureFlag="android.car.feature.android_b_vehicle_properties" />
+ <permission android:name="android.car.permission.READ_BRAKE_INFO"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_read_brake_info"
+ android:description="@string/car_permission_desc_read_brake_info"
+ android:featureFlag="android.car.feature.android_b_vehicle_properties" />
<permission android:name="android.car.permission.CAR_TIRES"
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_car_tires"
android:description="@string/car_permission_desc_car_tires"/>
+ <permission android:name="android.car.permission.CAR_TIRES_3P"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_car_tires_3p"
+ android:description="@string/car_permission_desc_car_tires_3p"
+ android:featureFlag="android.car.feature.vehicle_property_25q2_3p_permissions"/>
<permission android:name="android.car.permission.READ_CAR_STEERING"
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_car_steering"
android:description="@string/car_permission_desc_car_steering"/>
+ <permission android:name="android.car.permission.READ_CAR_STEERING_3P"
+ android:permissionGroup="android.permission-group.LOCATION"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_read_car_steering_3p"
+ android:description="@string/car_permission_desc_read_car_steering_3p"
+ android:featureFlag="android.car.feature.vehicle_property_25q2_3p_permissions"/>
<permission android:name="android.car.permission.READ_CAR_DISPLAY_UNITS"
android:protectionLevel="normal"
android:label="@string/car_permission_label_read_car_display_units"
@@ -121,6 +168,12 @@
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_car_engine_detailed"
android:description="@string/car_permission_desc_car_engine_detailed"/>
+ <permission android:name="android.car.permission.CAR_ENGINE_DETAILED_3P"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_car_engine_detailed_3p"
+ android:description="@string/car_permission_desc_car_engine_detailed_3p"
+ android:featureFlag="android.car.feature.vehicle_property_25q2_3p_permissions"/>
<permission android:name="android.car.permission.CAR_DYNAMICS_STATE"
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_vehicle_dynamics_state"
@@ -186,6 +239,12 @@
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_control_car_exterior_lights"
android:description="@string/car_permission_desc_control_car_exterior_lights"/>
+ <permission android:name="android.car.permission.READ_CAR_EXTERIOR_LIGHTS"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_read_car_exterior_lights"
+ android:description="@string/car_permission_desc_car_read_exterior_lights"
+ android:featureFlag="android.car.feature.android_b_vehicle_properties" />
<permission android:name="android.car.permission.READ_CAR_INTERIOR_LIGHTS"
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_car_interior_lights"
@@ -270,6 +329,12 @@
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_driving_state"
android:description="@string/car_permission_desc_driving_state"/>
+ <permission android:name="android.car.permission.CAR_DRIVING_STATE_3P"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_driving_state_3p"
+ android:description="@string/car_permission_desc_driving_state_3p"
+ android:featureFlag="android.car.feature.vehicle_property_25q2_3p_permissions"/>
<permission android:name="android.car.permission.USE_CAR_TELEMETRY_SERVICE"
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_use_telemetry_service"
@@ -602,6 +667,12 @@
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_read_windshield_wipers"
android:description="@string/car_permission_desc_read_windshield_wipers"/>
+ <permission android:name="android.car.permission.READ_WINDSHIELD_WIPERS_3P"
+ android:permissionGroup="android.car.permission-group.CAR_MONITORING"
+ android:protectionLevel="dangerous"
+ android:label="@string/car_permission_label_read_windshield_wipers_3p"
+ android:description="@string/car_permission_desc_read_windshield_wipers_3p"
+ android:featureFlag="android.car.feature.vehicle_property_25q2_3p_permissions"/>
<permission android:name="android.car.permission.CONTROL_WINDSHIELD_WIPERS"
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_control_windshield_wipers"
@@ -620,4 +691,16 @@
android:protectionLevel="signature|privileged"
android:label="@string/car_permission_label_bind_app_card_provider"
android:description="@string/car_permission_desc_bind_app_card_provider" />
+ <permission
+ android:name="android.car.permission.RECORD_VEHICLE_PROPERTIES"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_record_vehicle_properties"
+ android:description="@string/car_permission_desc_record_vehicle_properties"
+ android:featureFlag="android.car.feature.car_property_simulation" />
+ <permission
+ android:name="android.car.permission.INJECT_VEHICLE_PROPERTIES"
+ android:protectionLevel="signature"
+ android:label="@string/car_permission_label_inject_vehicle_properties"
+ android:description="@string/car_permission_desc_inject_vehicle_properties"
+ android:featureFlag="android.car.feature.car_property_simulation" />
</manifest>
diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt
index 70832b6ba..2ce48af44 100644
--- a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt
+++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/RuntimePermissionProperties.kt
@@ -59,6 +59,7 @@ import android.app.AppOpsManager.permissionToOp
import android.content.pm.PackageManager.GET_PERMISSIONS
import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS
import android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP
+import android.health.connect.HealthPermissions
import android.os.Build
import android.permission.flags.Flags
import android.permission.PermissionManager
@@ -195,6 +196,18 @@ class RuntimePermissionProperties {
expectedPerms.add(RANGING)
}
- assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
+ // Separately check health permissions.
+ if (Flags.replaceBodySensorPermissionEnabled()) {
+ assertThat(expectedPerms).contains(HealthPermissions.READ_HEART_RATE);
+ assertThat(expectedPerms).contains(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
+
+ // Remove these from the expected list once we've confirmed their
+ // present. These are not permissions owned by "android" so won't be
+ // in the list of platform runtime permissions.
+ expectedPerms.remove(HealthPermissions.READ_HEART_RATE);
+ expectedPerms.remove(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND);
+ }
+
+ assertThat(platformRuntimePerms.map { it.name }).containsExactlyElementsIn(expectedPerms)
}
}
diff --git a/tests/cts/permissionui/AndroidManifest.xml b/tests/cts/permissionui/AndroidManifest.xml
index 3b80b8d8b..b5c9e2ad0 100644
--- a/tests/cts/permissionui/AndroidManifest.xml
+++ b/tests/cts/permissionui/AndroidManifest.xml
@@ -25,6 +25,7 @@
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+ <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<application>
@@ -78,6 +79,23 @@
<meta-data android:name="android.accessibilityservice"
android:resource="@xml/test_accessibilityservice"/>
</service>
+ <service android:name=".VoipHelperTestConnectionService"
+ android:exported="true"
+ android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
+ <intent-filter>
+ <action android:name="android.telecom.ConnectionService" />
+ </intent-filter>
+ </service>
+
+ <service android:name=".EcmInCallTestInCallService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:exported="true">
+ <meta-data android:name="android.telecom.INCLUDE_SELF_MANAGED_CALLS"
+ android:value="true" />
+ <intent-filter>
+ <action android:name="android.telecom.InCallService"/>
+ </intent-filter>
+ </service>
</application>
diff --git a/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml
index 5949d08f2..6b9b9f41f 100644
--- a/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml
+++ b/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml
@@ -24,6 +24,8 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <uses-permission android:name="android.permission.BODY_SENSORS" />
+ <uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" />
<application>
<activity android:name=".RequestPermissionsActivity" android:exported="true" />
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/AppDataSharingUpdatesTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/AppDataSharingUpdatesTest.kt
index 0db639d49..421e93e2d 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/AppDataSharingUpdatesTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/AppDataSharingUpdatesTest.kt
@@ -311,6 +311,7 @@ class AppDataSharingUpdatesTest : BaseUsePermissionTest() {
}
@Test
+ @Ignore("b/381298073")
fun clickUpdate_opensAppLocationPermissionPage() {
installAndWaitTillPackageAdded(
APP_APK_NAME_31,
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt
index f0c12171c..05c824072 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt
@@ -18,7 +18,9 @@ package android.permissionui.cts
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission_group.SMS
+import android.app.AppOpsManager
import android.os.Build
+import android.os.Process
import android.permission.flags.Flags
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
@@ -31,8 +33,11 @@ import androidx.test.filters.SdkSuppress
import androidx.test.uiautomator.By
import androidx.test.uiautomator.Until
import com.android.compatibility.common.util.DeviceConfigStateChangerRule
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.modules.utils.build.SdkLevel
import com.google.common.truth.Truth
+import org.junit.Assert.assertEquals
import org.junit.Assume
import org.junit.Before
import org.junit.Rule
@@ -296,6 +301,9 @@ class AppPermissionTest : BaseUsePermissionTest() {
APP_APK_NAME_LATEST
)
+ // TODO: b/388960315 - Remove wait after addressing race condition
+ waitForModeDefault(APP_PACKAGE_NAME)
+
navigateToIndividualPermissionSetting(SMS)
assertAllowButtonIsDisabledAndRestrictedSettingDialogPoppedUp()
@@ -311,6 +319,8 @@ class AppPermissionTest : BaseUsePermissionTest() {
@Test
fun installFromLocalFile_disabledAllowRadioButtonAndIfClickedAndRestrictedSettingDialog_SMSPermGroup() {
installPackageWithInstallSourceAndMetadataFromLocalFile(APP_APK_NAME_LATEST)
+ // TODO: b/388960315 - Remove wait after addressing race condition
+ waitForModeDefault(APP_PACKAGE_NAME)
navigateToIndividualPermissionSetting(SMS)
@@ -341,6 +351,28 @@ class AppPermissionTest : BaseUsePermissionTest() {
)
}
+ private fun waitForModeDefault(packageName: String) {
+ val appOpsManager = context.getSystemService(AppOpsManager::class.java)!!
+ eventually {
+ val uid = context.packageManager.getApplicationInfoAsUser(
+ packageName,
+ /* flags */ 0,
+ Process.myUserHandle()
+ ).uid
+ runWithShellPermissionIdentity {
+ assertEquals(
+ "Timed out waiting for package mode to change to MODE_DEFAULT",
+ appOpsManager.checkOpNoThrow(
+ AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS,
+ uid,
+ packageName,
+ ),
+ AppOpsManager.MODE_DEFAULT,
+ )
+ }
+ }
+ }
+
companion object {
private const val PERMISSION_RATIONALE_ENABLED = "permission_rationale_enabled"
private val ENHANCED_CONFIRMATION_DIALOG_SELECTOR = By.res(
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt
index d8eb153bf..69528f83d 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt
@@ -37,6 +37,7 @@ import android.content.pm.PackageManager
import android.content.res.Resources
import android.os.PersistableBundle
import android.os.SystemClock
+import android.permission.cts.TestUtils
import android.platform.test.rule.ScreenRecordRule
import android.provider.DeviceConfig
import android.provider.Settings
@@ -72,6 +73,7 @@ import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
+import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
@@ -121,7 +123,7 @@ abstract class BasePermissionTest {
/* PackageManager.FEATURE_CAR_SPLITSCREEN_MULTITASKING */
"android.software.car.splitscreen_multitasking")
@JvmStatic
- private val isAutomotiveVisibleBackgroundUser = isAutomotive &&
+ protected val isAutomotiveVisibleBackgroundUser = isAutomotive &&
UserHelper(context).isVisibleBackgroundUser()
// TODO(b/382327037):find a way to avoid specifying the display ID for each UiSelector.
@@ -157,6 +159,7 @@ abstract class BasePermissionTest {
@Before
fun setUp() {
+ assumeTrue(TestUtils.isCddCompliantScreenSize())
runWithShellPermissionIdentity {
screenTimeoutBeforeTest =
Settings.System.getLong(context.contentResolver, Settings.System.SCREEN_OFF_TIMEOUT)
@@ -191,6 +194,9 @@ abstract class BasePermissionTest {
@After
fun tearDown() {
+ if (!TestUtils.isCddCompliantScreenSize()) {
+ return;
+ }
runWithShellPermissionIdentity {
Settings.System.putLong(
context.contentResolver,
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
index 68bd91546..f52e32344 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
@@ -38,6 +38,7 @@ import android.provider.DeviceConfig
import android.provider.Settings
import android.text.Spanned
import android.text.style.ClickableSpan
+import android.util.Log
import android.view.View
import android.view.accessibility.AccessibilityNodeInfo
import androidx.test.uiautomator.By
@@ -51,6 +52,7 @@ import com.android.compatibility.common.util.SystemUtil
import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.UiDumpUtils
import com.android.modules.utils.build.SdkLevel
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit
@@ -64,6 +66,7 @@ import org.junit.Before
abstract class BaseUsePermissionTest : BasePermissionTest() {
companion object {
+ const val LOG_TAG = "BaseUsePermissionTest"
const val APP_APK_NAME_31 = "CtsUsePermissionApp31.apk"
const val APP_APK_NAME_31_WITH_ASL = "CtsUsePermissionApp31WithAsl.apk"
const val APP_APK_NAME_LATEST = "CtsUsePermissionAppLatest.apk"
@@ -148,6 +151,9 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
const val APP_PERMISSION_RATIONALE_TITLE_TEXT = "app_location_permission_rationale_title"
const val APP_PERMISSION_RATIONALE_SUBTITLE_TEXT =
"app_location_permission_rationale_subtitle"
+ const val HEALTH_PERMISSION_SELECT_HEART_RATE_PLAIN_TEXT = "Heart rate"
+ const val HEALTH_PERMISSION_ALLOW_ALL_PLAIN_TEXT = "Allow all"
+ const val HEALTH_PERMISSION_ALLOW_ALWAYS_PLAIN_TEXT = "Allow all the time"
const val GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW =
"com.android.permissioncontroller:id/permission_rationale_container"
const val PERMISSION_RATIONALE_ACTIVITY_TITLE_VIEW =
@@ -315,7 +321,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
reinstall: Boolean,
grantRuntimePermissions: Boolean,
expectSuccess: Boolean,
- installSource: String?
+ installSource: String?,
) {
installPackage(
apkPath,
@@ -333,7 +339,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
grantRuntimePermissions: Boolean = false,
expectSuccess: Boolean = true,
installSource: String? = null,
- skipClearLowSdkDialog: Boolean = false
+ skipClearLowSdkDialog: Boolean = false,
) {
super.installPackage(
apkPath,
@@ -463,7 +469,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
- apkName: String
+ apkName: String,
) {
installPackageViaSession(
apkName,
@@ -506,7 +512,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceAndMetadataWithoutTopLevelVersion(
- apkName: String
+ apkName: String,
) {
installPackageViaSession(
apkName,
@@ -515,7 +521,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceAndMetadataWithInvalidTopLevelVersion(
- apkName: String
+ apkName: String,
) {
installPackageViaSession(
apkName,
@@ -524,7 +530,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceAndMetadataWithoutSafetyLabelVersion(
- apkName: String
+ apkName: String,
) {
installPackageViaSession(
apkName,
@@ -533,7 +539,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceAndMetadataWithInvalidSafetyLabelVersion(
- apkName: String
+ apkName: String,
) {
installPackageViaSession(
apkName,
@@ -552,7 +558,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun assertPermissionRationaleActivityDataSharingSourceSectionVisible(
- expected: Boolean
+ expected: Boolean,
) {
findView(By.res(DATA_SHARING_SOURCE_TITLE_ID).displayId(displayId), expected = expected)
findView(By.res(DATA_SHARING_SOURCE_MESSAGE_ID).displayId(displayId), expected = expected)
@@ -577,7 +583,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected fun assertPermissionRationaleDialogIsVisible(
expected: Boolean,
- showSettingsSection: Boolean = true
+ showSettingsSection: Boolean = true,
) {
assertPermissionRationaleActivityTitleIsVisible(expected)
assertPermissionRationaleActivityDataSharingSourceSectionVisible(expected)
@@ -623,7 +629,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected inline fun startAppActivityAndAssertResultCode(
expectedResultCode: Int,
- block: () -> Unit
+ block: () -> Unit,
) {
val future =
startActivityForFuture(
@@ -641,7 +647,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected inline fun requestAppPermissionsForNoResult(
vararg permissions: String?,
- crossinline block: () -> Unit
+ crossinline block: () -> Unit,
) {
// Request the permissions
doAndWaitForWindowTransition {
@@ -665,7 +671,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
vararg permissions: String?,
askTwice: Boolean = false,
waitForWindowTransition: Boolean = !isWatch,
- crossinline block: () -> Unit
+ crossinline block: () -> Unit,
): Instrumentation.ActivityResult {
// Request the permissions
lateinit var future: CompletableFuture<Instrumentation.ActivityResult>
@@ -700,7 +706,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
permissionAndExpectedGrantResults: Array<out Pair<String?, Boolean>>,
askTwice: Boolean = false,
waitForWindowTransition: Boolean = !isWatch,
- crossinline block: () -> Unit
+ crossinline block: () -> Unit,
) {
var shouldWaitForWindowTransition = waitForWindowTransition
// Do not wait for windowTransition after action is performed on auto, when permissions
@@ -766,7 +772,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
vararg permissionAndExpectedGrantResults: Pair<String?, Boolean>,
askTwice: Boolean = false,
waitForWindowTransition: Boolean = !isWatch,
- crossinline block: () -> Unit
+ crossinline block: () -> Unit,
) {
requestAppPermissionsAndAssertResult(
permissionAndExpectedGrantResults.map { it.first }.toTypedArray(),
@@ -788,6 +794,9 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
)
if (timeoutOccurred) {
+ val uiDump = StringBuilder()
+ UiDumpUtils.dumpNodes(uiDump)
+ Log.w(LOG_TAG, "Timed out waiting for window transition, UI dump: $uiDump")
throw RuntimeException("Timed out waiting for window transition.")
}
}
@@ -801,8 +810,11 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
}
- protected fun clickPermissionRequestAllowButton(timeoutMillis: Long = 20000) {
- if (isAutomotive || isWatch) {
+ protected fun clickPermissionRequestAllowButton(
+ timeoutMillis: Long = 20000,
+ isHealthPermission: Boolean = false,
+ ) {
+ if (isAutomotive || isWatch || isHealthPermission) {
click(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)).displayId(displayId),
timeoutMillis)
} else {
@@ -973,6 +985,37 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
assertTrue("Could not click on the settings link correctly", clickedOnLink)
}
+ protected fun clickAllowReadHeartRate() {
+ eventually {
+ scrollToBottom()
+
+ // Phone UI has allow all, toggle, and allow button. Watch UI only has allow button.
+ if (!isWatch) {
+ // Check "Allow all" button is visible.
+ val allowAllNode =
+ uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByText(
+ HEALTH_PERMISSION_ALLOW_ALL_PLAIN_TEXT
+ )[0]
+ assertTrue(allowAllNode.isVisibleToUser)
+
+ // Select "Heart rate" toggle and click "Allow" button.
+ click(By.text(HEALTH_PERMISSION_SELECT_HEART_RATE_PLAIN_TEXT).displayId(displayId))
+ }
+
+ clickPermissionRequestAllowButton(isHealthPermission = true)
+ }
+ }
+
+ protected fun clickAlwaysAllowReadHealthDataInBackground() {
+ eventually {
+ if (isWatch) {
+ click(By.text(HEALTH_PERMISSION_ALLOW_ALWAYS_PLAIN_TEXT).displayId(displayId))
+ } else {
+ clickPermissionRequestAllowButton(isHealthPermission = true)
+ }
+ }
+ }
+
protected fun clickPermissionRequestDenyAndDontAskAgainButton() {
if (isAutomotive) {
scrollToBottom()
@@ -1011,7 +1054,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected fun clickPermissionRationaleContentInAppPermission() {
clickAndWaitForWindowTransition(
By.text(getPermissionControllerString(APP_PERMISSION_RATIONALE_SUBTITLE_TEXT))
- .displayId(displayId))
+ .displayId(displayId)
+ )
}
protected fun clickPermissionRationaleViewInGrantDialog() {
@@ -1031,7 +1075,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected fun revokeAppPermissionsByUi(
vararg permissions: String,
- isLegacyApp: Boolean = false
+ isLegacyApp: Boolean = false,
) {
setAppPermissionState(
*permissions,
@@ -1091,10 +1135,17 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
}
}
+ protected fun navigateToIndividualPermissionSetting(
+ permission: String,
+ manuallyNavigate: Boolean = false,
+ ) {
+ navigateToIndividualPermissionSetting(permission, APP_PACKAGE_NAME, manuallyNavigate)
+ }
protected fun navigateToIndividualPermissionSetting(
permission: String,
- manuallyNavigate: Boolean = false
+ packageName: String,
+ manuallyNavigate: Boolean = false,
) {
val useLegacyNavigation = isWatch || isAutomotive || manuallyNavigate
if (useLegacyNavigation) {
@@ -1112,7 +1163,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
runWithShellPermissionIdentity {
context.startActivity(
Intent(Intent.ACTION_MANAGE_APP_PERMISSION).apply {
- putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+ putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
putExtra(Intent.EXTRA_PERMISSION_NAME, permission)
putExtra(Intent.EXTRA_USER, Process.myUserHandle())
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -1367,7 +1418,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
android.Manifest.permission.RECORD_AUDIO,
android.Manifest.permission.ACCESS_FINE_LOCATION,
android.Manifest.permission.ACCESS_COARSE_LOCATION,
- android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true
+ android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+ -> true
else -> false
}
@@ -1378,7 +1430,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
return when (permission) {
Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED,
Manifest.permission.READ_MEDIA_IMAGES,
- Manifest.permission.READ_MEDIA_VIDEO -> true
+ Manifest.permission.READ_MEDIA_VIDEO,
+ -> true
else -> false
}
}
@@ -1386,7 +1439,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
private fun showsForegroundOnlyButton(permission: String): Boolean =
when (permission) {
android.Manifest.permission.CAMERA,
- android.Manifest.permission.RECORD_AUDIO -> true
+ android.Manifest.permission.RECORD_AUDIO,
+ -> true
else -> false
}
@@ -1419,7 +1473,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected fun findAccessibilityNodeInfosByTextForSurfaceView(
node: AccessibilityNodeInfo,
- text: String
+ text: String,
): AccessibilityNodeInfo? {
if (node.text != null && node.text.contains(text)) return node
for (i in 0 until node.childCount) {
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt
index b9d1cb305..798b1942f 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/CameraMicIndicatorsPermissionTest.kt
@@ -23,6 +23,7 @@ import android.content.AttributionSource
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.content.res.Resources
import android.hardware.camera2.CameraManager
import android.os.Build
import android.os.Process
@@ -34,7 +35,6 @@ import android.platform.test.annotations.AsbSecurityTest
import android.platform.test.rule.ScreenRecordRule
import android.provider.DeviceConfig
import android.provider.Settings
-import android.safetycenter.SafetyCenterManager
import android.server.wm.WindowManagerStateHelper
import android.util.Log
import androidx.annotation.RequiresApi
@@ -146,7 +146,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
DeviceConfig.getString(
DeviceConfig.NAMESPACE_PRIVACY,
SAFETY_CENTER_ENABLED,
- false.toString()
+ false.toString(),
)!!
}
@@ -162,7 +162,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
@Before
fun setUp() {
- // Camera and Mic are not supported for secondary user visible as a background user.
+ // Skip the tests as Camera and Mic are not supported for visible background users.
assumeFalse(isCar && UserHelper(context).isVisibleBackgroundUser())
runWithShellPermissionIdentity {
screenTimeoutBeforeTest =
@@ -170,7 +170,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
Settings.System.putLong(
context.contentResolver,
Settings.System.SCREEN_OFF_TIMEOUT,
- 1800000L
+ 1800000L,
)
}
@@ -188,7 +188,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
"feature not present on this device",
callWithShellPermissionIdentity {
CompatChanges.isChangeEnabled(PERMISSION_INDICATORS_NOT_PRESENT, Process.SYSTEM_UID)
- }
+ },
)
install()
}
@@ -203,7 +203,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
DeviceConfig.NAMESPACE_PRIVACY,
INDICATORS_FLAG,
shouldBeEnabled.toString(),
- false
+ false,
)
}
}
@@ -212,7 +212,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
@After
fun tearDown() {
- // Camera and Mic are not supported for secondary user visible as a background user.
+ // Skip the tests as Camera and Mic are not supported for visible background users.
if (isCar && UserHelper(context).isVisibleBackgroundUser()) {
return
}
@@ -223,7 +223,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
}
eventually(
{ assertIndicatorsShown(false, false, false) },
- AUTO_MIC_INDICATOR_DISMISSAL_TIMEOUT_MS
+ AUTO_MIC_INDICATOR_DISMISSAL_TIMEOUT_MS,
)
if (!wasEnabled) {
setIndicatorsEnabledStateIfNeeded(false)
@@ -232,7 +232,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
Settings.System.putLong(
context.contentResolver,
Settings.System.SCREEN_OFF_TIMEOUT,
- screenTimeoutBeforeTest
+ screenTimeoutBeforeTest,
)
}
changeSafetyCenterFlag(safetyCenterEnabled)
@@ -248,7 +248,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
useMic: Boolean,
useCamera: Boolean,
useHotword: Boolean,
- finishEarly: Boolean = false
+ finishEarly: Boolean = false,
) {
context.startActivity(
Intent(USE_INTENT_ACTION).apply {
@@ -289,7 +289,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
useMic = true,
useCamera = false,
finishEarly = true,
- safetyCenterEnabled = getSafetyCenterEnabled()
+ safetyCenterEnabled = getSafetyCenterEnabled(),
)
}
@@ -350,7 +350,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
useMic = false,
useCamera = false,
useHotword = true,
- safetyCenterEnabled = true
+ safetyCenterEnabled = true,
)
}
@@ -365,7 +365,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
useMic = false,
useCamera = true,
chainUsage = true,
- safetyCenterEnabled = true
+ safetyCenterEnabled = true,
)
}
@@ -375,12 +375,12 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
useHotword: Boolean = false,
chainUsage: Boolean = false,
safetyCenterEnabled: Boolean = false,
- finishEarly: Boolean = false
+ finishEarly: Boolean = false,
) {
Log.d(
TAG,
"testCameraAndMicIndicator useMic=$useMic useCamera=$useCamera " +
- "safetyCenterEnabled=$safetyCenterEnabled finishEarly=$finishEarly"
+ "safetyCenterEnabled=$safetyCenterEnabled finishEarly=$finishEarly",
)
// If camera is not available skip the test
if (useCamera) {
@@ -409,7 +409,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
permissionManager.checkPermissionForStartDataDelivery(
Manifest.permission.RECORD_AUDIO,
chainAttribution!!,
- ""
+ "",
)
assertEquals(PermissionManager.PERMISSION_GRANTED, ret)
}
@@ -442,7 +442,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
runWithShellPermissionIdentity {
permissionManager.finishDataDelivery(
Manifest.permission.RECORD_AUDIO,
- chainAttribution
+ chainAttribution,
)
}
}
@@ -481,7 +481,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
micInUse,
useCamera,
chainUsage,
- safetyCenterEnabled
+ safetyCenterEnabled,
)
uiDevice.pressBack()
}
@@ -490,7 +490,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
private fun assertWatchIndicatorsShown(
useMic: Boolean,
useCamera: Boolean,
- useHotword: Boolean
+ useHotword: Boolean,
) {
if (useMic || useHotword || (!useMic && !useCamera && !useHotword)) {
val iconView = UiAutomatorUtils2.waitFindObjectOrNull(By.descContains(WEAR_MIC_LABEL))
@@ -530,7 +530,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
useMic: Boolean,
useCamera: Boolean,
useHotword: Boolean,
- chainUsage: Boolean
+ chainUsage: Boolean,
) {
eventually {
// Ensure the privacy chip is present (or not)
@@ -575,17 +575,17 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
val micLabelView = uiDevice.findObject(UiSelector().textContains(micLabel))
assertFalse(
"View with text $micLabel found, but did not expect to",
- micLabelView.exists()
+ micLabelView.exists(),
)
val cameraLabelView = uiDevice.findObject(UiSelector().textContains(cameraLabel))
assertFalse(
"View with text $cameraLabel found, but did not expect to",
- cameraLabelView.exists()
+ cameraLabelView.exists(),
)
val appView = uiDevice.findObject(UiSelector().textContains(APP_LABEL))
assertFalse(
"View with text $APP_LABEL found, but did not expect to",
- appView.exists()
+ appView.exists(),
)
}
}
@@ -595,7 +595,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
useMic: Boolean,
useCamera: Boolean,
chainUsage: Boolean,
- safetyCenterEnabled: Boolean = false
+ safetyCenterEnabled: Boolean = false,
) {
// Ensure the privacy chip is present
if (useCamera || useMic) {
@@ -655,7 +655,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
context.packageName,
null,
null,
- permissionManager.registerAttributionSource(childAttribution)
+ permissionManager.registerAttributionSource(childAttribution),
)
attrSource = permissionManager.registerAttributionSource(attribution)
} catch (e: PackageManager.NameNotFoundException) {
@@ -705,7 +705,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
DeviceConfig.NAMESPACE_PRIVACY,
SAFETY_CENTER_ENABLED,
safetyCenterEnabled,
- false
+ false,
)
}
}
@@ -720,9 +720,21 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
}
private fun getSafetyCenterEnabled(): Boolean {
- val safetyCenterManager =
- context.getSystemService(SafetyCenterManager::class.java) ?: return false
- return runWithShellPermissionIdentity<Boolean> { safetyCenterManager.isSafetyCenterEnabled }
+ if (!SdkLevel.isAtLeastT()) {
+ // Safety Center does not exist below T.
+ return false
+ }
+ val systemResources = Resources.getSystem()
+ val resId = systemResources.getIdentifier("config_enableSafetyCenter", "bool", "android")
+ val safetyCenterSupported = context.getResources().getBoolean(resId)
+ if (!SdkLevel.isAtLeastU()) {
+ // On T, Safety Center is controlled by the DeviceConfig flag.
+ return safetyCenterSupported && safetyCenterEnabled.toBoolean()
+ }
+ // On UDC+, Safety Center is no longer controlled by DeviceConfig.
+ // The only way it can be disabled is if the OEM suppresses it at the
+ // config level using config_enableSafetyCenter.
+ return safetyCenterSupported
}
protected fun waitFindObject(selector: BySelector): UiObject2? {
@@ -731,7 +743,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
private fun findObjectWithRetry(
automatorMethod: (timeoutMillis: Long) -> UiObject2?,
- timeoutMillis: Long = TIMEOUT_MILLIS
+ timeoutMillis: Long = TIMEOUT_MILLIS,
): UiObject2? {
val startTime = SystemClock.elapsedRealtime()
return try {
@@ -754,7 +766,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
permissionControllerContext.resources.getIdentifier(
resourceName,
"string",
- "com.android.permissioncontroller"
+ "com.android.permissioncontroller",
)
return permissionControllerContext.getString(resourceId)
} catch (e: PackageManager.NameNotFoundException) {
@@ -767,7 +779,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
val micView = waitFindObject(byOneOfText(originalMicLabel, safetyCenterMicLabel))
assertNotNull(
"View with text '$originalMicLabel' or '$safetyCenterMicLabel' not found",
- micView
+ micView,
)
}
@@ -776,7 +788,7 @@ class CameraMicIndicatorsPermissionTest : StsExtraBusinessLogicTestCase {
val cameraView = waitFindObject(byOneOfText(originalCameraLabel, safetyCenterCameraLabel))
assertNotNull(
"View with text '$originalCameraLabel' or '$safetyCenterCameraLabel' not found",
- cameraView
+ cameraView,
)
}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt
new file mode 100644
index 000000000..9a4908c79
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.app.AppOpsManager
+import android.app.Instrumentation
+import android.app.ecm.EnhancedConfirmationManager
+import android.content.ContentProviderOperation
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Build
+import android.os.Process
+import android.permission.flags.Flags
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.provider.ContactsContract
+import android.provider.ContactsContract.CommonDataKinds
+import android.provider.ContactsContract.Data
+import android.provider.ContactsContract.RawContacts
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.After
+import org.junit.AfterClass
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * This test verifies the behavior of the Enhanced Confirmation Manager APIs that deal with unknown
+ * callers
+ */
+@AppModeFull(reason = "Instant apps cannot install packages")
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+@RequiresFlagsEnabled(Flags.FLAG_UNKNOWN_CALL_PACKAGE_INSTALL_BLOCKING_ENABLED)
+// @CddTest(requirement = "TBD")
+class EnhancedConfirmationInCallTest {
+ private val ecm = context.getSystemService(EnhancedConfirmationManager::class.java)!!
+ private val aom = context.getSystemService(AppOpsManager::class.java)!!
+ private val packageManager = context.packageManager
+ private val addedContacts = mutableMapOf<String, List<Uri>>()
+ private val phoneOnlyRestrictedSetting = AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES
+ private val phoneAndEcmRestrictedSetting = AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES
+
+ @JvmField
+ @Rule
+ val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @Before
+ fun assumeNotAutoOrTv() {
+ Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
+ Assume.assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+ }
+
+ companion object {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.targetContext
+ private lateinit var voipService: VoipCallHelper
+
+ @JvmStatic
+ @BeforeClass
+ fun setupVoipService() {
+ voipService = VoipCallHelper(context)
+ voipService.registerPhoneAccount()
+ }
+
+ @JvmStatic
+ @AfterClass
+ fun tearDownVoipService() {
+ voipService.removePhoneAccount()
+ }
+
+ const val CONTACT_DISPLAY_NAME = "Alice Bobson"
+ const val NON_CONTACT_DISPLAY_NAME = "Eve McEve"
+ const val CONTACT_PHONE_NUMBER = "8888888888"
+ const val NON_CONTACT_PHONE_NUMBER = "1111111111"
+ }
+
+ private fun addContact(displayName: String, phoneNumber: String) {
+ runWithShellPermissionIdentity {
+ val ops: ArrayList<ContentProviderOperation> = ArrayList()
+ ops.add(
+ ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
+ .withValue(RawContacts.ACCOUNT_TYPE, "test type")
+ .withValue(RawContacts.ACCOUNT_NAME, "test account")
+ .build()
+ )
+ ops.add(
+ ContentProviderOperation.newInsert(Data.CONTENT_URI)
+ .withValueBackReference(Data.RAW_CONTACT_ID, 0)
+ .withValue(Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+ .withValue(CommonDataKinds.StructuredName.DISPLAY_NAME, displayName)
+ .build()
+ )
+ ops.add(
+ ContentProviderOperation.newInsert(Data.CONTENT_URI)
+ .withValueBackReference(Data.RAW_CONTACT_ID, 0)
+ .withValue(Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE)
+ .withValue(CommonDataKinds.Phone.NUMBER, phoneNumber)
+ .build()
+ )
+ val results = context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops)
+ val resultsForDisplayName = mutableListOf<Uri>()
+ results.forEach { resultsForDisplayName.add(it.uri!!) }
+ addedContacts[displayName] = resultsForDisplayName
+ }
+ }
+
+ private fun removeContact(displayName: String) {
+ runWithShellPermissionIdentity {
+ var totalRowsRemoved = 0
+ for (data in addedContacts[displayName] ?: emptyList()) {
+ totalRowsRemoved += context.contentResolver.delete(data, null)
+ }
+ // There are multiple contacts tables, and removing from the raw_contacts table
+ // can cause row removals from the data table, so we may get some uris that don't
+ // report a delete, but we should get at least one, and not more than the number of uris
+ Assert.assertNotEquals(
+ "Expected at least one contact row to be removed",
+ 0,
+ totalRowsRemoved,
+ )
+ Assert.assertTrue(
+ "Unexpectedly large number of contact rows removed",
+ totalRowsRemoved <= (addedContacts[displayName]?.size ?: 0),
+ )
+ addedContacts.remove(displayName)
+ }
+ }
+
+ @After
+ fun tearDown() {
+ voipService.endCallAndWaitForInactive()
+ addedContacts.keys.forEach { removeContact(it) }
+ runWithShellPermissionIdentity {
+ aom.setUidMode(
+ AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS,
+ Process.myUid(),
+ AppOpsManager.MODE_ALLOWED,
+ )
+ }
+ }
+
+ private fun isSettingRestricted(settingsIdentifier: String): Boolean {
+ return callWithShellPermissionIdentity {
+ ecm.isRestricted(context.packageName, settingsIdentifier)
+ }
+ }
+
+ private fun areSettingsRestricted(): Boolean {
+ return isSettingRestricted(phoneOnlyRestrictedSetting) &&
+ isSettingRestricted(phoneAndEcmRestrictedSetting)
+ }
+
+ @Test
+ fun testIncomingCall_NonContact() {
+ voipService.createCallAndWaitForActive(NON_CONTACT_DISPLAY_NAME, NON_CONTACT_PHONE_NUMBER)
+ Assert.assertTrue(areSettingsRestricted())
+ voipService.endCallAndWaitForInactive()
+ Assert.assertFalse(areSettingsRestricted())
+ }
+
+ @Test
+ fun testIncomingCall_Contact_DisplayNameMatches_PhoneNotGiven() {
+ addContact(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER)
+ // If no phone number is given, the display name will be checked
+ voipService.createCallAndWaitForActive(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER)
+ Assert.assertFalse(areSettingsRestricted())
+ voipService.endCallAndWaitForInactive()
+ Assert.assertFalse(areSettingsRestricted())
+ }
+
+ @Test
+ fun testIncomingCall_Contact_PhoneNumberMatches() {
+ addContact(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER)
+ // If the phone number matches, the display name is not checked
+ voipService.createCallAndWaitForActive(NON_CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER)
+ Assert.assertFalse(areSettingsRestricted())
+ voipService.endCallAndWaitForInactive()
+ Assert.assertFalse(areSettingsRestricted())
+ }
+
+ @Test
+ fun testCall_DoesntBecomeTrustedIfCallerAddedDuringCall() {
+ val tempContactDisplay = "TEMP CONTACT"
+ val tempContactPhone = "999-999-9999"
+ voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone)
+ addContact(tempContactDisplay, tempContactPhone)
+ // State should not be recomputed just because the contact is newly added
+ Assert.assertTrue(areSettingsRestricted())
+ voipService.endCallAndWaitForInactive()
+ voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone)
+ // A new call should recognize our contact, and mark the call as trusted
+ Assert.assertFalse(areSettingsRestricted())
+ }
+
+ @Test
+ fun testCallOnlyRestrictedSetting_notRestrictedIfEcmSet() {
+ // Set the current app to be restricted by ECM
+ runWithShellPermissionIdentity {
+ aom.setUidMode(
+ AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS,
+ Process.myUid(),
+ AppOpsManager.MODE_ERRORED,
+ )
+ }
+ // The ecm and phone restricted setting is restricted
+ Assert.assertFalse(isSettingRestricted(phoneOnlyRestrictedSetting))
+ // But the phone only restriction is not
+ Assert.assertFalse(isSettingRestricted(phoneAndEcmRestrictedSetting))
+ }
+}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt
index cde2b6d6a..596178b70 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt
@@ -29,6 +29,7 @@ import android.platform.test.annotations.AppModeFull
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.CheckFlagsRule
import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.filters.FlakyTest
import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
@@ -204,6 +205,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
}
@RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @FlakyTest(bugId = 387927331)
@Test
fun grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedGroupsDespiteOutOfOrderRequest() {
installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
@@ -219,6 +221,8 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
waitForWindowTransition = false
) {
clickECMAlertDialogOKButton()
+ // TODO: b/387927331 - On some targets, grant dialog hangs after this click
+ Thread.sleep(3_000L)
clickPermissionRequestDenyButton()
}
assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
@@ -234,6 +238,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
}
@RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @FlakyTest(bugId = 387927331)
@Test
fun grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedHighPriorityGroups() {
installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
@@ -247,12 +252,15 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
waitForWindowTransition = false
) {
clickECMAlertDialogOKButton()
+ // TODO: b/387927331 - On some targets, grant dialog hangs after this click
+ Thread.sleep(3_000L)
clickPermissionRequestAllowForegroundButton()
}
assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
}
@RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @FlakyTest(bugId = 390440965)
@Test
fun grantDialogBlocksRestrictedGroupsThenRequestsUnrestrictedLowPriorityGroups() {
installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
@@ -266,6 +274,8 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
waitForWindowTransition = false
) {
clickECMAlertDialogOKButton()
+ // TODO: b/387927331 - On some targets, grant dialog hangs after this click
+ Thread.sleep(3_000L)
clickPermissionRequestAllowForegroundButton()
}
assertTrue(isClearRestrictionAllowed(APP_PACKAGE_NAME))
@@ -311,7 +321,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
Manifest.permission.ACCESS_FINE_LOCATION
private const val GROUP_3_PERMISSION_2_UNRESTRICTED =
Manifest.permission.ACCESS_COARSE_LOCATION
- private const val GROUP_4_PERMISSION_1_UNRESTRICTED = Manifest.permission.BODY_SENSORS
+ private const val GROUP_4_PERMISSION_1_UNRESTRICTED = Manifest.permission.CAMERA
private const val NON_PROTECTED_SETTING = "example_setting_which_is_not_protected"
private const val PROTECTED_SETTING = "android:bind_accessibility_service"
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt
index 176010cf5..4781fb895 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt
@@ -25,6 +25,7 @@ import com.android.modules.utils.build.SdkLevel
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
@FlakyTest
@@ -50,6 +51,8 @@ class LocationAccuracyTest : BaseUsePermissionTest() {
}
@Test
+ @Ignore("b/390440965")
+ // Ignore this test until the cause of flakiness is identified.
fun testCoarsePermissionIsGranted() {
installPackage(APP_APK_PATH_31)
@@ -69,16 +72,20 @@ class LocationAccuracyTest : BaseUsePermissionTest() {
}
@Test
+ @Ignore("b/396478581")
+ // Ignore this test until the cause of flakiness is identified.
fun testPrecisePermissionIsGranted() {
installPackage(APP_APK_PATH_31)
assertAppHasPermission(ACCESS_FINE_LOCATION, false)
assertAppHasPermission(ACCESS_COARSE_LOCATION, false)
assertAppHasPermission(ACCESS_BACKGROUND_LOCATION, false)
+ val waitForWindowTransition = SdkLevel.isAtLeastB();
requestAppPermissionsAndAssertResult(
ACCESS_FINE_LOCATION to true,
- ACCESS_COARSE_LOCATION to true
+ ACCESS_COARSE_LOCATION to true,
+ waitForWindowTransition = waitForWindowTransition
) {
clickPreciseLocationRadioButton()
clickCoarseLocationRadioButton()
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt
index d41c7454f..5e2e40d20 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/MediaPermissionTest.kt
@@ -17,12 +17,18 @@
package android.permissionui.cts
import android.Manifest
+import android.app.role.RoleManager
+import android.content.pm.PackageManager
import android.os.Build
+import android.platform.test.annotations.AsbSecurityTest
import androidx.test.filters.FlakyTest
import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
import com.android.compatibility.common.util.CddTest
import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assert
import org.junit.Assume
+import org.junit.Assume.assumeTrue
import org.junit.Test
/**
@@ -180,4 +186,33 @@ class MediaPermissionTest : BaseUsePermissionTest() {
assertAppHasPermission(Manifest.permission.READ_MEDIA_VIDEO, true)
assertAppHasPermission(Manifest.permission.READ_MEDIA_IMAGES, true)
}
+
+
+ @Test
+ @AsbSecurityTest(cveBugId = [315320090])
+ fun testGalleryAppListedAsFixed() {
+ val galleryPkgs = SystemUtil.callWithShellPermissionIdentity {
+ context.getSystemService(RoleManager::class.java)
+ .getRoleHolders(SYSTEM_GALLERY_ROLE_NAME)
+ }
+ assumeTrue(galleryPkgs.isNotEmpty())
+ val galleryPkg = galleryPkgs[0]
+ val checkPermissionResult = SystemUtil.callWithShellPermissionIdentity {
+ packageManager.checkPermission(Manifest.permission.READ_MEDIA_IMAGES, galleryPkg)
+ }
+ assumeTrue(checkPermissionResult == PackageManager.PERMISSION_GRANTED)
+ navigateToIndividualPermissionSetting(Manifest.permission.READ_MEDIA_IMAGES, galleryPkg)
+ // Attempt to deny the permission. It should not show the
+ // "denying default permission dialog"
+ click(By.res(DENY_RADIO_BUTTON))
+ try {
+ waitFindObject(By.res(CANCEL_BUTTON_ID), 1000L)
+ Assert.fail("expected not to find the default deny dialog")
+ } catch (_: Exception) {}
+ }
+
+ companion object {
+ private val SYSTEM_GALLERY_ROLE_NAME = "android.app.role.SYSTEM_GALLERY"
+ private val CANCEL_BUTTON_ID = "android:id/button1"
+ }
}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt
index c8298b19a..75a8914bf 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/NotificationPermissionTest.kt
@@ -141,7 +141,7 @@ class NotificationPermissionTest : BaseUsePermissionTest() {
launchApp()
killTestApp()
launchApp()
- waitFindObject(By.textContains(ALLOW))
+ waitFindObject(By.textContains(ALLOW).displayId(displayId))
clickPermissionRequestAllowButton()
}
@@ -201,9 +201,10 @@ class NotificationPermissionTest : BaseUsePermissionTest() {
installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
launchApp(startSecondActivity = true)
if (isAutomotive || isWatch) {
- waitFindObject(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)))
+ waitFindObject(
+ By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)).displayId(displayId))
} else {
- waitFindObject(By.res(ALLOW_BUTTON))
+ waitFindObject(By.res(ALLOW_BUTTON).displayId(displayId))
}
pressBack()
clickPermissionRequestAllowButton()
@@ -239,7 +240,7 @@ class NotificationPermissionTest : BaseUsePermissionTest() {
try {
// Watch does not have app bar
if (!isWatch) {
- waitFindObject(By.textContains(SECOND_ACTIVITY_LABEL))
+ waitFindObject(By.textContains(SECOND_ACTIVITY_LABEL).displayId(displayId))
}
assertDialogNotShowing()
} finally {
@@ -400,7 +401,7 @@ class NotificationPermissionTest : BaseUsePermissionTest() {
// Watch does not have app bar
if (!isWatch) {
- waitFindObject(By.textContains(ACTIVITY_LABEL))
+ waitFindObject(By.textContains(ACTIVITY_LABEL).displayId(displayId))
}
}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt
index 751c56b3c..9a12765c0 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionRationalePermissionGrantDialogTest.kt
@@ -41,8 +41,7 @@ import org.junit.Test
@FlakyTest
class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
- @get:Rule
- val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@get:Rule
val deviceConfigPermissionRationaleEnabled =
@@ -50,7 +49,7 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
context,
DeviceConfig.NAMESPACE_PRIVACY,
PERMISSION_RATIONALE_ENABLED,
- true.toString()
+ true.toString(),
)
@Before
@@ -248,8 +247,10 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
- "VanillaIceCream")
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream",
+ )
@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
@Test
fun requestCoarseLocationPerm_hasAslInApk_packageSourceUnspecified() {
@@ -262,8 +263,10 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
- "VanillaIceCream")
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream",
+ )
@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
@Test
fun requestCoarseLocationPerm_hasAslInApk_packageSourceStore() {
@@ -276,8 +279,10 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
- "VanillaIceCream")
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream",
+ )
@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
@Test
fun requestCoarseLocationPerm_hasAslInApk_packageSourceLocalFile() {
@@ -290,8 +295,10 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
- "VanillaIceCream")
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream",
+ )
@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
@Test
fun requestCoarseLocationPerm_hasAslInApk_packageSourceDownloadedFile() {
@@ -304,8 +311,10 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
}
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName =
- "VanillaIceCream")
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM,
+ codeName = "VanillaIceCream",
+ )
@RequiresFlagsEnabled(android.content.pm.Flags.FLAG_ASL_IN_APK_APP_METADATA_SOURCE)
@Test
fun requestCoarseLocationPerm_hasAslInApk_packageSourceOther() {
@@ -338,7 +347,6 @@ class PermissionRationalePermissionGrantDialogTest : BaseUsePermissionTest() {
requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {
clickPermissionRationaleViewInGrantDialog()
assertPermissionRationaleDialogIsVisible(true)
- assertPermissionRationaleContainerOnGrantDialogIsVisible(false)
}
}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
index d509add3a..17cef0e31 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
@@ -16,16 +16,33 @@
package android.permissionui.cts
+import android.content.pm.PackageManager
+import android.health.connect.HealthPermissions
import android.os.Build
+import android.permission.flags.Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.filters.FlakyTest
import androidx.test.filters.SdkSuppress
import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
/** Runtime permission behavior tests for permission splits. */
@FlakyTest
class PermissionSplitTest : BaseUsePermissionTest() {
+
+ companion object {
+ @JvmStatic
+ private val supportHeartrate =
+ packageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_HEART_RATE)
+ }
+
+ @Rule @JvmField val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
@Before
fun assumeNotTv() {
assumeFalse(isTv)
@@ -55,25 +72,113 @@ class PermissionSplitTest : BaseUsePermissionTest() {
testLocationPermissionSplit(false)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ // TODO: b/388596433 - Update maxSdkVersion to VANILLA_ICE_CREAM after SDK bumps.
+ // TODO: b/383440585 - Remove this test when flag annotation issue is fixed.
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ )
+ @Test
+ fun testBodySensorSplitOnTToU() {
+ installPackage(APP_APK_PATH_31)
+ testBodySensorPermissionSplitToBodySensorsBackground(true)
+ }
+
+ // Before SDK_INT bumps to 36, the in-development B images are using SDK_INT=35(V). This will
+ // cause test failures on main builds where replaceBodySensor flag is enabled to remove Sensor
+ // group UI. As a workaround, we move SDK_INT=35 tests out and requires replaceBodySensor flag
+ // disabled when running on these images.
+ // TODO: b/388596433 - Update minSdkVersion to BAKLAVA after SDK bumps.
+ // TODO: b/383440585 - Update minSdkVersion to TIRAMISU when flag annotation issue is fixed.
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @RequiresFlagsDisabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
@Test
- fun testBodySensorSplit() {
+ fun testBodySensorSplitPostV_replaceBodySensorFlagDisabled() {
installPackage(APP_APK_PATH_31)
- testBodySensorPermissionSplit(true)
+ testBodySensorPermissionSplitToBodySensorsBackground(true)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ // TODO: b/388596433 - Update maxSdkVersion to VANILLA_ICE_CREAM after SDK bumps.
+ // TODO: b/383440585 - Remove this test when flag annotation issue is fixed.
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ )
@Test
- fun testBodySensorSplit32() {
+ fun testBodySensorSplit32OnTToU() {
installPackage(APP_APK_PATH_32)
- testBodySensorPermissionSplit(true)
+ testBodySensorPermissionSplitToBodySensorsBackground(true)
+ }
+
+ // Before SDK_INT bumps to 36, the in-development B images are using SDK_INT=35(V). This will
+ // cause test failures on main builds where replaceBodySensor flag is enabled to remove Sensor
+ // group UI. As a workaround, we move SDK_INT=35 tests out and requires replaceBodySensor flag
+ // disabled when running on these images.
+ // TODO: b/388596433 - Update minSdkVersion to BAKLAVA after SDK bumps.
+ // TODO: b/383440585 - Update minSdkVersion to TIRAMISU when flag annotation issue is fixed.
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @RequiresFlagsDisabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun testBodySensorSplit32PostV_replaceBodySensorFlagDisabled() {
+ installPackage(APP_APK_PATH_32)
+ testBodySensorPermissionSplitToBodySensorsBackground(true)
+ }
+
+ // TODO: b/388596433 - Update maxSdkVersion to VANILLA_ICE_CREAM after SDK bumps.
+ // TODO: b/383440585 - Remove this test when flag annotation issue is fixed.
+ @SdkSuppress(
+ minSdkVersion = Build.VERSION_CODES.TIRAMISU,
+ maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
+ )
+ @Test
+ fun testBodySensorNonSplitOnTToU() {
+ installPackage(APP_APK_PATH_LATEST)
+ testBodySensorPermissionSplitToBodySensorsBackground(false)
}
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ // Before SDK_INT bumps to 36, the in-development B images are using SDK_INT=35(V). This will
+ // cause test failures on main builds where replaceBodySensor flag is enabled to remove Sensor
+ // group UI. As a workaround, we move SDK_INT=35 tests out and requires replaceBodySensor flag
+ // disabled when running on these images.
+ // TODO: b/388596433 - Update minSdkVersion to BAKLAVA after SDK bumps.
+ // TODO: b/383440585 - Update minSdkVersion to TIRAMISU when flag annotation issue is fixed.
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @RequiresFlagsDisabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
@Test
- fun testBodySensorNonSplit() {
+ fun testBodySensorNonSplitPostV_replaceBodySensorFlagDisabled() {
installPackage(APP_APK_PATH_LATEST)
- testBodySensorPermissionSplit(false)
+ testBodySensorPermissionSplitToBodySensorsBackground(false)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun testBodySensorSplitOnBaklava_splitToReadHeartRate() {
+ assumeTrue(supportHeartrate)
+ installPackage(APP_APK_PATH_30_WITH_BACKGROUND)
+ assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, false)
+ assertAppHasPermission(HealthPermissions.READ_HEART_RATE, false)
+ assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, false)
+ assertAppHasPermission(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND, false)
+
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.BODY_SENSORS to true,
+ waitForWindowTransition = false,
+ ) {
+ clickAllowReadHeartRate()
+ }
+
+ requestAppPermissionsAndAssertResult(
+ android.Manifest.permission.BODY_SENSORS_BACKGROUND to true,
+ waitForWindowTransition = false,
+ ) {
+ clickAlwaysAllowReadHealthDataInBackground()
+ }
+
+ assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, true)
+ assertAppHasPermission(HealthPermissions.READ_HEART_RATE, true)
+ assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, true)
+ assertAppHasPermission(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND, true)
}
private fun testLocationPermissionSplit(expectSplit: Boolean) {
@@ -82,7 +187,7 @@ class PermissionSplitTest : BaseUsePermissionTest() {
requestAppPermissionsAndAssertResult(
android.Manifest.permission.ACCESS_FINE_LOCATION to true,
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {
if (expectSplit) {
clickPermissionRequestSettingsLinkAndAllowAlways()
@@ -98,13 +203,14 @@ class PermissionSplitTest : BaseUsePermissionTest() {
assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, expectSplit)
}
- private fun testBodySensorPermissionSplit(expectSplit: Boolean) {
+ private fun testBodySensorPermissionSplitToBodySensorsBackground(expectSplit: Boolean) {
+ assumeTrue(supportHeartrate)
assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, false)
assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, false)
requestAppPermissionsAndAssertResult(
android.Manifest.permission.BODY_SENSORS to true,
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {
if (expectSplit) {
clickPermissionRequestSettingsLinkAndAllowAlways()
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt
index baebfe06f..68af60dde 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt
@@ -52,14 +52,17 @@ class PermissionTapjackingTest : BaseUsePermissionTest() {
requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {}
val buttonCenter =
- waitFindObject(By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT))
- .displayId(displayId))
+ waitFindObject(
+ By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT))
+ .displayId(displayId)
+ )
.visibleCenter
// Wait for overlay to hide the dialog
context.sendBroadcast(Intent(ACTION_SHOW_OVERLAY).putExtra(EXTRA_FULL_OVERLAY, true))
waitFindObject(
- By.res("android.permissionui.cts.usepermission:id/overlay").displayId(displayId))
+ By.res("android.permissionui.cts.usepermission:id/overlay").displayId(displayId)
+ )
tryClicking(buttonCenter)
}
@@ -76,18 +79,19 @@ class PermissionTapjackingTest : BaseUsePermissionTest() {
assertAppHasPermission(ACCESS_FINE_LOCATION, false)
requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {}
- val foregroundButtonCenter =
- waitFindObject(By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT))
- .displayId(displayId))
- .visibleCenter
val oneTimeButton =
- waitFindObjectOrNull(By.text(getPermissionControllerString(ALLOW_ONE_TIME_BUTTON_TEXT))
- .displayId(displayId))
+ waitFindObjectOrNull(
+ By.text(getPermissionControllerString(ALLOW_ONE_TIME_BUTTON_TEXT))
+ .displayId(displayId)
+ )
+
// If one-time button is not available, fallback to deny button
val overlayButtonBounds =
oneTimeButton?.visibleBounds
- ?: waitFindObject(By.text(getPermissionControllerString(DENY_BUTTON_TEXT))
- .displayId(displayId))
+ ?: waitFindObject(
+ By.text(getPermissionControllerString(DENY_BUTTON_TEXT))
+ .displayId(displayId)
+ )
.visibleBounds
// Wait for overlay to hide the dialog
@@ -100,7 +104,15 @@ class PermissionTapjackingTest : BaseUsePermissionTest() {
.putExtra(OVERLAY_BOTTOM, overlayButtonBounds.bottom)
)
waitFindObject(
- By.res("android.permissionui.cts.usepermission:id/overlay").displayId(displayId))
+ By.res("android.permissionui.cts.usepermission:id/overlay").displayId(displayId)
+ )
+
+ val foregroundButtonCenter =
+ waitFindObject(
+ By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT))
+ .displayId(displayId)
+ )
+ .visibleCenter
tryClicking(foregroundButtonCenter)
}
@@ -119,7 +131,7 @@ class PermissionTapjackingTest : BaseUsePermissionTest() {
}
assertAppHasPermission(ACCESS_FINE_LOCATION, true)
},
- 10000
+ 10000,
)
} catch (e: RuntimeException) {
// expected
@@ -140,22 +152,26 @@ class PermissionTapjackingTest : BaseUsePermissionTest() {
}
assertAppHasPermission(ACCESS_FINE_LOCATION, true)
},
- 10000
+ 10000,
)
}
private fun click(buttonCenter: Point) {
- val downTime = SystemClock.uptimeMillis()
- val x= buttonCenter.x.toFloat()
- val y = buttonCenter.y.toFloat()
- var event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,x , y, 0)
- event.displayId = displayId
- uiAutomation.injectInputEvent(event, true)
-
- val upTime = SystemClock.uptimeMillis()
- event = MotionEvent.obtain(upTime, upTime, MotionEvent.ACTION_UP, x, y, 0)
- event.displayId = displayId
- uiAutomation.injectInputEvent(event, true)
+ if (isAutomotiveVisibleBackgroundUser) {
+ val downTime = SystemClock.uptimeMillis()
+ val x = buttonCenter.x.toFloat()
+ val y = buttonCenter.y.toFloat()
+ var event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0)
+ event.displayId = displayId
+ uiAutomation.injectInputEvent(event, true)
+
+ val upTime = SystemClock.uptimeMillis()
+ event = MotionEvent.obtain(upTime, upTime, MotionEvent.ACTION_UP, x, y, 0)
+ event.displayId = displayId
+ uiAutomation.injectInputEvent(event, true)
+ } else {
+ uiDevice.click(buttonCenter.x, buttonCenter.y)
+ }
}
companion object {
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt
index 9f76479e3..4a95a962a 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerPermissionTest.kt
@@ -39,16 +39,19 @@ import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.UiAutomatorUtils2
import org.junit.AfterClass
+import org.junit.Assert
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.BeforeClass
+import org.junit.Ignore
import org.junit.Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
@FlakyTest
+@Ignore("b/393428835")
class PhotoPickerPermissionTest : BaseUsePermissionTest() {
companion object {
@@ -68,7 +71,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
NAMESPACE_PRIVACY,
PICKER_ENABLED_SETTING,
true.toString(),
- false
+ false,
)
}
photoUri = PhotoPickerUtils.createImage(context)
@@ -85,7 +88,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
NAMESPACE_PRIVACY,
PICKER_ENABLED_SETTING,
false.toString(),
- false
+ false,
)
}
}
@@ -110,7 +113,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
val permissions = packageInfo.requestedPermissions?.toList() ?: emptyList<String>()
assertFalse(
"Expected app to not request READ_MEDIA_VISUAL_USER_SELECTED",
- permissions.contains(READ_MEDIA_VISUAL_USER_SELECTED)
+ permissions.contains(READ_MEDIA_VISUAL_USER_SELECTED),
)
}
}
@@ -125,7 +128,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
val permissions = packageInfo.requestedPermissions?.toList() ?: emptyList<String>()
assertTrue(
"Expected app to request READ_MEDIA_VISUAL_USER_SELECTED",
- permissions.contains(READ_MEDIA_VISUAL_USER_SELECTED)
+ permissions.contains(READ_MEDIA_VISUAL_USER_SELECTED),
)
}
}
@@ -145,7 +148,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
requestAppPermissionsAndAssertResult(
arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VISUAL_USER_SELECTED),
arrayOf(READ_MEDIA_IMAGES to false, READ_MEDIA_VISUAL_USER_SELECTED to false),
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {
doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
findImageOrVideo(expected = true)
@@ -161,7 +164,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
requestAppPermissionsAndAssertResult(
arrayOf(READ_MEDIA_IMAGES),
arrayOf(READ_MEDIA_IMAGES to true),
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {
doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
clickImageOrVideo()
@@ -174,17 +177,17 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
assertPermissionFlags(
READ_MEDIA_IMAGES,
FLAG_PERMISSION_ONE_TIME to true,
- FLAG_PERMISSION_REVOKED_COMPAT to true
+ FLAG_PERMISSION_REVOKED_COMPAT to true,
)
assertPermissionFlags(
READ_MEDIA_VIDEO,
FLAG_PERMISSION_ONE_TIME to true,
- FLAG_PERMISSION_REVOKED_COMPAT to true
+ FLAG_PERMISSION_REVOKED_COMPAT to true,
)
assertPermissionFlags(
READ_MEDIA_VISUAL_USER_SELECTED,
FLAG_PERMISSION_ONE_TIME to false,
- FLAG_PERMISSION_REVOKED_COMPAT to false
+ FLAG_PERMISSION_REVOKED_COMPAT to false,
)
}
}
@@ -209,7 +212,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
requestAppPermissionsAndAssertResult(
arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VISUAL_USER_SELECTED),
arrayOf(READ_MEDIA_IMAGES to false, READ_MEDIA_VISUAL_USER_SELECTED to true),
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {
doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
clickImageOrVideo()
@@ -265,7 +268,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
requestAppPermissionsAndAssertResult(
arrayOf(READ_MEDIA_IMAGES),
- arrayOf(READ_MEDIA_IMAGES to true)
+ arrayOf(READ_MEDIA_IMAGES to true),
) {
click(By.res(ALLOW_ALL_BUTTON))
}
@@ -278,7 +281,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
installPackage(APP_APK_PATH_LATEST)
requestAppPermissionsAndAssertResult(
arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VISUAL_USER_SELECTED),
- arrayOf(READ_MEDIA_IMAGES to true, READ_MEDIA_VISUAL_USER_SELECTED to true)
+ arrayOf(READ_MEDIA_IMAGES to true, READ_MEDIA_VISUAL_USER_SELECTED to true),
) {
click(By.res(ALLOW_ALL_BUTTON))
}
@@ -338,7 +341,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
.toList()
assertTrue(
"Expected package to have USER_SELECTED",
- requestedPerms.contains(READ_MEDIA_VISUAL_USER_SELECTED)
+ requestedPerms.contains(READ_MEDIA_VISUAL_USER_SELECTED),
)
}
@@ -363,7 +366,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
.toList()
assertTrue(
"Expected package to have USER_SELECTED",
- requestedPerms.contains(READ_MEDIA_VISUAL_USER_SELECTED)
+ requestedPerms.contains(READ_MEDIA_VISUAL_USER_SELECTED),
)
}
@@ -381,14 +384,14 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
installPackage(APP_APK_PATH_IMPLICIT_USER_SELECT_STORAGE)
requestAppPermissionsAndAssertResult(
READ_MEDIA_VISUAL_USER_SELECTED to false,
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {}
uninstallPackage(APP_PACKAGE_NAME)
installPackage(APP_APK_PATH_LATEST)
requestAppPermissionsAndAssertResult(
READ_MEDIA_VISUAL_USER_SELECTED to false,
ACCESS_MEDIA_LOCATION to false,
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {}
}
@@ -399,7 +402,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
requestAppPermissionsAndAssertResult(
READ_MEDIA_IMAGES to true,
READ_MEDIA_VIDEO to true,
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {}
}
@@ -410,7 +413,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
READ_MEDIA_IMAGES to false,
ACCESS_MEDIA_LOCATION to true,
READ_MEDIA_VISUAL_USER_SELECTED to true,
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {
doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
clickImageOrVideo()
@@ -427,7 +430,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
requestAppPermissionsAndAssertResult(
ACCESS_MEDIA_LOCATION to false,
READ_MEDIA_VISUAL_USER_SELECTED to false,
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {
findView(By.res(SELECT_BUTTON), expected = false)
}
@@ -440,7 +443,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
READ_MEDIA_IMAGES to false,
ACCESS_MEDIA_LOCATION to true,
READ_MEDIA_VISUAL_USER_SELECTED to true,
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {
doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
clickImageOrVideo()
@@ -459,7 +462,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
requestAppPermissionsAndAssertResult(
READ_MEDIA_IMAGES to false,
READ_MEDIA_VISUAL_USER_SELECTED to true,
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {
doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
clickImageOrVideo()
@@ -474,7 +477,7 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
requestAppPermissionsAndAssertResult(
READ_MEDIA_IMAGES to false,
READ_MEDIA_VISUAL_USER_SELECTED to true,
- waitForWindowTransition = false
+ waitForWindowTransition = false,
) {
doAndWaitForWindowTransition { click(By.res(SELECT_BUTTON)) }
try {
@@ -506,18 +509,32 @@ class PhotoPickerPermissionTest : BaseUsePermissionTest() {
}
private fun clickImageOrVideo() {
- click(By.res(PhotoPickerUtils.getImageOrVideoResId(context)))
+ PhotoPickerUtils.clickAndWait(uiDevice, PhotoPickerUtils.getMediaItem(uiDevice))
}
private fun clickAllow() {
- click(By.res(PhotoPickerUtils.getAllowId(context)))
+ PhotoPickerUtils.clickAndWait(uiDevice, PhotoPickerUtils.findAddOrDoneButton())
}
private fun findImageOrVideo(expected: Boolean) {
- findView(By.res(PhotoPickerUtils.getImageOrVideoResId(context)), expected)
+ eventually {
+ val item = PhotoPickerUtils.getMediaItem(uiDevice)
+ if (!expected && item.exists()) {
+ Assert.fail("Found image or video, when not expecting it")
+ } else if (expected && !item.exists()) {
+ Assert.fail("Failed to find image or video")
+ }
+ }
}
private fun findVideo(expected: Boolean) {
- findView(By.res(PhotoPickerUtils.getVideoResId(context)), expected)
+ eventually {
+ val item = PhotoPickerUtils.getVideoItem(uiDevice)
+ if (!expected && item.exists()) {
+ Assert.fail("Found video, when not expecting it")
+ } else if (expected && !item.exists()) {
+ Assert.fail("Failed to find video")
+ }
+ }
}
}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt b/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt
index db6c412db..aea5637df 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PhotoPickerUtils.kt
@@ -21,23 +21,27 @@ import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import android.os.FileUtils
+import android.provider.DeviceConfig
import android.provider.MediaStore
import android.provider.cts.media.MediaProviderTestUtils
import android.provider.cts.media.MediaStoreUtils
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject
+import androidx.test.uiautomator.UiSelector
import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
import java.io.IOException
+import org.junit.Assert
+// TODO: b/393428835: We need to merge these utils with the Photo Picker CTS ones
object PhotoPickerUtils {
private const val DISPLAY_NAME_PREFIX = "ctsPermissionPhotoPicker"
private const val VIDEO_ICON_ID = ":id/icon_video"
- private const val IMAGE_CHECK_BOX_ID = ":id/icon_check"
private const val ALLOW_ID = ":id/button_add"
+ private const val REGEX_MEDIA_ITEM_CONTENT_DESCRIPTION = "^(Media|Photo|Video|GIF|Motion)[^s].*"
+ private const val REGEX_VIDEO_ITEM_CONTENT_DESCRIPTION = "^(Video)[^s].*"
+ private const val REGEX_CONTAINS_DONE = ".*[Dd]one.*"
private var mediaProviderPkgName: String? = null
- fun getImageOrVideoResId(context: Context): String {
- return "${getMediaProviderPkgName(context)!!}$IMAGE_CHECK_BOX_ID"
- }
-
fun getVideoResId(context: Context): String {
return "${getMediaProviderPkgName(context)!!}$VIDEO_ICON_ID"
}
@@ -70,7 +74,7 @@ object PhotoPickerUtils {
context,
R.raw.lg_g4_iso_800_jpg,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- "image/jpeg"
+ "image/jpeg",
)
.first
}
@@ -81,7 +85,7 @@ object PhotoPickerUtils {
context,
R.raw.test_video,
MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
- "video/mp4"
+ "video/mp4",
)
.first
}
@@ -108,6 +112,7 @@ object PhotoPickerUtils {
stageMedia(context, resId, collectionUri, mimeType)
}
}
+
@Throws(IOException::class)
private fun stageMedia(
context: Context,
@@ -125,4 +130,44 @@ object PhotoPickerUtils {
return session.publish() to displayName
}
}
+
+ /** Find a media item to perform click events */
+ @Throws(Exception::class)
+ fun getMediaItem(device: UiDevice): UiObject {
+ val mediaItemSelector =
+ UiSelector().descriptionMatches(REGEX_MEDIA_ITEM_CONTENT_DESCRIPTION)
+ return device.findObject(mediaItemSelector)
+ }
+
+ /** Find a media item to perform click events */
+ @Throws(Exception::class)
+ fun getVideoItem(device: UiDevice): UiObject {
+ val mediaItemSelector =
+ UiSelector().descriptionMatches(REGEX_VIDEO_ITEM_CONTENT_DESCRIPTION)
+ return device.findObject(mediaItemSelector)
+ }
+
+ fun findAddOrDoneButton(): UiObject {
+ if (isModernPickerEnabled()) {
+ return UiObject(UiSelector().textMatches(REGEX_CONTAINS_DONE))
+ }
+ return UiObject(UiSelector().resourceIdMatches("$mediaProviderPkgName:id/button_add"))
+ }
+
+ @Throws(Exception::class)
+ fun clickAndWait(uiDevice: UiDevice, uiObject: UiObject) {
+ uiObject.click()
+ uiDevice.waitForIdle()
+ }
+
+ fun isModernPickerEnabled(): Boolean {
+ return try {
+ callWithShellPermissionIdentity {
+ DeviceConfig.getBoolean("mediaprovider", "enable_modern_picker", false)
+ }
+ } catch (e: Exception) {
+ Assert.fail("Failed to check if modern media provider is enabled")
+ false
+ }
+ }
}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt
index e434e9c70..55f028e17 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/ReviewAccessibilityServicesTest.kt
@@ -190,7 +190,7 @@ class ReviewAccessibilityServicesTest {
!uiDevice.performActionAndWait(
{ block() },
Until.newWindow(),
- NEW_WINDOW_TIMEOUT_MILLIS
+ NEW_WINDOW_TIMEOUT_MILLIS,
)
if (timeoutOccurred) {
@@ -212,17 +212,11 @@ class ReviewAccessibilityServicesTest {
private fun waitForSettingsButtonToDisappear() {
SystemUtil.eventually {
- findPCObjectByClassAndText(false,
- "android.widget.Button",
- "Settings"
- )
+ findPCObjectByClassAndText(false, "android.widget.Button", "Settings")
}
}
- private fun findObjectByTextWithoutRetry(
- shouldBePresent: Boolean,
- text: String,
- ): UiObject2? {
+ private fun findObjectByTextWithoutRetry(shouldBePresent: Boolean, text: String): UiObject2? {
val containsWithoutCaseSelector =
By.text(Pattern.compile(".*$text.*", Pattern.CASE_INSENSITIVE))
val view =
@@ -235,7 +229,7 @@ class ReviewAccessibilityServicesTest {
assertEquals(
"Expected to find view with text $text: $shouldBePresent",
shouldBePresent,
- view != null
+ view != null,
)
return view
}
@@ -251,15 +245,16 @@ class ReviewAccessibilityServicesTest {
private fun findPCObjectByClassAndText(
shouldBePresent: Boolean,
className: String,
- text: String
+ text: String,
): UiObject2? {
- val selector = By.pkg(packageName)
- .clazz(className)
- .text(text)
+ val selector = By.pkg(packageName).clazz(className).text(text)
val view = waitFindObjectOrNull(selector)
assertEquals(
"Expected to find view with packageName '$packageName' className '$className' " +
- "text '$text' : $shouldBePresent", shouldBePresent, view != null)
+ "text '$text' : $shouldBePresent",
+ shouldBePresent,
+ view != null,
+ )
return view
}
}
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/VoipCallHelper.kt b/tests/cts/permissionui/src/android/permissionui/cts/VoipCallHelper.kt
new file mode 100644
index 000000000..480d7bff3
--- /dev/null
+++ b/tests/cts/permissionui/src/android/permissionui/cts/VoipCallHelper.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permissionui.cts
+
+import android.content.ComponentName
+import android.content.Context
+import android.net.Uri
+import android.os.Bundle
+import android.os.Process
+import android.permissionui.cts.VoipCallHelper.Companion.EXTRA_DISPLAY_NAME
+import android.permissionui.cts.VoipCallHelper.Companion.awaitingCallStateLatch
+import android.permissionui.cts.VoipCallHelper.Companion.currentActiveConnection
+import android.telecom.Connection
+import android.telecom.ConnectionRequest
+import android.telecom.ConnectionService
+import android.telecom.DisconnectCause
+import android.telecom.PhoneAccount
+import android.telecom.PhoneAccountHandle
+import android.telecom.TelecomManager
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.Assert
+
+/** A helper class which can register a phone account, and make/end VOIP phone calls */
+class VoipCallHelper(val context: Context) {
+ private val telecomManager by lazy { context.getSystemService(TelecomManager::class.java) }
+ private lateinit var phoneAccount: PhoneAccount
+ private val accountHandle =
+ PhoneAccountHandle(
+ ComponentName(context, VoipHelperTestConnectionService::class.java),
+ "cts-voip-helper-test",
+ Process.myUserHandle(),
+ )
+
+ init {
+ registerPhoneAccount()
+ }
+
+ companion object {
+ var currentActiveConnection: VoIPConnection? = null
+ var awaitingCallStateLatch: CallPlacedLatch? = null
+
+ const val EXTRA_DISPLAY_NAME = "display_name"
+ const val CUSTOM_ADDRESS_SCHEMA = "custom_schema"
+ const val CALL_STATE_WAIT_MS = 1000L
+ const val CALL_TIMEOUT_MS = 10000L
+ }
+
+ fun registerPhoneAccount() {
+ val phoneAccountBuilder = PhoneAccount.builder(accountHandle, "CTS VOIP HELPER")
+ phoneAccountBuilder.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+ // see b/343674176. Some OEMs expect the PhoneAccount.getExtras() to be non-null
+ val defaultBundle = Bundle()
+ phoneAccountBuilder.setExtras(defaultBundle)
+
+ // build and register the PhoneAccount via the Platform API
+ phoneAccount = phoneAccountBuilder.build()
+ telecomManager.registerPhoneAccount(phoneAccount)
+ }
+
+ fun removePhoneAccount() {
+ telecomManager.unregisterPhoneAccount(phoneAccount.accountHandle)
+ }
+
+ fun createCallAndWaitForActive(displayName: String?, phoneNumber: String?) {
+ val extras = Bundle()
+
+ val phoneUri =
+ if (phoneNumber != null) {
+ Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null)
+ } else {
+ // If we don't have a phone number, provide a custom address URI, like many VOIP
+ // apps that aren't tied to a phone number do
+ Uri.fromParts(CUSTOM_ADDRESS_SCHEMA, "custom_address", null)
+ }
+ if (displayName != null) {
+ extras.putString(EXTRA_DISPLAY_NAME, displayName)
+ }
+ extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, phoneUri)
+ awaitingCallStateLatch = CallPlacedLatch(phoneUri, displayName)
+ telecomManager.addNewIncomingCall(phoneAccount.accountHandle, extras)
+ Assert.assertTrue(
+ "Timed out waiting for call to start",
+ awaitingCallStateLatch!!.await(CALL_TIMEOUT_MS, TimeUnit.MILLISECONDS),
+ )
+ // TODO b/379941144: Replace wait with waiting until a test InCallService gets a callback
+ Thread.sleep(CALL_STATE_WAIT_MS)
+ }
+
+ fun endCallAndWaitForInactive() {
+ currentActiveConnection?.let { connection ->
+ connection.setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
+ connection.destroy()
+ // TODO b/379941144: Replace wait with waiting until a test InCallService gets a
+ // callback
+ Thread.sleep(CALL_STATE_WAIT_MS)
+ }
+ currentActiveConnection = null
+ }
+}
+
+class CallPlacedLatch(val address: Uri?, val displayName: String?) : CountDownLatch(1) {
+ fun nameAndNumberMatch(connection: Connection): Boolean {
+ return connection.address == address && connection.callerDisplayName == displayName
+ }
+}
+
+class VoIPConnection : Connection() {
+ init {
+ setConnectionProperties(PROPERTY_SELF_MANAGED)
+ setAudioModeIsVoip(true)
+ setActive()
+ }
+
+ override fun onShowIncomingCallUi() {
+ super.onShowIncomingCallUi()
+ setActive()
+ currentActiveConnection = this
+ if (awaitingCallStateLatch?.nameAndNumberMatch(this) == true) {
+ awaitingCallStateLatch?.countDown()
+ }
+ }
+}
+
+class VoipHelperTestConnectionService : ConnectionService() {
+ override fun onCreateOutgoingConnection(
+ connectionManagerPhoneAccount: PhoneAccountHandle,
+ request: ConnectionRequest,
+ ): Connection {
+ return createConnection(request)
+ }
+
+ override fun onCreateIncomingConnection(
+ connectionManagerPhoneAccount: PhoneAccountHandle?,
+ request: ConnectionRequest?,
+ ): Connection {
+ return createConnection(request)
+ }
+
+ private fun createConnection(request: ConnectionRequest?): Connection {
+ val connection = VoIPConnection()
+ if (request?.extras?.containsKey(EXTRA_DISPLAY_NAME) == true) {
+ connection.setCallerDisplayName(
+ request.extras.getString(EXTRA_DISPLAY_NAME),
+ TelecomManager.PRESENTATION_ALLOWED,
+ )
+ connection.setAddress(
+ request.extras.getParcelable(
+ TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+ Uri::class.java,
+ ),
+ TelecomManager.PRESENTATION_ALLOWED,
+ )
+ }
+ return connection
+ }
+}
diff --git a/tests/cts/role/Android.bp b/tests/cts/role/Android.bp
index f0095b7dd..ea9af5d8e 100644
--- a/tests/cts/role/Android.bp
+++ b/tests/cts/role/Android.bp
@@ -30,11 +30,16 @@ android_test {
static_libs: [
"android.permission.flags-aconfig-java-export",
"androidx.test.rules",
+ "com.android.permission.flags-aconfig-java-export",
"compatibility-device-util-axt",
"ctstestrunner-axt",
"Harrier",
+ "bedstead-multiuser",
+ "flag-junit",
"platform-test-annotations",
+ "platform-test-rules",
"truth",
+ "uiautomator-helpers",
],
test_suites: [
@@ -45,9 +50,17 @@ android_test {
],
data: [
+ ":CtsDefaultNotesApp",
":CtsRoleTestApp",
":CtsRoleTestApp28",
":CtsRoleTestApp33WithoutInCallService",
":CtsRoleTestAppClone",
],
}
+
+filegroup {
+ name: "CtsRoleTestUtils",
+ srcs: [
+ "src/android/app/role/cts/RoleManagerUtil.kt",
+ ],
+}
diff --git a/tests/cts/role/AndroidManifest.xml b/tests/cts/role/AndroidManifest.xml
index a8c8c8e3d..7ea4287dc 100644
--- a/tests/cts/role/AndroidManifest.xml
+++ b/tests/cts/role/AndroidManifest.xml
@@ -22,6 +22,7 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+ <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application>
diff --git a/tests/cts/role/AndroidTest.xml b/tests/cts/role/AndroidTest.xml
index 73f23dd1b..9a60b09e3 100644
--- a/tests/cts/role/AndroidTest.xml
+++ b/tests/cts/role/AndroidTest.xml
@@ -32,6 +32,8 @@
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsRoleTestCases.apk" />
+ <option name="install-arg" value="-t" />
+ <option name="test-file-name" value="CtsDefaultNotesApp.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
@@ -40,6 +42,7 @@
</target_preparer>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
+ <option name="push" value="CtsDefaultNotesApp.apk->/data/local/tmp/cts-role/CtsDefaultNotesApp.apk" />
<option name="push" value="CtsRoleTestApp.apk->/data/local/tmp/cts-role/CtsRoleTestApp.apk" />
<option name="push" value="CtsRoleTestApp28.apk->/data/local/tmp/cts-role/CtsRoleTestApp28.apk" />
<option name="push" value="CtsRoleTestApp33WithoutInCallService.apk->/data/local/tmp/cts-role/CtsRoleTestApp33WithoutInCallService.apk" />
diff --git a/tests/cts/role/src/android/app/role/cts/ChooseNoteRoleAppTest.kt b/tests/cts/role/src/android/app/role/cts/ChooseNoteRoleAppTest.kt
new file mode 100644
index 000000000..18003d1d9
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/ChooseNoteRoleAppTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role.cts
+
+import android.content.Intent
+import android.platform.test.rule.NotesRoleManagerRule
+import android.platform.uiautomatorhelpers.WaitUtils.ensureThat
+import android.provider.Settings
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.By.text
+import com.android.compatibility.common.util.UiAutomatorUtils2.getUiDevice
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class ChooseNoteRoleAppTest {
+
+ @[Rule JvmField]
+ val rule = NotesRoleManagerRule(requiredNotesRoleHolderPackage = NOTES_APP_PACKAGE_NAME)
+
+ @Before
+ fun setUp() {
+ rule.utils.clearRoleHolder()
+ InstrumentationRegistry.getInstrumentation()
+ .context
+ .startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ }
+
+ @After
+ fun after() {
+ getUiDevice().pressHome()
+ }
+
+ @Test
+ fun chooseNoteRoleHolderApp() {
+ ensureThat { rule.utils.getRoleHolderPackageName().isEmpty() }
+
+ // Scroll to "Notes app" item in Default apps screen and click on it
+ waitFindObject(text("Notes app")).click()
+ // Scroll to "CtsDefaultNotesApp" item and click on it
+ waitFindObject(text("CtsDefaultNotesApp")).click()
+
+ assertEquals(rule.utils.getRoleHolderPackageName(), NOTES_APP_PACKAGE_NAME)
+ }
+
+ private companion object {
+ const val NOTES_APP_PACKAGE_NAME = "com.android.cts.notesapp"
+ }
+}
diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
index e31659dbc..5e61c66be 100644
--- a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
@@ -16,7 +16,9 @@
package android.app.role.cts;
+import static com.android.bedstead.multiuser.MultiUserDeviceStateExtensionsKt.privateProfile;
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.eventually;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
@@ -25,6 +27,7 @@ import static com.android.compatibility.common.util.UiAutomatorUtils2.waitFindOb
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -66,7 +69,7 @@ import androidx.test.uiautomator.Until;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasPrivateProfile;
+import com.android.bedstead.multiuser.annotations.EnsureHasPrivateProfile;
import com.android.bedstead.nene.types.OptionalBoolean;
import com.android.compatibility.common.util.DisableAnimationRule;
import com.android.compatibility.common.util.FreezeRotationRule;
@@ -103,6 +106,8 @@ public class RoleManagerTest {
private static final String ROLE_NAME = RoleManager.ROLE_BROWSER;
private static final String ROLE_PHONE_NAME = RoleManager.ROLE_DIALER;
private static final String ROLE_SMS_NAME = RoleManager.ROLE_SMS;
+ private static final String PROFILE_GROUP_EXCLUSIVE_ROLE_NAME =
+ RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY;
private static final String ROLE_SHORT_LABEL = "Browser app";
private static final String APP_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp.apk";
@@ -164,6 +169,9 @@ public class RoleManagerTest {
? By.text("Don\u2019t ask again")
: By.res("com.android.permissioncontroller:id/dont_ask_again");
+ private static final BySelector PERMISSION_APP_LABEL =
+ By.pkg(sPackageManager.getPermissionControllerPackageName()).text(APP_LABEL);
+
@Rule
public CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@Rule
@@ -184,6 +192,7 @@ public class RoleManagerTest {
@Before
public void setUp() throws Exception {
+ assumeTrue(RoleManagerUtil.INSTANCE.isCddCompliantScreenSize());
saveRoleHolder();
installApp();
wakeUpScreen();
@@ -281,6 +290,10 @@ public class RoleManagerTest {
public void requestRoleThenBlockRequestRoleDialogByRestrictedSettingDialog() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
assumeFalse(sIsWatch || sIsAutomotive || sIsTelevision);
+ // TODO: b/388960315 - Remove wait after addressing race condition
+ runWithShellPermissionIdentity(
+ () -> waitForEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME,
+ AppOpsManager.MODE_DEFAULT));
runWithShellPermissionIdentity(
() -> setEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME,
AppOpsManager.MODE_ERRORED));
@@ -462,7 +475,7 @@ public class RoleManagerTest {
private void respondToRoleRequest(boolean allow)
throws InterruptedException, UiObjectNotFoundException {
if (allow) {
- waitFindObject(By.text(APP_LABEL)).click();
+ waitFindObject(PERMISSION_APP_LABEL).click();
}
Pair<Integer, Intent> result = clickButtonAndWaitForResult(allow);
int expectedResult = allow ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
@@ -708,6 +721,10 @@ public class RoleManagerTest {
throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
assumeFalse(sIsWatch || sIsAutomotive || sIsTelevision);
+ // TODO: b/388960315 - Remove wait after addressing race condition
+ runWithShellPermissionIdentity(
+ () -> waitForEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME,
+ AppOpsManager.MODE_DEFAULT));
runWithShellPermissionIdentity(
() -> setEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME,
AppOpsManager.MODE_ERRORED));
@@ -864,7 +881,7 @@ public class RoleManagerTest {
return;
}
- UserHandle privateProfile = sDeviceState.privateProfile().userHandle();
+ UserHandle privateProfile = privateProfile(sDeviceState).userHandle();
assertThat(privateProfile).isNotNull();
installPackage(APP_APK_PATH, privateProfile);
installPackage(APP_CLONE_APK_PATH, privateProfile);
@@ -918,6 +935,19 @@ public class RoleManagerTest {
pressBack();
}
+ private void waitForEnhancedConfirmationRestrictedAppOpMode(@NonNull Context context,
+ @NonNull String packageName, int expectedMode)
+ throws PackageManager.NameNotFoundException {
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ eventually(() -> {
+ int uid = context.getPackageManager().getApplicationInfo(packageName, 0).uid;
+ int actualMode = appOpsManager.checkOpNoThrow(
+ AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, uid, packageName);
+ assertEquals("Even after waiting, a test app's post-install"
+ + " ACCESS_RESTRICTED_SETTINGS op mode is incorrect", expectedMode, actualMode);
+ });
+ }
+
private void setEnhancedConfirmationRestrictedAppOpMode(@NonNull Context context,
@NonNull String packageName, int mode)
throws PackageManager.NameNotFoundException {
@@ -1347,6 +1377,241 @@ public class RoleManagerTest {
});
}
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotGetActiveUserForRoleWithoutPermission() throws Exception {
+ assertThrows(SecurityException.class, () ->
+ sRoleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotGetActiveUserForNonProfileGroupExclusiveRole() throws Exception {
+ runWithShellPermissionIdentity(() ->
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.getActiveUserForRole(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER)));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotSetActiveUserForRoleWithoutPermission() throws Exception {
+ assertThrows(SecurityException.class, () ->
+ sRoleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME,
+ Process.myUserHandle(), 0));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotSetActiveUserForNonProfileGroupExclusiveRole() throws Exception {
+ runWithShellPermissionIdentity(() ->
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.setActiveUserForRole(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER, Process.myUserHandle(),
+ 0)));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void setAndGetActiveUserForRole() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME,
+ Process.myUserHandle(), 0);
+ assertThat(sRoleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME))
+ .isEqualTo(Process.myUserHandle());
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotGetDefaultHoldersForTestNoPermissions() throws Exception {
+ assertThrows(SecurityException.class, () ->
+ sRoleManager.getDefaultHoldersForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotGetDefaultHoldersForTestEmptyRoleName() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.getDefaultHoldersForTest(""));
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotGetDefaultHoldersForTestNonTestRoleName() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.getDefaultHoldersForTest(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER));
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotSetDefaultHoldersForTestNoPermissions() throws Exception {
+ List<String> testRoleHolders = List.of("a", "b", "c");
+ assertThrows(SecurityException.class, () ->
+ sRoleManager.setDefaultHoldersForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME,
+ testRoleHolders));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotSetDefaultHoldersForTestEmptyRoleName() throws Exception {
+ List<String> testRoleHolders = List.of("a", "b", "c");
+ runWithShellPermissionIdentity(() -> {
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.setDefaultHoldersForTest("", testRoleHolders));
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotSetDefaultHoldersForTestNonTestRoleName() throws Exception {
+ List<String> testRoleHolders = List.of("a", "b", "c");
+ runWithShellPermissionIdentity(() -> {
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.setDefaultHoldersForTest(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER, testRoleHolders));
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotSetDefaultHoldersForTestNullPackageNames() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ assertThrows(NullPointerException.class, () ->
+ sRoleManager.setDefaultHoldersForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME,
+ null));
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void setAndGetDefaultHolders() throws Exception {
+ List<String> testRoleHolders = List.of("a", "b", "c");
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.setDefaultHoldersForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME,
+ testRoleHolders);
+ List<String> roleHolders =
+ sRoleManager.getDefaultHoldersForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME);
+ assertThat(roleHolders).isEqualTo(testRoleHolders);
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void setAndGetDefaultHoldersNoRoleHolders() throws Exception {
+ List<String> initialRoleHolders = List.of("a", "b", "c");
+ List<String> testRoleHolders = Collections.emptyList();
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.setDefaultHoldersForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME,
+ initialRoleHolders);
+ sRoleManager.setDefaultHoldersForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME,
+ testRoleHolders);
+ List<String> roleHolders =
+ sRoleManager.getDefaultHoldersForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME);
+ assertThat(roleHolders).isEqualTo(testRoleHolders);
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotGetIsRoleVisibleForTestNoPermissions() throws Exception {
+ assertThrows(SecurityException.class, () ->
+ sRoleManager.isRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotGetIsRoleVisibleForTestEmptyRoleName() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.isRoleVisibleForTest(""));
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotGetIsRoleVisibleForTestNonTestRoleName() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.isRoleVisibleForTest(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER));
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotSetRoleVisibleForTestNoPermissions() throws Exception {
+ assertThrows(SecurityException.class, () ->
+ sRoleManager.setRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME, false));
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotSetRoleVisibleForTestEmptyRoleName() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.setRoleVisibleForTest("", false));
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void cannotSetRoleVisibleForTestNonTestRoleName() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.setRoleVisibleForTest(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER,
+ false));
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void setAndGetIsRoleVisibleForTestSetTrue() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.setRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME, true);
+ boolean isRoleVisibleForTest =
+ sRoleManager.isRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME);
+ assertThat(isRoleVisibleForTest).isEqualTo(true);
+ });
+ }
+
+ @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ public void setAndGetIsRoleVisibleForTestSetFalse() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.setRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME, false);
+ boolean isRoleVisibleForTest =
+ sRoleManager.isRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME);
+ assertThat(isRoleVisibleForTest).isEqualTo(false);
+ });
+ }
+
@NonNull
private List<String> getRoleHolders(@NonNull String roleName) throws Exception {
return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName));
diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerUtil.kt b/tests/cts/role/src/android/app/role/cts/RoleManagerUtil.kt
new file mode 100644
index 000000000..10a3834a2
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/RoleManagerUtil.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.role.cts
+
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.util.Log
+
+object RoleManagerUtil {
+ private val TAG = RoleManagerUtil::class.java.getSimpleName()
+
+ /**
+ * This method checks for the minimum screen size described in CDD {@see
+ * https://source.android.com/docs/compatibility/14/android-14-cdd#7111_screen_size_and_shape}
+ */
+ fun isCddCompliantScreenSize(): Boolean {
+ if (
+ Resources.getSystem().configuration.uiMode and Configuration.UI_MODE_TYPE_MASK ==
+ Configuration.UI_MODE_TYPE_WATCH
+ ) {
+ Log.d(TAG, "UI mode is UI_MODE_TYPE_WATCH, skipping the min dp check")
+ return true
+ }
+
+ val screenSize =
+ Resources.getSystem().configuration.screenLayout and
+ Configuration.SCREENLAYOUT_SIZE_MASK
+ return when (screenSize) {
+ Configuration.SCREENLAYOUT_SIZE_SMALL -> hasMinScreenSize(426, 320)
+ Configuration.SCREENLAYOUT_SIZE_NORMAL -> hasMinScreenSize(480, 320)
+ Configuration.SCREENLAYOUT_SIZE_LARGE -> hasMinScreenSize(640, 480)
+ Configuration.SCREENLAYOUT_SIZE_XLARGE -> hasMinScreenSize(960, 720)
+ else -> {
+ Log.e(TAG, "Unknown screen size: $screenSize")
+ true
+ }
+ }
+ }
+
+ private fun hasMinScreenSize(minWidthDp: Int, minHeightDp: Int): Boolean {
+ val dpi = Resources.getSystem().displayMetrics.densityDpi
+ val widthDp = (160f / dpi) * Resources.getSystem().displayMetrics.widthPixels
+ val heightDp = (160f / dpi) * Resources.getSystem().displayMetrics.heightPixels
+
+ // CDD does seem to follow width & height convention correctly, hence checking both ways
+ return (widthDp >= minWidthDp && heightDp >= minHeightDp) ||
+ (widthDp >= minHeightDp && heightDp >= minWidthDp)
+ }
+}
diff --git a/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt b/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt
index 83d4f78ad..c9dc97b8f 100644
--- a/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt
+++ b/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt
@@ -19,15 +19,19 @@ package android.app.role.cts
import android.app.role.RoleManager
import android.os.Build
import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.InstrumentationRegistry
import androidx.test.filters.SdkSuppress
import androidx.test.runner.AndroidJUnit4
import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.permission.flags.Flags
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Assert.assertThrows
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,6 +47,8 @@ class RoleShellCommandTest {
private var roleHolder: String? = null
private var wasBypassingRoleQualification: Boolean = false
+ @get:Rule val flagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
@Before
public fun setUp() {
saveRoleHolder()
@@ -156,6 +162,31 @@ class RoleShellCommandTest {
assertThat(isBypassingRoleQualification()).isFalse()
}
+ @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ fun setActiveUserForProfileGroupExclusiveRoleAsUser() {
+ val activeUser = userId
+ setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, activeUser)
+
+ val currentActiveUserId = getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ assertThat(currentActiveUserId).isEqualTo(activeUser)
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ fun setActiveUserForNonProfileGroupExclusiveRoleThenFails() {
+ assertThrows(AssertionError::class.java) { setActiveUserForRole(ROLE_NAME, userId) }
+ }
+
+ @RequiresFlagsEnabled(Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @Test
+ fun getActiveUserForNonProfileGroupExclusiveRoleThenFails() {
+ assertThrows(AssertionError::class.java) { getActiveUserForRole(ROLE_NAME) }
+ }
+
private fun addRoleHolder(packageName: String = APP_PACKAGE_NAME) {
runShellCommandOrThrow("cmd role add-role-holder --user $userId $ROLE_NAME $packageName")
}
@@ -204,8 +235,22 @@ class RoleShellCommandTest {
callWithShellPermissionIdentity { roleManager.setBypassingRoleQualification(value) }
}
+ private fun getActiveUserForRole(roleName: String): Int? {
+ return runShellCommandOrThrow("cmd role get-active-user-for-role --user $userId $roleName")
+ .trim()
+ .toIntOrNull()
+ }
+
+ private fun setActiveUserForRole(roleName: String, activeUserId: Int) {
+ runShellCommandOrThrow(
+ "cmd role set-active-user-for-role --user $userId $roleName $activeUserId"
+ )
+ }
+
companion object {
private const val ROLE_NAME = RoleManager.ROLE_BROWSER
+ private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME =
+ RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY
private const val APP_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp.apk"
private const val APP_PACKAGE_NAME = "android.app.role.cts.app"
private const val APP_CLONE_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestAppClone.apk"
diff --git a/tests/cts/rolemultiuser/Android.bp b/tests/cts/rolemultiuser/Android.bp
new file mode 100644
index 000000000..51eff83b9
--- /dev/null
+++ b/tests/cts/rolemultiuser/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsRoleMultiUserTestCases",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+
+ srcs: [
+ "src/**/*.kt",
+ ":CtsRoleTestUtils",
+ ],
+
+ static_libs: [
+ "bedstead-flags",
+ "bedstead-multiuser",
+ "com.android.permission.flags-aconfig-java-export",
+ "ctstestrunner-axt",
+ "Harrier",
+ "flag-junit",
+ "Nene",
+ "truth",
+ ],
+
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ "mcts-permission",
+ ],
+
+ data: [
+ ":CtsRoleTestApp",
+ ],
+}
diff --git a/tests/cts/rolemultiuser/AndroidManifest.xml b/tests/cts/rolemultiuser/AndroidManifest.xml
new file mode 100644
index 000000000..77710a5aa
--- /dev/null
+++ b/tests/cts/rolemultiuser/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.rolemultiuser.cts">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+
+ <activity
+ android:name=".WaitForResultActivity"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"/>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.app.rolemultiuser.cts"
+ android:label="CTS multi-user tests of android.app.role">
+ </instrumentation>
+</manifest>
diff --git a/tests/cts/rolemultiuser/AndroidTest.xml b/tests/cts/rolemultiuser/AndroidTest.xml
new file mode 100644
index 000000000..510b70fdb
--- /dev/null
+++ b/tests/cts/rolemultiuser/AndroidTest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Config for CTS role multi-user test cases">
+
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="permissions" />
+ <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="run_on_sdk_sandbox" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.permission.apex" />
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsRoleMultiUserTestCases.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts-role" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts-role"/>
+ </target_preparer>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsRoleTestApp.apk->/data/local/tmp/cts-role/CtsRoleTestApp.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.app.rolemultiuser.cts" />
+ <option name="exclude-annotation" value="com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile" />
+ <option name="exclude-annotation" value="com.android.bedstead.multiuser.annotations.RequireRunOnSecondaryUser" />
+ <option name="runtime-hint" value="5m" />
+ </test>
+</configuration>
diff --git a/tests/cts/rolemultiuser/TEST_MAPPING b/tests/cts/rolemultiuser/TEST_MAPPING
new file mode 100644
index 000000000..b45469e14
--- /dev/null
+++ b/tests/cts/rolemultiuser/TEST_MAPPING
@@ -0,0 +1,17 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsRoleMultiUserTestCases"
+ }
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]"
+ }
+ ],
+ "permission-mainline-presubmit": [
+ {
+ "name": "CtsRoleMultiUserTestCases"
+ }
+ ]
+}
diff --git a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
new file mode 100644
index 000000000..3e24d5025
--- /dev/null
+++ b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
@@ -0,0 +1,2412 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.rolemultiuser.cts
+
+import android.app.Activity
+import android.app.role.RoleManager
+import android.app.role.cts.RoleManagerUtil
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.os.UserManager.DISALLOW_CONFIG_DEFAULT_APPS
+import android.provider.Settings
+import android.util.Log
+import android.util.Pair
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.ActivityTestRule
+import androidx.test.uiautomator.By
+import com.android.bedstead.enterprise.annotations.EnsureDoesNotHaveUserRestriction
+import com.android.bedstead.enterprise.annotations.EnsureHasNoWorkProfile
+import com.android.bedstead.enterprise.annotations.EnsureHasUserRestriction
+import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile
+import com.android.bedstead.enterprise.annotations.RequireRunOnWorkProfile
+import com.android.bedstead.enterprise.workProfile
+import com.android.bedstead.flags.annotations.RequireFlagsEnabled
+import com.android.bedstead.harrier.BedsteadJUnit4
+import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.harrier.UserType.INITIAL_USER
+import com.android.bedstead.harrier.UserType.WORK_PROFILE
+import com.android.bedstead.multiuser.annotations.EnsureCanAddUser
+import com.android.bedstead.multiuser.annotations.EnsureHasAdditionalUser
+import com.android.bedstead.multiuser.annotations.EnsureHasPrivateProfile
+import com.android.bedstead.multiuser.annotations.EnsureHasSecondaryUser
+import com.android.bedstead.multiuser.annotations.RequireRunNotOnSecondaryUser
+import com.android.bedstead.multiuser.annotations.RequireRunOnPrimaryUser
+import com.android.bedstead.multiuser.privateProfile
+import com.android.bedstead.multiuser.secondaryUser
+import com.android.bedstead.nene.TestApis.context
+import com.android.bedstead.nene.TestApis.permissions
+import com.android.bedstead.nene.TestApis.users
+import com.android.bedstead.nene.types.OptionalBoolean
+import com.android.bedstead.nene.userrestrictions.CommonUserRestrictions.DISALLOW_ADD_MANAGED_PROFILE
+import com.android.bedstead.nene.users.UserReference
+import com.android.bedstead.nene.users.UserType
+import com.android.bedstead.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL
+import com.android.bedstead.permissions.CommonPermissions.MANAGE_DEFAULT_APPLICATIONS
+import com.android.bedstead.permissions.CommonPermissions.MANAGE_ROLE_HOLDERS
+import com.android.bedstead.permissions.annotations.EnsureDoesNotHavePermission
+import com.android.bedstead.permissions.annotations.EnsureHasPermission
+import com.android.compatibility.common.util.DisableAnimationRule
+import com.android.compatibility.common.util.FreezeRotationRule
+import com.android.compatibility.common.util.SystemUtil
+import com.android.compatibility.common.util.SystemUtil.eventually
+import com.android.compatibility.common.util.UiAutomatorUtils2.getUiDevice
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject
+import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObjectOrNull
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+import org.junit.After
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertThrows
+import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.ClassRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+@RunWith(BedsteadJUnit4::class)
+class RoleManagerMultiUserTest {
+
+ @JvmField
+ @Rule
+ var activityRule: ActivityTestRule<WaitForResultActivity> =
+ ActivityTestRule(WaitForResultActivity::class.java)
+
+ @Before
+ fun setUp() {
+ assumeTrue(RoleManagerUtil.isCddCompliantScreenSize())
+ installAppForAllUsers()
+
+ // If "none" selected in test, ensure we re-enable fallback for other test runs
+ permissions().withPermission(MANAGE_ROLE_HOLDERS, INTERACT_ACROSS_USERS_FULL).use {
+ setRoleFallbackEnabledForAllUsers()
+ }
+ }
+
+ @After
+ fun tearDown() {
+ uninstallAppForAllUsers()
+
+ // If "none" selected in test, ensure we re-enable fallback for other test runs
+ permissions().withPermission(MANAGE_ROLE_HOLDERS, INTERACT_ACROSS_USERS_FULL).use {
+ setRoleFallbackEnabledForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @EnsureHasPrivateProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(Exception::class)
+ fun isAvailableAsUserForProfileGroupExclusiveRole() {
+ val workProfileRoleManager = getRoleManagerForUser(deviceState.workProfile())
+ val privateProfileRoleManager = getRoleManagerForUser(deviceState.privateProfile())
+
+ assertThat(roleManager.isRoleAvailable(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)).isTrue()
+ assertThat(workProfileRoleManager.isRoleAvailable(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isTrue()
+ assertThat(privateProfileRoleManager.isRoleAvailable(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isFalse()
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @Test
+ @Throws(Exception::class)
+ fun cannotGetActiveUserForNonCrossUserRole() {
+ assertThrows(IllegalArgumentException::class.java) {
+ roleManager.getActiveUserForRole(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+ @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
+ @Test
+ @Throws(Exception::class)
+ fun cannotGetActiveUserForRoleWithoutInteractAcrossUserPermission() {
+ assertThrows(SecurityException::class.java) {
+ roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL)
+ @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS, MANAGE_DEFAULT_APPLICATIONS)
+ @Test
+ @Throws(Exception::class)
+ fun cannotGetActiveUserForRoleWithoutManageRoleAndManageDefaultApplicationsPermission() {
+ assertThrows(SecurityException::class.java) {
+ roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForNonCrossUserRole() {
+ assertThrows(IllegalArgumentException::class.java) {
+ roleManager.setActiveUserForRole(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER,
+ Process.myUserHandle(),
+ 0,
+ )
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+ @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForRoleWithoutInteractAcrossUserPermission() {
+ assertThrows(SecurityException::class.java) {
+ roleManager.setActiveUserForRole(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ Process.myUserHandle(),
+ 0,
+ )
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL)
+ @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS, MANAGE_DEFAULT_APPLICATIONS)
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForRoleWithoutManageRoleAndManageDefaultApplicationsPermission() {
+ assertThrows(SecurityException::class.java) {
+ roleManager.setActiveUserForRole(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ Process.myUserHandle(),
+ 0,
+ )
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForRoleToNonExistentUser() {
+ val targetActiveUser = users().nonExisting().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isNotEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasPrivateProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForRoleToPrivateProfileUser() {
+ val targetActiveUser = deviceState.privateProfile().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isNotEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasAdditionalUser(installInstrumentedApp = OptionalBoolean.TRUE)
+ @EnsureHasSecondaryUser
+ @RequireRunNotOnSecondaryUser
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetActiveUserForRoleToUserNotInProfileGroup() {
+ val targetActiveUser = deviceState.secondaryUser().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isNotEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @EnsureHasAdditionalUser(installInstrumentedApp = OptionalBoolean.TRUE)
+ @EnsureHasSecondaryUser
+ @RequireRunNotOnSecondaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun ensureRoleHasActiveUser() {
+ val primaryUser = deviceState.initialUser()
+ val primaryUserId = primaryUser.userHandle().identifier
+ val primaryUserRoleManager = getRoleManagerForUser(primaryUser)
+ val secondaryUser = deviceState.secondaryUser()
+ val secondaryUserId = secondaryUser.userHandle().identifier
+ val secondaryUserRoleManager = getRoleManagerForUser(secondaryUser)
+
+ assertWithMessage("Expected active user in profile group for user $primaryUserId")
+ .that(primaryUserRoleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isNotNull()
+ assertWithMessage("Expected active user in profile group for user $secondaryUserId")
+ .that(
+ secondaryUserRoleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isNotNull()
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureDoesNotHavePermission(MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @Test
+ @Throws(Exception::class)
+ fun setAndGetActiveUserForRoleSetCurrentUserWithManageRoleHoldersPermission() {
+ assumeFalse(
+ "setActiveUser not supported for private profile",
+ users().current().type().name() == PRIVATE_PROFILE_TYPE_NAME,
+ )
+
+ val targetActiveUser = users().current().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureDoesNotHavePermission(MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @Test
+ @Throws(Exception::class)
+ fun setAndGetActiveUserForRoleSetCurrentUserWithManageDefaultApplicationPermission() {
+ assumeFalse(
+ "setActiveUser not supported for private profile",
+ users().current().type().name() == PRIVATE_PROFILE_TYPE_NAME,
+ )
+
+ val targetActiveUser = users().current().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, targetActiveUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @Test
+ @Throws(Exception::class)
+ fun setAndGetActiveUserForRoleSetCurrentUserEnsureRoleNotHeldByInactiveUser() {
+ assumeFalse(
+ "setActiveUser not supported for private profile",
+ users().current().type().name() == PRIVATE_PROFILE_TYPE_NAME,
+ )
+ // initialUser needs to be not the targetUser
+ val targetActiveUser = users().current().userHandle()
+ val initialUser =
+ if (targetActiveUser == deviceState.initialUser().userHandle()) {
+ deviceState.workProfile().userHandle()
+ } else {
+ deviceState.initialUser().userHandle()
+ }
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+
+ roleManager.setActiveUserForRole(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ targetActiveUser,
+ 0,
+ )
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ // We can assume targetActiveUser is role holder since fallback is enabled
+ eventually { assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser) }
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile(installInstrumentedApp = OptionalBoolean.TRUE)
+ @Test
+ @Throws(Exception::class)
+ fun setAndGetActiveUserForRoleSetWorkProfile() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ roleManager.setActiveUserForRole(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ targetActiveUser,
+ 0,
+ )
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ // We can assume targetActiveUser is role holder since fallback is enabled
+ eventually { assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser) }
+ } finally {
+ setDefaultHoldersForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(MANAGE_ROLE_HOLDERS)
+ @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(Exception::class)
+ fun cannotAddRoleHolderAsUserForProfileExclusiveRoleWithoutInteractAcrossUserPermission() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ // setActiveUserForRole and getActiveUserForRole is used to ensure initial active users
+ // state and requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use {
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+
+ val targetActiveUser = users().current().userHandle()
+ val future = CallbackFuture()
+ assertThrows(SecurityException::class.java) {
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ }
+ assertThat(
+ roleManager.getRoleHoldersAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ targetActiveUser,
+ )
+ )
+ .isEmpty()
+
+ // getActiveUserForRole is used to ensure addRoleHolderAsUser didn't set active user, and
+ // requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use {
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun addRoleHolderAsUserSetsCurrentUserAsActive() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val targetActiveUser = users().current().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun addRoleHolderAsUserSetsWorkProfileAsActive() {
+ // Set other user as active
+ val initialUser = deviceState.initialUser().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+
+ assertThat(targetActiveUser).isNotEqualTo(initialUser)
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun addRoleHolderAsUserReenablesFallbackOnProfileParent() {
+ // Set other user as active
+ val initialUserReference = deviceState.initialUser()
+ val initialUser = initialUserReference.userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val profileParentRoleManager = getRoleManagerForUser(initialUserReference)
+ profileParentRoleManager.setRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, false)
+ assertThat(
+ profileParentRoleManager.isRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isFalse()
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(
+ profileParentRoleManager.isRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isTrue()
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureDoesNotHavePermission(INTERACT_ACROSS_USERS_FULL)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(Exception::class)
+ fun cannotSetDefaultApplicationForProfileExclusiveRoleWithoutInteractAcrossUserPermission() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ // setActiveUserForRole and getActiveUserForRole is used to ensure initial active users
+ // state and requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use {
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+
+ val future = CallbackFuture()
+ assertThrows(SecurityException::class.java) {
+ roleManager.setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ }
+ assertThat(roleManager.getDefaultApplication(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)).isNull()
+
+ // getActiveUserForRole is used to ensure setDefaultApplication didn't set active user,
+ // and requires INTERACT_ACROSS_USERS_FULL
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL).use {
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureHasWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun setDefaultApplicationSetsCurrentUserAsActive() {
+ // Set other user as active
+ val initialUser = deviceState.workProfile().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val targetActiveUser = users().current().userHandle()
+ val future = CallbackFuture()
+ roleManager.setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ eventually { assertExpectedProfileHasRoleUsingGetDefaultApplication(targetActiveUser) }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_DEFAULT_APPLICATIONS)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun setDefaultApplicationSetsWorkProfileAsActive() {
+ // Set other user as active
+ val initialUser = deviceState.initialUser().userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ assertThat(targetActiveUser).isNotEqualTo(initialUser)
+ val future = CallbackFuture()
+ roleManager.setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ eventually { assertExpectedProfileHasRoleUsingGetDefaultApplication(targetActiveUser) }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(
+ INTERACT_ACROSS_USERS_FULL,
+ MANAGE_DEFAULT_APPLICATIONS,
+ MANAGE_ROLE_HOLDERS,
+ )
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun setDefaultApplicationReenablesFallbackOnProfileParent() {
+ // Set other user as active
+ val initialUserReference = deviceState.initialUser()
+ val initialUser = initialUserReference.userHandle()
+ roleManager.setActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, initialUser, 0)
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialUser)
+
+ val profileParentRoleManager = getRoleManagerForUser(initialUserReference)
+ profileParentRoleManager.setRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, false)
+ assertThat(
+ profileParentRoleManager.isRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isFalse()
+
+ val future = CallbackFuture()
+ getRoleManagerForUser(deviceState.workProfile())
+ .setDefaultApplication(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(
+ profileParentRoleManager.isRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isTrue()
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureCanAddUser
+ @EnsureHasNoWorkProfile
+ @RequireRunOnPrimaryUser
+ @EnsureDoesNotHaveUserRestriction(DISALLOW_ADD_MANAGED_PROFILE)
+ @Test
+ @Throws(Exception::class)
+ fun ensureActiveUserSetToParentOnUserRemoved() {
+ users()
+ .createUser()
+ .parent(users().initial())
+ .type(users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME))
+ .createAndStart()
+ .use { userReference ->
+ val targetActiveUser = userReference.userHandle()
+ roleManager.setActiveUserForRole(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ targetActiveUser,
+ 0,
+ )
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+
+ userReference.remove()
+ }
+
+ // Removal of users in roles service might take a moment
+ eventually {
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(users().current().userHandle())
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndSetDefaultAppThenIsDefaultApp() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = users().current().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ }
+
+ if (isWatch) {
+ waitFindObject(
+ By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel))
+ )
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ }
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+
+ pressBack()
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndSetWorkDefaultAppThenIsDefaultApp() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ }
+
+ if (isWatch) {
+ waitFindObject(
+ By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel))
+ )
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ }
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+
+ pressBack()
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndSetDefaultAppThenSetNoneThenHasNoneDefaultApp() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = users().current().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ getUiDevice().waitForIdle()
+ waitFindObject(By.clickable(true).hasDescendant(By.text(NONE_LABEL))).click()
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ getUiDevice().waitForIdle()
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(NONE_LABEL))
+ )
+ .click()
+ }
+
+ if (isWatch) {
+ waitFindObject(By.clickable(true).checked(true).hasDescendant(By.text(NONE_LABEL)))
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(NONE_LABEL))
+ )
+ }
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(deviceState.initialUser().userHandle())
+ assertNoRoleHoldersUsingGetRoleHoldersAsUser()
+
+ pressBack()
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndSetWorkDefaultAppThenSetNoneThenHasNoneDefaultApp() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ getUiDevice().waitForIdle()
+ waitFindObject(By.clickable(true).hasDescendant(By.text(NONE_LABEL))).click()
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ getUiDevice().waitForIdle()
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(NONE_LABEL))
+ )
+ .click()
+ }
+
+ if (isWatch) {
+ waitFindObject(By.clickable(true).checked(true).hasDescendant(By.text(NONE_LABEL)))
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(NONE_LABEL))
+ )
+ }
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(deviceState.initialUser().userHandle())
+ assertNoRoleHoldersUsingGetRoleHoldersAsUser()
+
+ pressBack()
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndSetDefaultAppThenIsDefaultAppInList() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = users().current().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ waitFindObject(
+ By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel))
+ )
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ }
+ pressBack()
+
+ waitFindObject(By.text(targetAppLabel))
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndSetWorkDefaultAppThenIsDefaultAppInList() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ waitFindObject(
+ By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel))
+ )
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ }
+ pressBack()
+
+ waitFindObject(By.text(targetAppLabel))
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndSetDefaultAppThenSetNoneThenIsNoneDefaultAppInList() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = users().current().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ getUiDevice().waitForIdle()
+ waitFindObject(By.clickable(true).hasDescendant(By.text(NONE_LABEL))).click()
+ waitFindObject(By.clickable(true).checked(true).hasDescendant(By.text(NONE_LABEL)))
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ getUiDevice().waitForIdle()
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(NONE_LABEL))
+ )
+ .click()
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(NONE_LABEL))
+ )
+ }
+ pressBack()
+
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL))
+ .hasDescendant(By.text(NONE_LABEL))
+ )
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(deviceState.initialUser().userHandle())
+ assertNoRoleHoldersUsingGetRoleHoldersAsUser()
+
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndSetWorkDefaultAppThenSetNoneThenIsNoneDefaultAppInList() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ getUiDevice().waitForIdle()
+ waitFindObject(By.clickable(true).hasDescendant(By.text(NONE_LABEL))).click()
+ waitFindObject(By.clickable(true).checked(true).hasDescendant(By.text(NONE_LABEL)))
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ getUiDevice().waitForIdle()
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(NONE_LABEL))
+ )
+ .click()
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(NONE_LABEL))
+ )
+ }
+ pressBack()
+
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL))
+ .hasDescendant(By.text(NONE_LABEL))
+ )
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(deviceState.initialUser().userHandle())
+ assertNoRoleHoldersUsingGetRoleHoldersAsUser()
+
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListFromPrimaryUserAndShowsPrimaryIsDefaultAppInList() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ val targetActiveUser = deviceState.initialUser().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ waitFindObject(By.text(targetAppLabel))
+
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListFromPrimaryUserAndShowsWorkIsDefaultAppInList() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ waitFindObject(By.text(targetAppLabel))
+
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListFromWorkProfileAndShowsPrimaryIsDefaultAppInList() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ val targetActiveUser = deviceState.initialUser().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ waitFindObject(By.text(targetAppLabel))
+
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListFromWorkProfileAndShowsWorkIsDefaultAppInList() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ targetActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ waitFindObject(By.text(targetAppLabel))
+
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun requestRoleAndAllowPrimaryThenIsRoleHolder() {
+ try {
+ // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-primary selected first. Request exits early if user and package
+ // already the role holder
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ deviceState.workProfile().userHandle(),
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
+ requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+
+ val targetActiveUser = deviceState.initialUser().userHandle()
+ respondToRoleRequest(true, targetActiveUser)
+
+ // getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
+ // MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
+ // holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+ }
+ } finally {
+ // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun requestRoleAndAllowWorkThenWorkIsRoleHolder() {
+ try {
+ // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-primary selected first. Request exits early if user and package
+ // already the role holder
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ deviceState.workProfile().userHandle(),
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
+ requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ respondToRoleRequest(true, targetActiveUser)
+
+ // getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
+ // MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
+ // holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+ }
+ } finally {
+ // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasWorkProfile
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun requestRoleAndSelectNoneThenIsNoneRoleHolder() {
+ try {
+ // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-primary selected first. Request exits early if user and package
+ // already the role holder
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ deviceState.workProfile().userHandle(),
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
+ requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ respondNoneToRoleRequest()
+
+ // getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
+ // MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
+ // holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(deviceState.initialUser().userHandle())
+ assertNoRoleHoldersUsingGetRoleHoldersAsUser()
+ }
+ } finally {
+ // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun requestRoleFromWorkProfileAndAllowPrimaryThenIsRoleHolder() {
+ try {
+ // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-work selected first. Request exits early if user and package
+ // already the role holder
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ deviceState.initialUser().userHandle(),
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
+ requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+
+ val targetActiveUser = deviceState.initialUser().userHandle()
+ respondToRoleRequest(true, targetActiveUser)
+
+ // getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
+ // MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
+ // holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+ }
+ } finally {
+ // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun requestRoleFromWorkProfileAndAllowWorkThenWorkIsRoleHolder() {
+ try {
+ // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-work selected first. Request exits early if user and package
+ // already the role holder
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ deviceState.initialUser().userHandle(),
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
+ requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+
+ val targetActiveUser = deviceState.workProfile().userHandle()
+ respondToRoleRequest(true, targetActiveUser)
+
+ // getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
+ // MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
+ // holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+ }
+ } finally {
+ // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+ }
+
+ @RequireFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
+ @EnsureHasWorkProfile
+ @RequireRunOnWorkProfile
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun requestRoleFromWorkProfileAndSelectNoneThenIsNoneRoleHolder() {
+ try {
+ // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-work selected first. Request exits early if user and package
+ // already the role holder
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ deviceState.initialUser().userHandle(),
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
+ requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ respondNoneToRoleRequest()
+
+ // getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
+ // MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
+ // holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(deviceState.initialUser().userHandle())
+ assertNoRoleHoldersUsingGetRoleHoldersAsUser()
+ }
+ } finally {
+ // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER)
+ @EnsureHasWorkProfile(isOrganizationOwned = false)
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndOpenDefaultAppWhenBYODHasUserRestrictionOnPrimaryProfile() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ // CollapsingToolbar title can't be found by text, so using description instead.
+ waitFindObject(By.desc(PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL))
+
+ pressBack()
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER)
+ @EnsureHasWorkProfile(isOrganizationOwned = true)
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndCannotOpenDefaultAppWhenHasUserRestrictionOnPrimaryProfile() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ // CollapsingToolbar title can't be found by text, so using description instead.
+ assertNull(
+ waitFindObjectOrNull(
+ By.desc(PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL),
+ IDLE_TIMEOUT_MILLIS,
+ )
+ )
+
+ pressBack()
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = WORK_PROFILE)
+ @EnsureHasWorkProfile(isOrganizationOwned = true)
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppListAndCannotOpenDefaultAppWhenHasUserRestrictionOnWorkProfile() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ context.startActivity(
+ Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+ waitFindObject(By.text(PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL)).click()
+ getUiDevice().waitForIdle()
+
+ // CollapsingToolbar title can't be found by text, so using description instead.
+ assertNull(
+ waitFindObjectOrNull(
+ By.desc(PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL),
+ IDLE_TIMEOUT_MILLIS,
+ )
+ )
+
+ pressBack()
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER)
+ @EnsureHasWorkProfile(isOrganizationOwned = false)
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppDetailsAndSetDefaultAppWhenBYODHasUserRestrictionOnPrimaryProfile() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-target selected first. Request exits early if user and package
+ // already the role holder
+ val initialActiveUser = deviceState.workProfile().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ initialActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+
+ context.startActivity(
+ Intent(Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = users().current().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ waitFindObject(
+ By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel))
+ )
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ }
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+
+ pressBack()
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER)
+ @EnsureHasWorkProfile(isOrganizationOwned = true)
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppDetailsAndCannotSetDefaultAppWhenHasUserRestrictionOnPrimaryProfile() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-target selected first. Request exits early if user and package
+ // already the role holder
+ val initialActiveUser = deviceState.workProfile().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ initialActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+
+ context.startActivity(
+ Intent(Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = users().current().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ }
+
+ if (isWatch) {
+ assertNull(
+ waitFindObjectOrNull(
+ By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)),
+ IDLE_TIMEOUT_MILLIS,
+ )
+ )
+ } else {
+ assertNull(
+ waitFindObjectOrNull(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(targetAppLabel)),
+ IDLE_TIMEOUT_MILLIS,
+ )
+ )
+ }
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(initialActiveUser)
+
+ pressBack()
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS)
+ @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = WORK_PROFILE)
+ @EnsureHasWorkProfile(isOrganizationOwned = true)
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun openDefaultAppDetailsAndCannotSetDefaultAppWhenHasUserRestrictionOnWorkProfile() {
+ try {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-target selected first. Request exits early if user and package
+ // already the role holder
+ val initialActiveUser = deviceState.workProfile().userHandle()
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ initialActiveUser,
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+
+ context.startActivity(
+ Intent(Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ )
+ getUiDevice().waitForIdle()
+
+ val targetActiveUser = users().current().userHandle()
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ if (isWatch) {
+ waitFindObject(By.clickable(true).hasDescendant(By.text(targetAppLabel))).click()
+ } else {
+ waitFindObject(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true))
+ .hasDescendant(By.text(targetAppLabel))
+ )
+ .click()
+ }
+
+ if (isWatch) {
+ assertNull(
+ waitFindObjectOrNull(
+ By.clickable(true).checked(true).hasDescendant(By.text(targetAppLabel)),
+ IDLE_TIMEOUT_MILLIS,
+ )
+ )
+ } else {
+ assertNull(
+ waitFindObjectOrNull(
+ By.clickable(true)
+ .hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(targetAppLabel)),
+ IDLE_TIMEOUT_MILLIS,
+ )
+ )
+ }
+
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(initialActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(initialActiveUser)
+
+ pressBack()
+ pressBack()
+ } finally {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER)
+ @EnsureHasWorkProfile(isOrganizationOwned = false)
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun requestRoleAllowedWhenBYODHasUserRestrictionOnPrimaryProfile() {
+ try {
+ // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-primary selected first. Request exits early if user and package
+ // already the role holder
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ deviceState.workProfile().userHandle(),
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
+ requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+
+ val targetActiveUser = deviceState.initialUser().userHandle()
+ respondToRoleRequest(true, targetActiveUser)
+
+ // getActiveUserForRole and getRoleHoldersAsUser require INTERACT_ACROSS_USERS_FULL and
+ // MANAGE_ROLE_HOLDERS permissions to validate cross user role active user and role
+ // holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ assertThat(roleManager.getActiveUserForRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME))
+ .isEqualTo(targetActiveUser)
+ assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(targetActiveUser)
+ }
+ } finally {
+ // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = INITIAL_USER)
+ @EnsureHasWorkProfile(isOrganizationOwned = true)
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun requestRoleDeniedWhenHasUserRestrictionOnPrimaryProfile() {
+ try {
+ // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-primary selected first. Request exits early if user and package
+ // already the role holder
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ deviceState.workProfile().userHandle(),
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
+ requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ roleRequestNotShown()
+ } finally {
+ // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+ }
+
+ @RequireFlagsEnabled(
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED,
+ com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_UX_BUGFIX_ENABLED,
+ )
+ @EnsureHasUserRestriction(value = DISALLOW_CONFIG_DEFAULT_APPS, onUser = WORK_PROFILE)
+ @EnsureHasWorkProfile(isOrganizationOwned = true)
+ @RequireRunOnPrimaryUser
+ @Test
+ @Throws(java.lang.Exception::class)
+ fun requestRoleDeniedWhenHasUserRestrictionOnWorkProfile() {
+ try {
+ // setDefaultHoldersForTestForAllUsers and setRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ setDefaultHoldersForTestForAllUsers()
+ setRoleVisibleForTestForAllUsers()
+
+ // Ensure non-primary selected first. Request exits early if user and package
+ // already the role holder
+ val future = CallbackFuture()
+ roleManager.addRoleHolderAsUser(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ APP_PACKAGE_NAME,
+ 0,
+ deviceState.workProfile().userHandle(),
+ context.mainExecutor,
+ future,
+ )
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue()
+ }
+
+ requestRole(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ roleRequestNotShown()
+ } finally {
+ // clearDefaultHoldersForTestForAllUsers and clearRoleVisibleForTestForAllUsers require
+ // INTERACT_ACROSS_USERS_FULL and MANAGE_ROLE_HOLDERS permissions to validate cross user
+ // role active user and role holder states
+ permissions().withPermission(INTERACT_ACROSS_USERS_FULL, MANAGE_ROLE_HOLDERS).use {
+ clearDefaultHoldersForTestForAllUsers()
+ clearRoleVisibleForTestForAllUsers()
+ }
+ }
+ }
+
+ private fun installAppForAllUsers() {
+ SystemUtil.runShellCommandOrThrow("pm install -r --user all $APP_APK_PATH")
+ }
+
+ private fun uninstallAppForAllUsers() {
+ SystemUtil.runShellCommand("pm uninstall $APP_PACKAGE_NAME")
+ }
+
+ private fun pressBack() {
+ getUiDevice().pressBack()
+ getUiDevice().waitForIdle()
+ }
+
+ private fun requestRole(roleName: String) {
+ val intent =
+ Intent()
+ .setComponent(ComponentName(APP_PACKAGE_NAME, APP_REQUEST_ROLE_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_ROLE_NAME, roleName)
+ activityRule.getActivity().startActivityToWaitForResult(intent)
+ }
+
+ private fun respondToRoleRequest(allow: Boolean, targetActiveUser: UserHandle) {
+ if (allow) {
+ val targetAppLabel = "$APP_LABEL@${targetActiveUser.identifier}"
+ waitFindObject(By.text(targetAppLabel)).click()
+ }
+ val result: Pair<Int, Intent?> = clickButtonAndWaitForResult(allow)
+ val expectedResult =
+ if (allow && targetActiveUser == users().instrumented().userHandle()) Activity.RESULT_OK
+ else Activity.RESULT_CANCELED
+
+ assertThat(result.first).isEqualTo(expectedResult)
+ }
+
+ private fun respondNoneToRoleRequest() {
+ waitFindObject(By.text(NONE_LABEL)).click()
+ val result: Pair<Int, Intent?> = clickButtonAndWaitForResult(true)
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED)
+ }
+
+ private fun clickButtonAndWaitForResult(positive: Boolean): Pair<Int, Intent?> {
+ waitFindObject(if (positive) POSITIVE_BUTTON_SELECTOR else NEGATIVE_BUTTON_SELECTOR).click()
+ return waitForResult()
+ }
+
+ private fun roleRequestNotShown() {
+ val requestRoleItem =
+ waitFindObjectOrNull(By.textStartsWith(APP_LABEL), IDLE_TIMEOUT_MILLIS)
+ assertNull(requestRoleItem)
+
+ val result: Pair<Int, Intent?> = waitForResult()
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED)
+ }
+
+ @Throws(InterruptedException::class)
+ private fun waitForResult(): Pair<Int, Intent?> {
+ return activityRule.getActivity().waitForActivityResult(TIMEOUT_MILLIS)
+ }
+
+ private fun assertNoRoleHoldersUsingGetRoleHoldersAsUser() {
+ for (userReference in users().profileGroup(deviceState.initialUser())) {
+ val user = userReference.userHandle()
+ // Verify the non-active user does not hold the role
+ assertWithMessage(
+ "Expected user ${user.identifier} to not have a role holder for" +
+ " $PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME"
+ )
+ .that(roleManager.getRoleHoldersAsUser(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, user))
+ .isEmpty()
+ }
+ }
+
+ private fun assertExpectedProfileHasRoleUsingGetRoleHoldersAsUser(
+ expectedActiveUser: UserHandle
+ ) {
+ for (userReference in users().profileGroup(deviceState.initialUser())) {
+ val user = userReference.userHandle()
+ val roleHolders =
+ roleManager.getRoleHoldersAsUser(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, user)
+ if (user == expectedActiveUser) {
+ assertWithMessage(
+ "Expected user ${user.identifier} to have a role holder for " +
+ " $PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME"
+ )
+ .that(roleHolders)
+ .isNotEmpty()
+ assertWithMessage(
+ "Expected user ${user.identifier} to have $APP_PACKAGE_NAME as role " +
+ "holder for $PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME"
+ )
+ .that(roleHolders.first())
+ .isEqualTo(APP_PACKAGE_NAME)
+ } else {
+ // Verify the non-active user does not hold the role
+ assertWithMessage(
+ "Expected user ${user.identifier} to not have a role holder for" +
+ " $PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME"
+ )
+ .that(roleHolders)
+ .isEmpty()
+ }
+ }
+ }
+
+ private fun assertExpectedProfileHasRoleUsingGetDefaultApplication(
+ expectedActiveUser: UserHandle
+ ) {
+ for (userReference in users().profileGroup(deviceState.initialUser())) {
+ val userRoleManager = getRoleManagerForUser(userReference)
+ val user = userReference.userHandle()
+ if (user == expectedActiveUser) {
+ assertWithMessage("Expected default application for user ${user.identifier}")
+ .that(
+ userRoleManager.getDefaultApplication(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isEqualTo(APP_PACKAGE_NAME)
+ } else {
+ // Verify the non-active user does not hold the role
+ assertWithMessage("Expected no default application for user ${user.identifier}")
+ .that(
+ userRoleManager.getDefaultApplication(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME)
+ )
+ .isNull()
+ }
+ }
+ }
+
+ private fun setDefaultHoldersForTestForAllUsers() {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ for (userRoleManager in
+ users().profileGroup(users().current()).map { getRoleManagerForUser(it) }) {
+ userRoleManager.setDefaultHoldersForTest(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ listOf(APP_PACKAGE_NAME),
+ )
+ }
+ }
+
+ private fun clearDefaultHoldersForTestForAllUsers() {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ for (userRoleManager in
+ users().profileGroup(users().current()).map { getRoleManagerForUser(it) }) {
+ userRoleManager.setDefaultHoldersForTest(
+ PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME,
+ emptyList(),
+ )
+ }
+ }
+
+ private fun setRoleVisibleForTestForAllUsers() {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ for (userRoleManager in
+ users().profileGroup(users().current()).map { getRoleManagerForUser(it) }) {
+ userRoleManager.setRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, true)
+ }
+ }
+
+ private fun clearRoleVisibleForTestForAllUsers() {
+ // Set test default role holder. Ensures fallbacks to a default holder
+ for (userRoleManager in
+ users().profileGroup(users().current()).map { getRoleManagerForUser(it) }) {
+ userRoleManager.setRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, false)
+ }
+ }
+
+ private fun setRoleFallbackEnabledForAllUsers() {
+ for (userReference in users().profileGroup(users().current())) {
+ try {
+ val userRoleManager = getRoleManagerForUser(userReference)
+ userRoleManager.setRoleFallbackEnabled(PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME, true)
+ } catch (e: Exception) {
+ Log.w(
+ LOG_TAG,
+ "Encountered error setting fallback enabled for" +
+ " $PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME@" +
+ "${userReference.userHandle().identifier}",
+ e,
+ )
+ }
+ }
+ }
+
+ private fun getRoleManagerForUser(user: UserReference): RoleManager {
+ val userContext = context().androidContextAsUser(user)
+ return userContext.getSystemService(RoleManager::class.java)
+ }
+
+ class CallbackFuture : CompletableFuture<Boolean?>(), Consumer<Boolean?> {
+ override fun accept(successful: Boolean?) {
+ complete(successful)
+ }
+ }
+
+ companion object {
+ private val LOG_TAG = RoleManagerMultiUserTest::class.java.simpleName
+
+ private const val TIMEOUT_MILLIS: Long = (15 * 1000).toLong()
+ private const val IDLE_TIMEOUT_MILLIS: Long = (2 * 1000).toLong()
+ private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_NAME =
+ RoleManager.ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY
+ private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_LABEL =
+ "Default test profile group exclusive role app"
+ private const val PROFILE_GROUP_EXCLUSIVITY_ROLE_SHORT_LABEL =
+ "Test profile group exclusive role app"
+ private const val PRIVATE_PROFILE_TYPE_NAME = "android.os.usertype.profile.PRIVATE"
+ private const val APP_APK_PATH = "/data/local/tmp/cts-role/CtsRoleTestApp.apk"
+ private const val APP_PACKAGE_NAME = "android.app.role.cts.app"
+ private const val APP_LABEL = "CtsRoleTestApp"
+ private const val APP_REQUEST_ROLE_ACTIVITY_NAME = APP_PACKAGE_NAME + ".RequestRoleActivity"
+ private const val NONE_LABEL = "None"
+
+ private val context: Context = context().instrumentedContext()
+ private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java)
+ private val packageManager: PackageManager = context.packageManager
+ private val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+
+ private val NEGATIVE_BUTTON_SELECTOR =
+ if (isWatch) By.text("Cancel") else By.res("android:id/button2")
+ private val POSITIVE_BUTTON_SELECTOR =
+ if (isWatch) By.text("Set as default") else By.res("android:id/button1")
+
+ @JvmField @ClassRule @Rule val deviceState = DeviceState()
+
+ @JvmField
+ @ClassRule
+ @Rule
+ var disableAnimationRule: DisableAnimationRule = DisableAnimationRule()
+
+ @JvmField @ClassRule @Rule var freezeRotationRule: FreezeRotationRule = FreezeRotationRule()
+ }
+}
diff --git a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/WaitForResultActivity.kt b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/WaitForResultActivity.kt
new file mode 100644
index 000000000..84590ce91
--- /dev/null
+++ b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/WaitForResultActivity.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.rolemultiuser.cts
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.util.Pair
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+/** An Activity that can start another Activity and wait for its result. */
+class WaitForResultActivity : Activity() {
+ private var mLatch: CountDownLatch? = null
+ private var mResultCode = 0
+ private var mData: Intent? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ if (savedInstanceState != null) {
+ throw RuntimeException(
+ ("Activity was recreated (perhaps due to a configuration change?) " +
+ "and this activity doesn't currently know how to gracefully handle " +
+ "configuration changes.")
+ )
+ }
+ }
+
+ fun startActivityToWaitForResult(intent: Intent) {
+ mLatch = CountDownLatch(1)
+ startActivityForResult(intent, REQUEST_CODE_WAIT_FOR_RESULT)
+ }
+
+ @Throws(InterruptedException::class)
+ fun waitForActivityResult(timeoutMillis: Long): Pair<Int, Intent?> {
+ mLatch!!.await(timeoutMillis, TimeUnit.MILLISECONDS)
+ return Pair(mResultCode, mData)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == REQUEST_CODE_WAIT_FOR_RESULT) {
+ mResultCode = resultCode
+ mData = data
+ mLatch!!.countDown()
+ } else {
+ super.onActivityResult(requestCode, resultCode, data)
+ }
+ }
+
+ companion object {
+ private const val REQUEST_CODE_WAIT_FOR_RESULT = 1
+ }
+}
diff --git a/tests/cts/safetycenter/AndroidTest.xml b/tests/cts/safetycenter/AndroidTest.xml
index 6d8c3069c..ed161f0b6 100644
--- a/tests/cts/safetycenter/AndroidTest.xml
+++ b/tests/cts/safetycenter/AndroidTest.xml
@@ -47,6 +47,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
<option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
index c344d7ebd..1320c2ff9 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
@@ -492,8 +492,8 @@ class SafetyCenterDataTest {
status1,
listOf(issue1),
listOf(entryOrGroup1),
- listOf(staticEntryGroup1)
- )
+ listOf(staticEntryGroup1),
+ ),
)
.addEqualityGroup(
data2,
@@ -501,19 +501,19 @@ class SafetyCenterDataTest {
status2,
listOf(issue2),
listOf(entryOrGroup2),
- listOf(staticEntryGroup2)
- )
+ listOf(staticEntryGroup2),
+ ),
)
.addEqualityGroup(
SafetyCenterData(status1, listOf(), listOf(), listOf()),
- SafetyCenterData(status1, listOf(), listOf(), listOf())
+ SafetyCenterData(status1, listOf(), listOf(), listOf()),
)
.addEqualityGroup(
SafetyCenterData(
status2,
listOf(issue1),
listOf(entryOrGroup1),
- listOf(staticEntryGroup1)
+ listOf(staticEntryGroup1),
)
)
.addEqualityGroup(
@@ -521,7 +521,7 @@ class SafetyCenterDataTest {
status1,
listOf(issue2),
listOf(entryOrGroup1),
- listOf(staticEntryGroup1)
+ listOf(staticEntryGroup1),
)
)
.addEqualityGroup(
@@ -529,7 +529,7 @@ class SafetyCenterDataTest {
status1,
listOf(issue1),
listOf(entryOrGroup2),
- listOf(staticEntryGroup1)
+ listOf(staticEntryGroup1),
)
)
.addEqualityGroup(
@@ -537,7 +537,7 @@ class SafetyCenterDataTest {
status1,
listOf(issue1),
listOf(entryOrGroup1),
- listOf(staticEntryGroup2)
+ listOf(staticEntryGroup2),
)
)
@@ -550,7 +550,7 @@ class SafetyCenterDataTest {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetyCenterData.CREATOR,
ignoreToString = true,
- createCopy = { SafetyCenterData.Builder(it).build() }
+ createCopy = { SafetyCenterData.Builder(it).build() },
)
.addEqualityGroup(
data1,
@@ -558,7 +558,7 @@ class SafetyCenterDataTest {
status1,
listOf(issue1),
listOf(entryOrGroup1),
- listOf(staticEntryGroup1)
+ listOf(staticEntryGroup1),
),
SafetyCenterData.Builder(status1)
.addIssue(issue1)
@@ -570,7 +570,7 @@ class SafetyCenterDataTest {
.addEntryOrGroup(entryOrGroup1)
.addStaticEntryGroup(staticEntryGroup1)
.setExtras(unknownExtras)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetyCenterData.Builder(status1)
@@ -640,7 +640,7 @@ class SafetyCenterDataTest {
.addStaticEntryGroup(staticEntryGroup1)
.addIssue(issue1)
.setExtras(filledExtrasIssuesToGroups1)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetyCenterData.Builder(status1)
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt
index 7ae5fb347..38ed449f7 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt
@@ -175,7 +175,7 @@ class SafetyCenterEntryGroupTest {
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetyCenterEntryGroup.CREATOR,
- createCopy = { SafetyCenterEntryGroup.Builder(it).build() }
+ createCopy = { SafetyCenterEntryGroup.Builder(it).build() },
)
.addEqualityGroup(
entryGroup1,
@@ -183,7 +183,7 @@ class SafetyCenterEntryGroupTest {
.setSummary("A group summary")
.setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
.setEntries(listOf(entry1))
- .build()
+ .build(),
)
.addEqualityGroup(entryGroup2)
.addEqualityGroup(
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt
index 116164288..2811b87f2 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt
@@ -41,18 +41,18 @@ class SafetyCenterEntryTest {
context,
0,
Intent("Fake Different Data"),
- PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_IMMUTABLE,
)
private val iconAction1 =
SafetyCenterEntry.IconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR,
- pendingIntent1
+ pendingIntent1,
)
private val iconAction2 =
SafetyCenterEntry.IconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO,
- pendingIntent2
+ pendingIntent2,
)
private val entry1 =
@@ -213,7 +213,7 @@ class SafetyCenterEntryTest {
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetyCenterEntry.CREATOR,
- createCopy = { SafetyCenterEntry.Builder(it).build() }
+ createCopy = { SafetyCenterEntry.Builder(it).build() },
)
.addEqualityGroup(entry1)
.addEqualityGroup(
@@ -226,7 +226,7 @@ class SafetyCenterEntryTest {
.setPendingIntent(pendingIntent1)
.setIconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO,
- pendingIntent2
+ pendingIntent2,
)
.build(),
SafetyCenterEntry.Builder("id", "a title")
@@ -238,9 +238,9 @@ class SafetyCenterEntryTest {
.setPendingIntent(pendingIntent1)
.setIconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO,
- pendingIntent2
+ pendingIntent2,
)
- .build()
+ .build(),
)
.addEqualityGroup(SafetyCenterEntry.Builder(entry1).setId("a different id").build())
.addEqualityGroup(
@@ -274,7 +274,7 @@ class SafetyCenterEntryTest {
assertThat(
SafetyCenterEntry.IconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR,
- pendingIntent1
+ pendingIntent1,
)
.type
)
@@ -282,7 +282,7 @@ class SafetyCenterEntryTest {
assertThat(
SafetyCenterEntry.IconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO,
- pendingIntent1
+ pendingIntent1,
)
.type
)
@@ -294,7 +294,7 @@ class SafetyCenterEntryTest {
assertThat(
SafetyCenterEntry.IconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR,
- pendingIntent1
+ pendingIntent1,
)
.pendingIntent
)
@@ -302,7 +302,7 @@ class SafetyCenterEntryTest {
assertThat(
SafetyCenterEntry.IconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR,
- pendingIntent2
+ pendingIntent2,
)
.pendingIntent
)
@@ -340,26 +340,26 @@ class SafetyCenterEntryTest {
iconAction1,
SafetyCenterEntry.IconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR,
- pendingIntent1
- )
+ pendingIntent1,
+ ),
)
.addEqualityGroup(
iconAction2,
SafetyCenterEntry.IconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO,
- pendingIntent2
- )
+ pendingIntent2,
+ ),
)
.addEqualityGroup(
SafetyCenterEntry.IconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO,
- pendingIntent1
+ pendingIntent1,
)
)
.addEqualityGroup(
SafetyCenterEntry.IconAction(
SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR,
- pendingIntent2
+ pendingIntent2,
)
)
.test()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt
index 0d97026bd..e7565bf61 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt
@@ -58,7 +58,7 @@ class SafetyCenterErrorDetailsTest {
.addEqualityGroup(errorDetails2, SafetyCenterErrorDetails("another error message"))
.addEqualityGroup(
SafetyCenterErrorDetails("a different error message"),
- SafetyCenterErrorDetails("a different error message")
+ SafetyCenterErrorDetails("a different error message"),
)
.test()
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
index be7ca343c..fd359f600 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
@@ -47,7 +47,7 @@ class SafetyCenterIssueTest {
context,
0,
Intent("Fake Different Data"),
- PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_IMMUTABLE,
)
private val action1 =
@@ -451,7 +451,7 @@ class SafetyCenterIssueTest {
.setIsInFlight(true)
.setSuccessMessage("a success message")
.setConfirmationDialogDetails(confirmationDialogDetails)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
@@ -534,7 +534,7 @@ class SafetyCenterIssueTest {
.build(),
SafetyCenterIssue.Builder(issueWithTiramisuFields)
.setAttributionTitle("Attribution title")
- .build()
+ .build(),
)
.addEqualityGroup(
SafetyCenterIssue.Builder(issueWithTiramisuFields)
@@ -549,7 +549,7 @@ class SafetyCenterIssueTest {
)
.addEqualityGroup(
SafetyCenterIssue.Builder(issueWithTiramisuFields).setGroupId("group_id").build(),
- SafetyCenterIssue.Builder(issueWithTiramisuFields).setGroupId("group_id").build()
+ SafetyCenterIssue.Builder(issueWithTiramisuFields).setGroupId("group_id").build(),
)
.addEqualityGroup(
SafetyCenterIssue.Builder(issueWithTiramisuFields)
@@ -625,7 +625,7 @@ class SafetyCenterIssueTest {
)
.addEqualityGroup(
ConfirmationDialogDetails("Title", "Text", "Accept", "Deny"),
- ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny"),
)
.addEqualityGroup(ConfirmationDialogDetails("Other title", "Text", "Accept", "Deny"))
.addEqualityGroup(ConfirmationDialogDetails("Title", "Other text", "Accept", "Deny"))
@@ -643,7 +643,7 @@ class SafetyCenterIssueTest {
) =
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetyCenterIssue.CREATOR,
- createCopy = createCopyFromBuilder
+ createCopy = createCopyFromBuilder,
)
.addEqualityGroup(issue1, SafetyCenterIssue.Builder(issue1).build())
.addEqualityGroup(issueWithRequiredFieldsOnly)
@@ -657,7 +657,7 @@ class SafetyCenterIssueTest {
.setSubtitle("In the neighborhood")
.setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
.setActions(listOf(action1))
- .build()
+ .build(),
)
.addEqualityGroup(SafetyCenterIssue.Builder(issue1).setId("a different id").build())
.addEqualityGroup(
@@ -685,7 +685,7 @@ class SafetyCenterIssueTest {
) =
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetyCenterIssue.Action.CREATOR,
- createCopy = createCopyFromBuilder
+ createCopy = createCopyFromBuilder,
)
.addEqualityGroup(action1)
.addEqualityGroup(action2)
@@ -699,7 +699,7 @@ class SafetyCenterIssueTest {
.setWillResolve(true)
.setIsInFlight(true)
.setSuccessMessage("a success message")
- .build()
+ .build(),
)
.addEqualityGroup(
SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
index 8fd45efb8..b1c731f08 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
@@ -216,7 +216,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterTestHelper.setData(
DYNAMIC_OTHER_PACKAGE_ID,
- safetySourceTestData.unspecified
+ safetySourceTestData.unspecified,
)
}
@@ -281,7 +281,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterTestHelper.setData(
DYNAMIC_IN_STATELESS_ID,
- safetySourceTestData.information
+ safetySourceTestData.information,
)
}
@@ -342,7 +342,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterTestHelper.setData(
ISSUE_ONLY_BAREBONE_ID,
- safetySourceTestData.unspecified
+ safetySourceTestData.unspecified,
)
}
@@ -359,7 +359,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterTestHelper.setData(
DYNAMIC_BAREBONE_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue),
)
}
@@ -400,7 +400,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.informationWithIssue
+ safetySourceTestData.informationWithIssue,
)
}
@@ -421,7 +421,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
}
@@ -454,7 +454,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterTestHelper.setData(
DYNAMIC_ALL_OPTIONAL_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
}
@@ -490,7 +490,7 @@ class SafetyCenterManagerTest {
ISSUE_ONLY_ALL_OPTIONAL_ID,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalResolvingGeneralIssue
- )
+ ),
)
}
@@ -521,7 +521,7 @@ class SafetyCenterManagerTest {
SafetyCenterFlags.issueCategoryAllowlists =
mapOf(
ISSUE_CATEGORY_DEVICE to setOf(SAMPLE_SOURCE_ID),
- ISSUE_CATEGORY_GENERAL to setOf(SAMPLE_SOURCE_ID)
+ ISSUE_CATEGORY_GENERAL to setOf(SAMPLE_SOURCE_ID),
)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -539,7 +539,7 @@ class SafetyCenterManagerTest {
mapOf(
ISSUE_CATEGORY_ACCOUNT to setOf(SINGLE_SOURCE_ID, SAMPLE_SOURCE_ID),
ISSUE_CATEGORY_DEVICE to setOf(SAMPLE_SOURCE_ID),
- ISSUE_CATEGORY_GENERAL to setOf(SAMPLE_SOURCE_ID)
+ ISSUE_CATEGORY_GENERAL to setOf(SAMPLE_SOURCE_ID),
)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -560,7 +560,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.recommendationWithAccountIssue
+ safetySourceTestData.recommendationWithAccountIssue,
)
}
@@ -577,7 +577,7 @@ class SafetyCenterManagerTest {
SafetyCenterFlags.issueCategoryAllowlists =
mapOf(
ISSUE_CATEGORY_ACCOUNT to setOf(SAMPLE_SOURCE_ID),
- ISSUE_CATEGORY_DEVICE to setOf(SINGLE_SOURCE_ID)
+ ISSUE_CATEGORY_DEVICE to setOf(SINGLE_SOURCE_ID),
)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -585,7 +585,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.recommendationWithAccountIssue
+ safetySourceTestData.recommendationWithAccountIssue,
)
}
@@ -605,7 +605,7 @@ class SafetyCenterManagerTest {
safetyCenterManager.setSafetySourceDataWithPermission(
SINGLE_SOURCE_ID,
safetySourceTestData.unspecified,
- EVENT_SOURCE_STATE_CHANGED
+ EVENT_SOURCE_STATE_CHANGED,
)
safetyCenterTestHelper.setEnabled(true)
@@ -620,7 +620,7 @@ class SafetyCenterManagerTest {
safetyCenterManager.setSafetySourceData(
SINGLE_SOURCE_ID,
safetySourceTestData.unspecified,
- EVENT_SOURCE_STATE_CHANGED
+ EVENT_SOURCE_STATE_CHANGED,
)
}
}
@@ -702,7 +702,7 @@ class SafetyCenterManagerTest {
safetyCenterManager.reportSafetySourceErrorWithPermission(
SINGLE_SOURCE_ID,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
listener.receiveSafetyCenterData()
@@ -717,7 +717,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterManager.reportSafetySourceErrorWithPermission(
SINGLE_SOURCE_ID,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
}
@@ -732,7 +732,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterManager.reportSafetySourceErrorWithPermission(
STATIC_BAREBONE_ID,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
}
@@ -749,7 +749,7 @@ class SafetyCenterManagerTest {
assertFailsWith(IllegalArgumentException::class) {
safetyCenterManager.reportSafetySourceErrorWithPermission(
DYNAMIC_OTHER_PACKAGE_ID,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
}
@@ -769,7 +769,7 @@ class SafetyCenterManagerTest {
safetyCenterManager.reportSafetySourceErrorWithPermission(
SINGLE_SOURCE_ID,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
assertFailsWith(TimeoutCancellationException::class) {
@@ -782,7 +782,7 @@ class SafetyCenterManagerTest {
assertFailsWith(SecurityException::class) {
safetyCenterManager.reportSafetySourceError(
SINGLE_SOURCE_ID,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
}
}
@@ -791,7 +791,7 @@ class SafetyCenterManagerTest {
fun safetyCenterEnabledChanged_whenImplicitReceiverHasPermission_receiverCalled() {
assumeTrue(
"Cannot toggle SafetyCenter using DeviceConfig",
- SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig()
+ SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig(),
)
// Implicit broadcast is only sent to system user.
assumeTrue(context.getSystemService(UserManager::class.java)!!.isSystemUser)
@@ -813,7 +813,7 @@ class SafetyCenterManagerTest {
fun safetyCenterEnabledChanged_whenImplicitReceiverDoesntHavePermission_receiverNotCalled() {
assumeTrue(
"Cannot toggle SafetyCenter using DeviceConfig",
- SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig()
+ SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig(),
)
// Implicit broadcast is only sent to system user.
assumeTrue(context.getSystemService(UserManager::class.java)!!.isSystemUser)
@@ -829,7 +829,7 @@ class SafetyCenterManagerTest {
fun safetyCenterEnabledChanged_whenSourceReceiverHasPermission_receiverCalled() {
assumeTrue(
"Cannot toggle SafetyCenter using DeviceConfig",
- SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig()
+ SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig(),
)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -848,14 +848,14 @@ class SafetyCenterManagerTest {
fun safetyCenterEnabledChanged_valueDoesntChange_receiverNotCalled() {
assumeTrue(
"Cannot toggle SafetyCenter using DeviceConfig",
- SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig()
+ SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig(),
)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
assertFailsWith(TimeoutCancellationException::class) {
SafetySourceReceiver.setSafetyCenterEnabledWithReceiverPermissionAndWait(
true,
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
}
@@ -864,7 +864,7 @@ class SafetyCenterManagerTest {
fun safetyCenterEnabledChanged_whenSourceReceiverDoesntHavePermission_receiverNotCalled() {
assumeTrue(
"Cannot toggle SafetyCenter using DeviceConfig",
- SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig()
+ SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig(),
)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -877,12 +877,12 @@ class SafetyCenterManagerTest {
fun safetyCenterEnabledChanged_whenSourceReceiverNotInConfig_receiverNotCalled() {
assumeTrue(
"Cannot toggle SafetyCenter using DeviceConfig",
- SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig()
+ SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig(),
)
assertFailsWith(TimeoutCancellationException::class) {
SafetySourceReceiver.setSafetyCenterEnabledWithReceiverPermissionAndWait(
false,
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
}
@@ -891,14 +891,14 @@ class SafetyCenterManagerTest {
fun safetyCenterEnabledChanged_whenNoDeviceConfigFlag_receiverNotCalled() {
assumeFalse(
"SafetyCenter DeviceConfig flag is in use",
- SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig()
+ SafetyCenterTestHelper.safetyCenterCanBeToggledUsingDeviceConfig(),
)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
assertFailsWith(TimeoutCancellationException::class) {
SafetySourceReceiver.setSafetyCenterEnabledWithReceiverPermissionAndWait(
false,
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
}
@@ -908,7 +908,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Rescan(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_RESCAN_BUTTON_CLICK
@@ -925,7 +925,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -943,7 +943,7 @@ class SafetyCenterManagerTest {
SafetySourceReceiver.runInForegroundService = true
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -961,13 +961,13 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.informationWithIssue)
+ Response.SetData(safetySourceTestData.informationWithIssue),
)
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- timeout = TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT,
)
}
@@ -982,7 +982,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.informationWithIssue)
+ Response.SetData(safetySourceTestData.informationWithIssue),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -1015,11 +1015,11 @@ class SafetyCenterManagerTest {
SafetySourceReceiver.apply {
setResponse(
Request.Rescan(SOURCE_ID_1),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
setResponse(
Request.Rescan(SOURCE_ID_3),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
}
@@ -1047,11 +1047,11 @@ class SafetyCenterManagerTest {
SafetySourceReceiver.apply {
setResponse(
Request.Refresh(SOURCE_ID_1),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
setResponse(
Request.Refresh(SOURCE_ID_3),
- Response.SetData(safetySourceTestData.informationWithIssue)
+ Response.SetData(safetySourceTestData.informationWithIssue),
)
}
@@ -1077,7 +1077,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Rescan(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
assertFailsWith(TimeoutCancellationException::class) {
@@ -1094,13 +1094,13 @@ class SafetyCenterManagerTest {
fun refreshSafetySources_whenSourceNotInConfig_sourceDoesntSendData() {
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- timeout = TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT,
)
}
}
@@ -1122,7 +1122,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Rescan(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
val broadcastId1 =
@@ -1143,7 +1143,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Rescan(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information, overrideBroadcastId = "invalid")
+ Response.SetData(safetySourceTestData.information, overrideBroadcastId = "invalid"),
)
val listener = safetyCenterTestHelper.addListener()
@@ -1157,7 +1157,7 @@ class SafetyCenterManagerTest {
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN
@@ -1172,14 +1172,14 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN
)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -1201,7 +1201,7 @@ class SafetyCenterManagerTest {
)
SafetySourceReceiver.setResponse(
Request.Rescan(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -1230,7 +1230,7 @@ class SafetyCenterManagerTest {
SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_LONG)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -1249,7 +1249,7 @@ class SafetyCenterManagerTest {
)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -1269,7 +1269,7 @@ class SafetyCenterManagerTest {
for (sourceId in listOf(SOURCE_ID_2, SOURCE_ID_3)) {
SafetySourceReceiver.setResponse(
Request.Rescan(sourceId),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
}
val listener = safetyCenterTestHelper.addListener()
@@ -1289,7 +1289,7 @@ class SafetyCenterManagerTest {
for (sourceId in listOf(SOURCE_ID_2, SOURCE_ID_3)) {
SafetySourceReceiver.setResponse(
Request.Rescan(sourceId),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
}
val listener = safetyCenterTestHelper.addListener()
@@ -1308,7 +1308,7 @@ class SafetyCenterManagerTest {
// SOURCE_ID_1 and SOURCE_ID_2 will timeout
SafetySourceReceiver.setResponse(
Request.Rescan(SOURCE_ID_3),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
val listener = safetyCenterTestHelper.addListener()
@@ -1355,7 +1355,7 @@ class SafetyCenterManagerTest {
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- timeout = TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT,
)
}
}
@@ -1377,15 +1377,15 @@ class SafetyCenterManagerTest {
SafetySourceReceiver.apply {
setResponse(
Request.Refresh(SOURCE_ID_1),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
setResponse(
Request.Refresh(SOURCE_ID_2),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
setResponse(
Request.Refresh(SOURCE_ID_3),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
}
// But sources 1 and 3 should not be refreshed in background
@@ -1409,7 +1409,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
SafetyCenterFlags.backgroundRefreshDeniedSources = setOf(SINGLE_SOURCE_ID)
@@ -1426,7 +1426,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Rescan(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
SafetyCenterFlags.backgroundRefreshDeniedSources = setOf(SINGLE_SOURCE_ID)
@@ -1444,14 +1444,14 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
SafetyCenterFlags.backgroundRefreshDeniedSources = setOf(SINGLE_SOURCE_ID)
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PERIODIC,
- timeout = TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT,
)
}
@@ -1466,7 +1466,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -1484,21 +1484,21 @@ class SafetyCenterManagerTest {
SafetySourceReceiver.apply {
setResponse(
Request.Refresh(SOURCE_ID_1),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
setResponse(
Request.Refresh(SOURCE_ID_2),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
setResponse(
Request.Refresh(SOURCE_ID_3),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
}
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- safetySourceIds = listOf(SOURCE_ID_1, SOURCE_ID_2)
+ safetySourceIds = listOf(SOURCE_ID_1, SOURCE_ID_2),
)
val apiSafetySourceData1 =
@@ -1518,7 +1518,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
assertFailsWith(TimeoutCancellationException::class) {
@@ -1541,7 +1541,7 @@ class SafetyCenterManagerTest {
assertFails {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- safetySourceIds = listOf(SOURCE_ID_1, SOURCE_ID_3)
+ safetySourceIds = listOf(SOURCE_ID_1, SOURCE_ID_3),
)
}
}
@@ -1647,7 +1647,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val safetyCenterDataFromListener = listener.receiveSafetyCenterData()
@@ -1708,7 +1708,7 @@ class SafetyCenterManagerTest {
}
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
directExecutor(),
- oneShotListener
+ oneShotListener,
)
// Check that we don't deadlock when using a one-shot listener. This is because adding the
@@ -1725,7 +1725,7 @@ class SafetyCenterManagerTest {
val listener = SafetyCenterTestListener()
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
directExecutor(),
- listener
+ listener,
)
assertFailsWith(TimeoutCancellationException::class) {
@@ -1764,7 +1764,7 @@ class SafetyCenterManagerTest {
val listener = SafetyCenterTestListener()
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
fakeExecutor,
- listener
+ listener,
)
fakeExecutor.getNextTask().run()
listener.receiveSafetyCenterData()
@@ -1812,7 +1812,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.recommendationDismissPendingIntentIssue
+ safetySourceTestData.recommendationDismissPendingIntentIssue,
)
val apiSafetySourceDataBeforeDismissal =
safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
@@ -1822,7 +1822,7 @@ class SafetyCenterManagerTest {
)
SafetySourceReceiver.setResponse(
Request.DismissIssue(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.dismissSafetyCenterIssueWithPermissionAndWait(
@@ -1839,7 +1839,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
@@ -1857,7 +1857,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SINGLE_SOURCE_ID, CRITICAL_ISSUE_ID)
@@ -1878,7 +1878,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SINGLE_SOURCE_ID, CRITICAL_ISSUE_ID)
@@ -1889,7 +1889,7 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- issueTypeId = "some_other_issue_type_id"
+ issueTypeId = "some_other_issue_type_id",
)
)
@@ -1903,7 +1903,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
safetyCenterTestHelper.setEnabled(false)
@@ -1931,7 +1931,7 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- userId = USER_NULL
+ userId = USER_NULL,
)
)
}
@@ -1949,7 +1949,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val apiSafetySourceDataBeforeExecution =
safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
@@ -1959,7 +1959,7 @@ class SafetyCenterManagerTest {
)
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.executeSafetyCenterIssueActionWithPermissionAndWait(
@@ -1967,8 +1967,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
val apiSafetySourceDataAfterExecution =
@@ -1989,8 +1989,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
val error = listener.receiveSafetyCenterErrorDetails()
@@ -2015,8 +2015,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
val error = listener.receiveSafetyCenterErrorDetails()
@@ -2043,7 +2043,7 @@ class SafetyCenterManagerTest {
}
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
fakeExecutor,
- listener
+ listener,
)
fakeExecutor.getNextTask().run()
@@ -2052,8 +2052,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
fakeExecutor.getNextTask().run()
@@ -2072,9 +2072,9 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
+ CRITICAL_ISSUE_ACTION_ID,
),
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
assertFailsWith(TimeoutCancellationException::class) {
@@ -2087,7 +2087,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
safetyCenterManager.executeSafetyCenterIssueActionWithPermissionAndWait(
@@ -2095,8 +2095,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
listener.receiveSafetyCenterData()
@@ -2106,9 +2106,9 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
+ CRITICAL_ISSUE_ACTION_ID,
),
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
assertFailsWith(TimeoutCancellationException::class) {
@@ -2121,7 +2121,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
safetyCenterTestHelper.setEnabled(false)
@@ -2132,9 +2132,9 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
+ CRITICAL_ISSUE_ACTION_ID,
),
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
assertFailsWith(TimeoutCancellationException::class) {
@@ -2154,12 +2154,12 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
assertFailsWith(IllegalArgumentException::class) {
@@ -2168,9 +2168,9 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID + "invalid",
- CRITICAL_ISSUE_ACTION_ID
+ CRITICAL_ISSUE_ACTION_ID,
),
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
@@ -2184,12 +2184,12 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
assertFailsWith(TimeoutCancellationException::class) {
@@ -2198,9 +2198,9 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID + "invalid"
+ CRITICAL_ISSUE_ACTION_ID + "invalid",
),
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
@@ -2217,8 +2217,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SOURCE_ID_1,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
}
}
@@ -2230,14 +2230,14 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- userId = USER_NULL
+ userId = USER_NULL,
),
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
CRITICAL_ISSUE_ACTION_ID,
- userId = USER_NULL
- )
+ userId = USER_NULL,
+ ),
)
}
}
@@ -2255,7 +2255,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(SOURCE_ID_1, safetySourceTestData.unspecified)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterManager.clearAllSafetySourceDataForTestsWithPermission()
@@ -2271,7 +2271,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setEnabled(false)
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryGroupTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryGroupTest.kt
index 71be3d5eb..0709bfc83 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryGroupTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryGroupTest.kt
@@ -104,11 +104,11 @@ class SafetyCenterStaticEntryGroupTest {
)
.addEqualityGroup(
staticEntryGroup,
- SafetyCenterStaticEntryGroup("a title", listOf(staticEntry1, staticEntry2))
+ SafetyCenterStaticEntryGroup("a title", listOf(staticEntry1, staticEntry2)),
)
.addEqualityGroup(
SafetyCenterStaticEntryGroup("a title", listOf(staticEntry1)),
- SafetyCenterStaticEntryGroup("a title", listOf(staticEntry1))
+ SafetyCenterStaticEntryGroup("a title", listOf(staticEntry1)),
)
.addEqualityGroup(
SafetyCenterStaticEntryGroup("a different title", listOf(staticEntry1))
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryTest.kt
index 045a50f75..7a5e6a737 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryTest.kt
@@ -40,7 +40,7 @@ class SafetyCenterStaticEntryTest {
context,
0,
Intent("Fake Different Data"),
- PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_IMMUTABLE,
)
private val title1 = "a title"
@@ -101,14 +101,14 @@ class SafetyCenterStaticEntryTest {
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetyCenterStaticEntry.CREATOR,
- createCopy = { SafetyCenterStaticEntry.Builder(it).build() }
+ createCopy = { SafetyCenterStaticEntry.Builder(it).build() },
)
.addEqualityGroup(
staticEntry1,
SafetyCenterStaticEntry.Builder("a title")
.setSummary("a summary")
.setPendingIntent(pendingIntent1)
- .build()
+ .build(),
)
.addEqualityGroup(staticEntry2)
.addEqualityGroup(staticEntryMinimal, SafetyCenterStaticEntry.Builder("").build())
@@ -120,7 +120,7 @@ class SafetyCenterStaticEntryTest {
SafetyCenterStaticEntry.Builder("titlee")
.setSummary("sumaree")
.setPendingIntent(pendingIntent1)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetyCenterStaticEntry.Builder(staticEntry1).setTitle("a different title").build()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt
index 4df4e5d35..561e34d3b 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt
@@ -156,7 +156,7 @@ class SafetyCenterStatusTest {
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetyCenterStatus.CREATOR,
- createCopy = { SafetyCenterStatus.Builder(it).build() }
+ createCopy = { SafetyCenterStatus.Builder(it).build() },
)
.addEqualityGroup(
baseStatus,
@@ -164,7 +164,7 @@ class SafetyCenterStatusTest {
.setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
.setRefreshStatus(SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS)
.build(),
- SafetyCenterStatus.Builder(baseStatus).build()
+ SafetyCenterStatus.Builder(baseStatus).build(),
)
.addEqualityGroup(
SafetyCenterStatus.Builder("same title", "same summary")
@@ -172,7 +172,7 @@ class SafetyCenterStatusTest {
.build(),
SafetyCenterStatus.Builder("same title", "same summary")
.setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetyCenterStatus.Builder(baseStatus).setTitle("that's not it").build()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt
index d882fc3cb..3beced151 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterUnsupportedTest.kt
@@ -119,7 +119,7 @@ class SafetyCenterUnsupportedTest {
safetyCenterManager.setSafetySourceDataWithPermission(
SINGLE_SOURCE_ID,
safetySourceTestData.criticalWithResolvingGeneralIssue,
- EVENT_SOURCE_STATE_CHANGED
+ EVENT_SOURCE_STATE_CHANGED,
)
val apiSafetySourceData =
@@ -134,7 +134,7 @@ class SafetyCenterUnsupportedTest {
safetyCenterManager.setSafetySourceData(
SINGLE_SOURCE_ID,
safetySourceTestData.unspecified,
- EVENT_SOURCE_STATE_CHANGED
+ EVENT_SOURCE_STATE_CHANGED,
)
}
}
@@ -154,12 +154,12 @@ class SafetyCenterUnsupportedTest {
val listener = SafetyCenterTestListener()
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
directExecutor(),
- listener
+ listener,
)
safetyCenterManager.reportSafetySourceErrorWithPermission(
SINGLE_SOURCE_ID,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
assertFailsWith(TimeoutCancellationException::class) {
@@ -172,7 +172,7 @@ class SafetyCenterUnsupportedTest {
assertFailsWith(SecurityException::class) {
safetyCenterManager.reportSafetySourceError(
SINGLE_SOURCE_ID,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
}
}
@@ -186,7 +186,7 @@ class SafetyCenterUnsupportedTest {
assertFailsWith(TimeoutCancellationException::class) {
enabledChangedReceiver.setSafetyCenterEnabledWithReceiverPermissionAndWait(
false,
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
enabledChangedReceiver.unregister()
@@ -201,7 +201,7 @@ class SafetyCenterUnsupportedTest {
assertFailsWith(TimeoutCancellationException::class) {
SafetySourceReceiver.setSafetyCenterEnabledWithReceiverPermissionAndWait(
false,
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
}
@@ -215,7 +215,7 @@ class SafetyCenterUnsupportedTest {
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- timeout = TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT,
)
}
}
@@ -260,7 +260,7 @@ class SafetyCenterUnsupportedTest {
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
directExecutor(),
- listener
+ listener,
)
assertFailsWith(TimeoutCancellationException::class) {
@@ -285,7 +285,7 @@ class SafetyCenterUnsupportedTest {
val listener = SafetyCenterTestListener()
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
directExecutor(),
- listener
+ listener,
)
safetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(listener)
@@ -300,7 +300,7 @@ class SafetyCenterUnsupportedTest {
val listener = SafetyCenterTestListener()
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
directExecutor(),
- listener
+ listener,
)
assertFailsWith(SecurityException::class) {
@@ -316,12 +316,12 @@ class SafetyCenterUnsupportedTest {
safetyCenterManager.setSafetySourceDataWithPermission(
SINGLE_SOURCE_ID,
safetySourceTestData.criticalWithResolvingGeneralIssue,
- EVENT_SOURCE_STATE_CHANGED
+ EVENT_SOURCE_STATE_CHANGED,
)
val listener = SafetyCenterTestListener()
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
directExecutor(),
- listener
+ listener,
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
@@ -348,12 +348,12 @@ class SafetyCenterUnsupportedTest {
safetyCenterManager.setSafetySourceDataWithPermission(
SINGLE_SOURCE_ID,
safetySourceTestData.criticalWithResolvingGeneralIssue,
- EVENT_SOURCE_STATE_CHANGED
+ EVENT_SOURCE_STATE_CHANGED,
)
val listener = SafetyCenterTestListener()
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
directExecutor(),
- listener
+ listener,
)
assertFailsWith(TimeoutCancellationException::class) {
@@ -362,9 +362,9 @@ class SafetyCenterUnsupportedTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
+ CRITICAL_ISSUE_ACTION_ID,
),
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
assertFailsWith(TimeoutCancellationException::class) {
@@ -387,7 +387,7 @@ class SafetyCenterUnsupportedTest {
safetyCenterManager.setSafetySourceDataWithPermission(
SINGLE_SOURCE_ID,
safetySourceTestData.criticalWithResolvingGeneralIssue,
- EVENT_SOURCE_STATE_CHANGED
+ EVENT_SOURCE_STATE_CHANGED,
)
val apiSafetySourceDataBeforeClearing =
safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt
index 4d1cb6a8b..cba29852f 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt
@@ -203,7 +203,7 @@ class SafetyEventTest {
) =
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetyEvent.CREATOR,
- createCopy = createCopyFromBuilder
+ createCopy = createCopyFromBuilder,
)
.addEqualityGroup(SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build())
.addEqualityGroup(
@@ -212,7 +212,7 @@ class SafetyEventTest {
.build(),
SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
.setRefreshBroadcastId(REFRESH_BROADCAST_ID)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED)
@@ -222,7 +222,7 @@ class SafetyEventTest {
SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED)
.setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID)
.setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED)
@@ -232,7 +232,7 @@ class SafetyEventTest {
SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED)
.setSafetySourceIssueId(SAFETY_SOURCE_ISSUE_ID)
.setSafetySourceIssueActionId(SAFETY_SOURCE_ISSUE_ACTION_ID)
- .build()
+ .build(),
)
.addEqualityGroup(SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_REBOOTED).build())
.addEqualityGroup(SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED).build())
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
index ba2ab3d76..287aa15a3 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
@@ -350,11 +350,11 @@ class SafetySourceDataTest {
EqualsHashCodeToStringTester.ofParcelable(parcelableCreator = SafetySourceData.CREATOR)
.addEqualityGroup(
SafetySourceData.Builder().setStatus(firstStatus).build(),
- SafetySourceData.Builder().setStatus(firstStatus).build()
+ SafetySourceData.Builder().setStatus(firstStatus).build(),
)
.addEqualityGroup(
SafetySourceData.Builder().addIssue(firstIssue).addIssue(secondIssue).build(),
- SafetySourceData.Builder().addIssue(firstIssue).addIssue(secondIssue).build()
+ SafetySourceData.Builder().addIssue(firstIssue).addIssue(secondIssue).build(),
)
.addEqualityGroup(
SafetySourceData.Builder()
@@ -366,7 +366,7 @@ class SafetySourceDataTest {
.setStatus(firstStatus)
.addIssue(firstIssue)
.addIssue(secondIssue)
- .build()
+ .build(),
)
.addEqualityGroup(SafetySourceData.Builder().setStatus(secondStatus).build())
.addEqualityGroup(
@@ -394,11 +394,11 @@ class SafetySourceDataTest {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetySourceData.CREATOR,
ignoreToString = true,
- createCopy = { SafetySourceData.Builder(it).build() }
+ createCopy = { SafetySourceData.Builder(it).build() },
)
.addEqualityGroup(
SafetySourceData.Builder().setStatus(firstStatus).build(),
- SafetySourceData.Builder().setStatus(firstStatus).setExtras(filledExtras).build()
+ SafetySourceData.Builder().setStatus(firstStatus).setExtras(filledExtras).build(),
)
.addEqualityGroup(
SafetySourceData.Builder().addIssue(firstIssue).addIssue(secondIssue).build(),
@@ -406,7 +406,7 @@ class SafetySourceDataTest {
.addIssue(firstIssue)
.addIssue(secondIssue)
.setExtras(filledExtras)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetySourceData.Builder()
@@ -419,11 +419,11 @@ class SafetySourceDataTest {
.addIssue(firstIssue)
.addIssue(secondIssue)
.setExtras(filledExtras)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetySourceData.Builder().setStatus(secondStatus).build(),
- SafetySourceData.Builder().setStatus(secondStatus).setExtras(filledExtras).build()
+ SafetySourceData.Builder().setStatus(secondStatus).setExtras(filledExtras).build(),
)
.addEqualityGroup(
SafetySourceData.Builder().addIssue(secondIssue).addIssue(firstIssue).build(),
@@ -431,11 +431,11 @@ class SafetySourceDataTest {
.addIssue(secondIssue)
.addIssue(firstIssue)
.setExtras(filledExtras)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetySourceData.Builder().addIssue(firstIssue).build(),
- SafetySourceData.Builder().addIssue(firstIssue).setExtras(filledExtras).build()
+ SafetySourceData.Builder().addIssue(firstIssue).setExtras(filledExtras).build(),
)
.addEqualityGroup(
SafetySourceData.Builder()
@@ -448,7 +448,7 @@ class SafetySourceDataTest {
.addIssue(firstIssue)
.addIssue(secondIssue)
.setExtras(filledExtras)
- .build()
+ .build(),
)
.test()
}
@@ -485,7 +485,7 @@ class SafetySourceDataTest {
context,
/* requestCode = */ 0,
Intent("Status PendingIntent $id"),
- FLAG_IMMUTABLE
+ FLAG_IMMUTABLE,
)
)
.build()
@@ -496,7 +496,7 @@ class SafetySourceDataTest {
"Issue summary $id",
"Issue summary $id",
severityLevel,
- "Issue type id $id"
+ "Issue type id $id",
)
.addAction(
SafetySourceIssue.Action.Builder(
@@ -506,8 +506,8 @@ class SafetySourceDataTest {
context,
/* requestCode = */ 0,
Intent("Issue PendingIntent $id"),
- FLAG_IMMUTABLE
- )
+ FLAG_IMMUTABLE,
+ ),
)
.build()
)
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt
index 336462491..4133f6f17 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt
@@ -51,7 +51,7 @@ class SafetySourceErrorDetailsTest {
SafetySourceErrorDetails(SAFETY_EVENT),
SafetySourceErrorDetails(
SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build()
- )
+ ),
)
.addEqualityGroup(
SafetySourceErrorDetails(
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
index 2d19a3175..a7149e4d7 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
@@ -238,7 +238,7 @@ class SafetySourceIssueTest {
Action.Builder("action_id", "Action label", pendingIntent1)
.setConfirmationDialogDetails(confirmationDialogDetails)
.setWillResolve(false)
- .build()
+ .build(),
)
.addEqualityGroup(
Action.Builder("action_id", "Action label", pendingIntent1)
@@ -270,8 +270,8 @@ class SafetySourceIssueTest {
context,
0,
Intent("Other action PendingIntent"),
- FLAG_IMMUTABLE
- )
+ FLAG_IMMUTABLE,
+ ),
)
.setConfirmationDialogDetails(confirmationDialogDetails)
.build()
@@ -465,7 +465,7 @@ class SafetySourceIssueTest {
fun notification_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = Notification.CREATOR,
- createCopy = { Notification.Builder(it).build() }
+ createCopy = { Notification.Builder(it).build() },
)
.addEqualityGroup(
Notification.Builder("Title", "Text").build(),
@@ -477,7 +477,7 @@ class SafetySourceIssueTest {
.addEqualityGroup(Notification.Builder("Title", "Text").addAction(action2).build())
.addEqualityGroup(
Notification.Builder("Title", "Text").addAction(action1).addAction(action2).build(),
- Notification.Builder("Title", "Text").addActions(listOf(action1, action2)).build()
+ Notification.Builder("Title", "Text").addActions(listOf(action1, action2)).build(),
)
.test()
}
@@ -538,7 +538,7 @@ class SafetySourceIssueTest {
)
.addEqualityGroup(
ConfirmationDialogDetails("Title", "Text", "Accept", "Deny"),
- ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny"),
)
.addEqualityGroup(ConfirmationDialogDetails("Other title", "Text", "Accept", "Deny"))
.addEqualityGroup(ConfirmationDialogDetails("Title", "Other text", "Accept", "Deny"))
@@ -555,7 +555,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -571,7 +571,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -587,7 +587,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -603,7 +603,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.addAction(action1)
@@ -620,7 +620,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -637,7 +637,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -654,7 +654,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.setAttributionTitle("attribution title")
@@ -672,7 +672,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -689,7 +689,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
assertFails { safetySourceIssueBuilder.setAttributionTitle("title") }
@@ -703,7 +703,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -719,7 +719,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -735,7 +735,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.setIssueCategory(ISSUE_CATEGORY_DEVICE)
@@ -753,7 +753,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS)
@@ -771,7 +771,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.addAction(action2)
@@ -788,7 +788,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.addAction(action2)
@@ -806,7 +806,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
val actions = safetySourceIssueBuilder.build().actions
@@ -824,7 +824,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.addAction(action2)
@@ -843,7 +843,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -859,7 +859,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.setOnDismissPendingIntent(pendingIntentService)
@@ -877,7 +877,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -894,7 +894,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.setDeduplicationId("deduplication_id")
@@ -912,7 +912,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -929,7 +929,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
assertFails { safetySourceIssueBuilder.setDeduplicationId("id") }
@@ -943,7 +943,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -960,7 +960,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -977,7 +977,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.setCustomNotification(
@@ -1004,7 +1004,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -1021,7 +1021,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
assertFails { safetySourceIssueBuilder.setCustomNotification(null) }
@@ -1036,7 +1036,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -1054,7 +1054,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_NEVER)
@@ -1073,7 +1073,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -1090,7 +1090,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
val exception =
@@ -1110,7 +1110,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
assertFails { safetySourceIssueBuilder.setNotificationBehavior(0) }
@@ -1125,7 +1125,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -1143,7 +1143,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
@@ -1162,7 +1162,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -1179,7 +1179,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
val exception =
@@ -1199,7 +1199,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
assertFails { safetySourceIssueBuilder.setIssueActionability(0) }
@@ -1213,7 +1213,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
}
}
@@ -1226,7 +1226,7 @@ class SafetySourceIssueTest {
Generic.asNull(),
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
}
}
@@ -1239,7 +1239,7 @@ class SafetySourceIssueTest {
"Issue title",
Generic.asNull(),
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
}
}
@@ -1253,7 +1253,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_UNSPECIFIED,
- "issue_type_id"
+ "issue_type_id",
)
}
assertThat(exception)
@@ -1270,7 +1270,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
-1,
- "issue_type_id"
+ "issue_type_id",
)
}
assertThat(exception)
@@ -1286,7 +1286,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- Generic.asNull()
+ Generic.asNull(),
)
}
}
@@ -1299,7 +1299,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
val exception =
@@ -1319,7 +1319,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
@@ -1339,7 +1339,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
val exception =
assertFailsWith(IllegalArgumentException::class) {
@@ -1348,7 +1348,7 @@ class SafetySourceIssueTest {
context,
/* requestCode = */ 0,
Intent("PendingIntent activity"),
- FLAG_IMMUTABLE
+ FLAG_IMMUTABLE,
)
)
}
@@ -1365,7 +1365,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.addAction(action1)
@@ -1385,7 +1385,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
val exception =
@@ -1410,7 +1410,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.addAction(action2)
@@ -1432,7 +1432,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
val exception =
@@ -1452,7 +1452,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_TIP)
.build()
@@ -1469,7 +1469,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
.build()
@@ -1485,7 +1485,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1505,7 +1505,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1526,7 +1526,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1553,7 +1553,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DATA)
@@ -1601,7 +1601,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1622,7 +1622,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1641,7 +1641,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1655,7 +1655,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1663,7 +1663,7 @@ class SafetySourceIssueTest {
.addAction(action2)
.setOnDismissPendingIntent(pendingIntentService)
.setAttributionTitle("attribution title")
- .build()
+ .build(),
)
.addEqualityGroup(
SafetySourceIssue.Builder(
@@ -1671,7 +1671,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_CRITICAL_WARNING,
- "issue_type_id"
+ "issue_type_id",
)
.setAttributionTitle("Other issue attribution title")
.addAction(action1)
@@ -1683,7 +1683,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1698,7 +1698,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1707,7 +1707,7 @@ class SafetySourceIssueTest {
.setOnDismissPendingIntent(pendingIntentService)
.setAttributionTitle("attribution title")
.setDeduplicationId("deduplication_id")
- .build()
+ .build(),
)
.addEqualityGroup(
SafetySourceIssue.Builder(
@@ -1715,7 +1715,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1732,7 +1732,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Other issue subtitle")
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DATA)
@@ -1745,7 +1745,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Other issue subtitle")
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS)
@@ -1758,7 +1758,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Other issue subtitle")
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY)
@@ -1771,7 +1771,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_MANUAL)
.addAction(action1)
@@ -1786,7 +1786,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_TIP)
.addAction(action1)
@@ -1796,11 +1796,11 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_TIP)
.addAction(action1)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetySourceIssue.Builder(
@@ -1808,7 +1808,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
.addAction(action1)
@@ -1820,7 +1820,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
.build()
@@ -1831,7 +1831,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_DELAYED)
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
@@ -1843,7 +1843,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1864,7 +1864,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1889,7 +1889,7 @@ class SafetySourceIssueTest {
) =
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetySourceIssue.CREATOR,
- createCopy = createCopyFromBuilder
+ createCopy = createCopyFromBuilder,
)
.addEqualityGroup(
SafetySourceIssue.Builder(
@@ -1897,7 +1897,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -1910,14 +1910,14 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
.addAction(action1)
.addAction(action2)
.setOnDismissPendingIntent(pendingIntentService)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetySourceIssue.Builder(
@@ -1925,7 +1925,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -1936,7 +1936,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -1947,7 +1947,7 @@ class SafetySourceIssueTest {
"Other issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -1958,7 +1958,7 @@ class SafetySourceIssueTest {
"Issue title",
"Different issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -1969,7 +1969,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_CRITICAL_WARNING,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action1)
.build()
@@ -1980,7 +1980,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "other_issue_type_id"
+ "other_issue_type_id",
)
.addAction(action1)
.build()
@@ -1991,7 +1991,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.addAction(action2)
.build()
@@ -2002,7 +2002,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Other issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
@@ -2017,7 +2017,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Other issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_DEVICE)
@@ -2032,7 +2032,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Other issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_DEVICE)
@@ -2047,7 +2047,7 @@ class SafetySourceIssueTest {
"Issue title",
"Issue summary",
SEVERITY_LEVEL_INFORMATION,
- "issue_type_id"
+ "issue_type_id",
)
.setSubtitle("Other issue subtitle")
.setIssueCategory(ISSUE_CATEGORY_DEVICE)
@@ -2058,7 +2058,7 @@ class SafetySourceIssueTest {
context,
0,
Intent("Other PendingIntent service"),
- FLAG_IMMUTABLE
+ FLAG_IMMUTABLE,
)
)
.build()
@@ -2069,14 +2069,14 @@ class SafetySourceIssueTest {
) =
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = Action.CREATOR,
- createCopy = createCopyFromBuilder
+ createCopy = createCopyFromBuilder,
)
.addEqualityGroup(
Action.Builder("action_id", "Action label", pendingIntent1).build(),
Action.Builder("action_id", "Action label", pendingIntent1).build(),
Action.Builder("action_id", "Action label", pendingIntent1)
.setWillResolve(false)
- .build()
+ .build(),
)
.addEqualityGroup(
Action.Builder("action_id", "Action label", pendingIntent1)
@@ -2102,8 +2102,8 @@ class SafetySourceIssueTest {
context,
0,
Intent("Other action PendingIntent"),
- FLAG_IMMUTABLE
- )
+ FLAG_IMMUTABLE,
+ ),
)
.build()
)
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
index 2a20cd45d..04a91b268 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
@@ -98,7 +98,7 @@ class SafetySourceStatusTest {
EqualsHashCodeToStringTester.ofParcelable(parcelableCreator = IconAction.CREATOR)
.addEqualityGroup(
IconAction(ICON_TYPE_GEAR, pendingIntent1),
- IconAction(ICON_TYPE_GEAR, pendingIntent1)
+ IconAction(ICON_TYPE_GEAR, pendingIntent1),
)
.addEqualityGroup(IconAction(ICON_TYPE_INFO, pendingIntent1))
.addEqualityGroup(IconAction(ICON_TYPE_GEAR, pendingIntent2))
@@ -195,7 +195,7 @@ class SafetySourceStatusTest {
SafetySourceStatus.Builder(
Generic.asNull(),
"Status summary",
- SEVERITY_LEVEL_INFORMATION
+ SEVERITY_LEVEL_INFORMATION,
)
}
}
@@ -229,7 +229,7 @@ class SafetySourceStatusTest {
context,
/* requestCode = */ 0,
Intent("PendingIntent service"),
- FLAG_IMMUTABLE
+ FLAG_IMMUTABLE,
)
)
}
@@ -298,13 +298,13 @@ class SafetySourceStatusTest {
) =
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetySourceStatus.CREATOR,
- createCopy = createCopyFromBuilder
+ createCopy = createCopyFromBuilder,
)
.addEqualityGroup(
SafetySourceStatus.Builder(
"Status title",
"Status summary",
- SEVERITY_LEVEL_INFORMATION
+ SEVERITY_LEVEL_INFORMATION,
)
.setPendingIntent(pendingIntent1)
.setIconAction(iconAction1)
@@ -313,18 +313,18 @@ class SafetySourceStatusTest {
SafetySourceStatus.Builder(
"Status title",
"Status summary",
- SEVERITY_LEVEL_INFORMATION
+ SEVERITY_LEVEL_INFORMATION,
)
.setPendingIntent(pendingIntent1)
.setIconAction(iconAction1)
.setEnabled(true)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetySourceStatus.Builder(
"Status title",
"Status summary",
- SEVERITY_LEVEL_INFORMATION
+ SEVERITY_LEVEL_INFORMATION,
)
.build()
)
@@ -332,7 +332,7 @@ class SafetySourceStatusTest {
SafetySourceStatus.Builder(
"Other status title",
"Status summary",
- SEVERITY_LEVEL_INFORMATION
+ SEVERITY_LEVEL_INFORMATION,
)
.build()
)
@@ -340,7 +340,7 @@ class SafetySourceStatusTest {
SafetySourceStatus.Builder(
"Status title",
"Other status summary",
- SEVERITY_LEVEL_INFORMATION
+ SEVERITY_LEVEL_INFORMATION,
)
.build()
)
@@ -348,7 +348,7 @@ class SafetySourceStatusTest {
SafetySourceStatus.Builder(
"Status title",
"Status summary",
- SEVERITY_LEVEL_CRITICAL_WARNING
+ SEVERITY_LEVEL_CRITICAL_WARNING,
)
.build()
)
@@ -356,7 +356,7 @@ class SafetySourceStatusTest {
SafetySourceStatus.Builder(
"Status title",
"Status summary",
- SEVERITY_LEVEL_CRITICAL_WARNING
+ SEVERITY_LEVEL_CRITICAL_WARNING,
)
.setPendingIntent(pendingIntent2)
.build()
@@ -365,7 +365,7 @@ class SafetySourceStatusTest {
SafetySourceStatus.Builder(
"Status title",
"Status summary",
- SEVERITY_LEVEL_CRITICAL_WARNING
+ SEVERITY_LEVEL_CRITICAL_WARNING,
)
.setIconAction(iconAction2)
.build()
@@ -374,7 +374,7 @@ class SafetySourceStatusTest {
SafetySourceStatus.Builder(
"Status title",
"Status summary",
- SEVERITY_LEVEL_UNSPECIFIED
+ SEVERITY_LEVEL_UNSPECIFIED,
)
.setEnabled(false)
.build()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt
index 68dcd4a11..c3b6ef1b3 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt
@@ -36,7 +36,7 @@ class SafetyCenterConfigTest {
assertThat(BASE.safetySourcesGroups)
.containsExactly(
SafetySourcesGroupTest.STATELESS_INFERRED,
- SafetySourcesGroupTest.HIDDEN_INFERRED
+ SafetySourcesGroupTest.HIDDEN_INFERRED,
)
.inOrder()
}
@@ -65,7 +65,7 @@ class SafetyCenterConfigTest {
assertThat(sourceGroups)
.containsExactly(
SafetySourcesGroupTest.STATELESS_INFERRED,
- SafetySourcesGroupTest.HIDDEN_INFERRED
+ SafetySourcesGroupTest.HIDDEN_INFERRED,
)
.inOrder()
}
@@ -99,14 +99,14 @@ class SafetyCenterConfigTest {
) =
EqualsHashCodeToStringTester.ofParcelable(
parcelableCreator = SafetyCenterConfig.CREATOR,
- createCopy = createCopyFromBuilder
+ createCopy = createCopyFromBuilder,
)
.addEqualityGroup(
BASE,
SafetyCenterConfig.Builder()
.addSafetySourcesGroup(SafetySourcesGroupTest.STATELESS_INFERRED)
.addSafetySourcesGroup(SafetySourcesGroupTest.HIDDEN_INFERRED)
- .build()
+ .build(),
)
.addEqualityGroup(
SafetyCenterConfig.Builder()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
index 4b6f0f6f9..210a5b5e7 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
@@ -368,7 +368,7 @@ class SafetySourceTest {
{ SafetySource.Builder(it).build() }
} else {
null
- }
+ },
)
.addEqualityGroup(DYNAMIC_BAREBONE)
.addEqualityGroup(
@@ -396,7 +396,7 @@ class SafetySourceTest {
setTitleForPrivateProfileResId(REFERENCE_RES_ID)
}
}
- .build()
+ .build(),
)
.addEqualityGroup(DYNAMIC_HIDDEN)
.addEqualityGroup(DYNAMIC_HIDDEN_WITH_SEARCH)
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt
index f741369eb..06aad5d40 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt
@@ -155,7 +155,7 @@ class SafetySourcesGroupTest {
.containsExactly(
SafetySourceTest.DYNAMIC_BAREBONE,
SafetySourceTest.STATIC_BAREBONE,
- SafetySourceTest.ISSUE_ONLY_BAREBONE
+ SafetySourceTest.ISSUE_ONLY_BAREBONE,
)
.inOrder()
assertThat(STATELESS_INFERRED.safetySources)
@@ -169,7 +169,7 @@ class SafetySourcesGroupTest {
.containsExactly(
SafetySourceTest.DYNAMIC_BAREBONE,
SafetySourceTest.STATIC_BAREBONE,
- SafetySourceTest.ISSUE_ONLY_BAREBONE
+ SafetySourceTest.ISSUE_ONLY_BAREBONE,
)
assertThat(STATELESS_BAREBONE.safetySources)
.containsExactly(SafetySourceTest.STATIC_BAREBONE)
@@ -177,7 +177,7 @@ class SafetySourcesGroupTest {
.containsExactly(
SafetySourceTest.DYNAMIC_BAREBONE,
SafetySourceTest.STATIC_BAREBONE,
- SafetySourceTest.ISSUE_ONLY_BAREBONE
+ SafetySourceTest.ISSUE_ONLY_BAREBONE,
)
assertThat(HIDDEN_BAREBONE.safetySources)
.containsExactly(SafetySourceTest.ISSUE_ONLY_BAREBONE)
@@ -325,7 +325,7 @@ class SafetySourcesGroupTest {
{ SafetySourcesGroup.Builder(it).build() }
} else {
null
- }
+ },
)
.addEqualityGroup(STATEFUL_INFERRED_WITH_SUMMARY)
.addEqualityGroup(STATEFUL_INFERRED_WITH_ICON)
@@ -340,7 +340,7 @@ class SafetySourcesGroupTest {
.addSafetySource(SafetySourceTest.DYNAMIC_BAREBONE)
.addSafetySource(SafetySourceTest.STATIC_BAREBONE)
.addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
- .build()
+ .build(),
)
.apply { if (SdkLevel.isAtLeastU()) add(STATEFUL_ALL_OPTIONAL) }
.toTypedArray()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt
index 7c9e2cffc..34f5c1f69 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/XmlConfigTest.kt
@@ -88,7 +88,7 @@ class XmlConfigTest {
private fun parseXmlConfig() =
SafetyCenterConfigParser.parseXmlResource(
safetyCenterResourcesApk.safetyCenterConfig!!,
- safetyCenterResourcesApk.resources
+ safetyCenterResourcesApk.resources,
)
companion object {
diff --git a/tests/functional/safetycenter/multiusers/Android.bp b/tests/functional/safetycenter/multiusers/Android.bp
index 30024221b..745e763f0 100644
--- a/tests/functional/safetycenter/multiusers/Android.bp
+++ b/tests/functional/safetycenter/multiusers/Android.bp
@@ -36,6 +36,7 @@ android_test {
"Harrier",
"Nene",
"TestApp",
+ "bedstead-enterprise",
"com.android.permission.flags-aconfig-java-export",
],
test_suites: [
diff --git a/tests/functional/safetycenter/multiusers/AndroidTest.xml b/tests/functional/safetycenter/multiusers/AndroidTest.xml
index 20032357a..bfb7fdbf2 100644
--- a/tests/functional/safetycenter/multiusers/AndroidTest.xml
+++ b/tests/functional/safetycenter/multiusers/AndroidTest.xml
@@ -47,6 +47,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
<option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
diff --git a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt
index ff5dcc1f6..041cc652f 100644
--- a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt
+++ b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt
@@ -41,16 +41,20 @@ import android.safetycenter.SafetyEvent
import android.safetycenter.SafetySourceData
import androidx.test.core.app.ApplicationProvider
import androidx.test.filters.LargeTest
-import com.android.bedstead.harrier.BedsteadJUnit4
-import com.android.bedstead.harrier.DeviceState
-import com.android.bedstead.harrier.annotations.EnsureHasAdditionalUser
-import com.android.bedstead.harrier.annotations.EnsureHasCloneProfile
-import com.android.bedstead.enterprise.annotations.EnsureHasNoWorkProfile
-import com.android.bedstead.harrier.annotations.EnsureHasPrivateProfile
-import com.android.bedstead.harrier.annotations.EnsureHasNoPrivateProfile
-import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile
import com.android.bedstead.enterprise.annotations.EnsureHasDeviceOwner
import com.android.bedstead.enterprise.annotations.EnsureHasNoDeviceOwner
+import com.android.bedstead.enterprise.annotations.EnsureHasNoWorkProfile
+import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile
+import com.android.bedstead.enterprise.workProfile
+import com.android.bedstead.harrier.BedsteadJUnit4
+import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.multiuser.additionalUser
+import com.android.bedstead.multiuser.annotations.EnsureHasAdditionalUser
+import com.android.bedstead.multiuser.annotations.EnsureHasCloneProfile
+import com.android.bedstead.multiuser.annotations.EnsureHasNoPrivateProfile
+import com.android.bedstead.multiuser.annotations.EnsureHasPrivateProfile
+import com.android.bedstead.multiuser.cloneProfile
+import com.android.bedstead.multiuser.privateProfile
import com.android.bedstead.nene.TestApis
import com.android.bedstead.nene.types.OptionalBoolean.TRUE
import com.android.compatibility.common.util.DisableAnimationRule
@@ -132,30 +136,30 @@ class SafetyCenterMultiUsersTest {
listOf(
safetyCenterTestData.safetyCenterIssueCritical(
DYNAMIC_BAREBONE_ID,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueCritical(
ISSUE_ONLY_BAREBONE_ID,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
DYNAMIC_DISABLED_ID,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
- )
+ groupId = MIXED_STATELESS_GROUP_ID,
+ ),
)
private val dynamicBareboneDefault: SafetyCenterEntry
@@ -181,7 +185,7 @@ class SafetyCenterMultiUsersTest {
.safetyCenterEntryDefaultBuilder(
DYNAMIC_DISABLED_ID,
userId = deviceState.workProfile().id(),
- title = "Paste"
+ title = "Paste",
)
.setPendingIntent(null)
.setEnabled(false)
@@ -196,11 +200,9 @@ class SafetyCenterMultiUsersTest {
DYNAMIC_DISABLED_ID,
deviceState.workProfile().id(),
title = "Ok title for Work",
- pendingIntent = null
- )
- .setSummary(
- safetyCenterResourcesApk.getStringByName("work_profile_paused"),
+ pendingIntent = null,
)
+ .setSummary(safetyCenterResourcesApk.getStringByName("work_profile_paused"))
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
.setEnabled(false)
.build()
@@ -212,7 +214,7 @@ class SafetyCenterMultiUsersTest {
get() =
safetyCenterTestData.safetyCenterEntryUnspecified(
DYNAMIC_HIDDEN_ID,
- pendingIntent = null
+ pendingIntent = null,
)
private val dynamicHiddenForWorkUpdated: SafetyCenterEntry
@@ -225,11 +227,9 @@ class SafetyCenterMultiUsersTest {
DYNAMIC_HIDDEN_ID,
deviceState.workProfile().id(),
title = "Ok title for Work",
- pendingIntent = null
- )
- .setSummary(
- safetyCenterResourcesApk.getStringByName("work_profile_paused"),
+ pendingIntent = null,
)
+ .setSummary(safetyCenterResourcesApk.getStringByName("work_profile_paused"))
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
.setEnabled(false)
.build()
@@ -262,12 +262,12 @@ class SafetyCenterMultiUsersTest {
.safetyCenterEntryDefaultStaticBuilder(
STATIC_ALL_OPTIONAL_ID,
userId = deviceState.workProfile().id(),
- title = "Paste"
+ title = "Paste",
)
.setPendingIntent(
createTestActivityRedirectPendingIntentForUser(
deviceState.workProfile().userHandle(),
- explicit = false
+ explicit = false,
)
)
@@ -287,12 +287,12 @@ class SafetyCenterMultiUsersTest {
.safetyCenterEntryDefaultStaticBuilder(
STATIC_ALL_OPTIONAL_ID,
userId = deviceState.privateProfile().id(),
- title = "Unknown"
+ title = "Unknown",
)
.setPendingIntent(
createTestActivityRedirectPendingIntentForUser(
deviceState.privateProfile().userHandle(),
- explicit = false
+ explicit = false,
)
)
@@ -320,20 +320,20 @@ class SafetyCenterMultiUsersTest {
.setPendingIntent(
createTestActivityRedirectPendingIntentForUser(
deviceState.workProfile().userHandle(),
- explicit
+ explicit,
)
)
private fun staticEntryForPrivateBuilder(
title: CharSequence = "Unknown",
- explicit: Boolean = true
+ explicit: Boolean = true,
) =
SafetyCenterStaticEntry.Builder(title)
.setSummary("OK")
.setPendingIntent(
createTestActivityRedirectPendingIntentForUser(
deviceState.privateProfile().userHandle(),
- explicit
+ explicit,
)
)
@@ -374,18 +374,20 @@ class SafetyCenterMultiUsersTest {
safetyCenterTestData.safetyCenterStatusUnknown,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
safetyCenterTestData.safetyCenterEntryDefault(
SINGLE_SOURCE_ALL_PROFILE_ID,
deviceState.additionalUser().id(),
pendingIntent =
createTestActivityRedirectPendingIntentForUser(
deviceState.additionalUser().userHandle()
- )
- )
+ ),
+ ),
+ "No info yet",
)
),
- emptyList()
+ emptyList(),
)
@get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
@@ -449,7 +451,7 @@ class SafetyCenterMultiUsersTest {
getSafetyCenterManagerForUser(deviceState.workProfile().userHandle())
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForWork
+ dataForWork,
)
setQuietMode(true)
@@ -470,7 +472,7 @@ class SafetyCenterMultiUsersTest {
val dataForAdditionalUser = safetySourceTestData.information
additionalUserSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForAdditionalUser
+ dataForAdditionalUser,
)
checkState(
additionalUserSafetyCenterManager.getSafetySourceDataWithInteractAcrossUsersPermission(
@@ -510,7 +512,7 @@ class SafetyCenterMultiUsersTest {
val dataForWork = safetySourceTestData.informationWithIssueForWork
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForWork
+ dataForWork,
)
TestApis.packages().find(context.packageName).uninstall(deviceState.workProfile())
@@ -531,7 +533,7 @@ class SafetyCenterMultiUsersTest {
val dataForWork = safetySourceTestData.informationWithIssueForWork
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForWork
+ dataForWork,
)
deviceState.workProfile().remove()
@@ -552,7 +554,7 @@ class SafetyCenterMultiUsersTest {
val dataForWork = safetySourceTestData.informationWithIssueForWork
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForWork
+ dataForWork,
)
setQuietMode(true)
@@ -593,14 +595,14 @@ class SafetyCenterMultiUsersTest {
staticGroupBuilder
.setEntries(listOf(staticBarebone, staticAllOptional))
.build()
- )
+ ),
),
listOf(
SafetyCenterStaticEntryGroup(
"OK",
- listOf(createStaticEntry(), createStaticEntry(explicit = false))
+ listOf(createStaticEntry(), createStaticEntry(explicit = false)),
)
- )
+ ),
)
assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig)
}
@@ -628,7 +630,7 @@ class SafetyCenterMultiUsersTest {
listOf(
dynamicBareboneDefault,
dynamicDisabledDefault,
- dynamicDisabledForWorkDefault
+ dynamicDisabledForWorkDefault,
)
)
.setSeverityUnspecifiedIconType(
@@ -642,7 +644,7 @@ class SafetyCenterMultiUsersTest {
listOf(staticBarebone, staticAllOptional, staticAllOptionalForWork)
)
.build()
- )
+ ),
),
listOf(
SafetyCenterStaticEntryGroup(
@@ -651,10 +653,10 @@ class SafetyCenterMultiUsersTest {
createStaticEntry(),
createStaticEntryForWork(),
createStaticEntry(explicit = false),
- createStaticEntryForWork(explicit = false)
- )
+ createStaticEntryForWork(explicit = false),
+ ),
)
- )
+ ),
)
assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig)
}
@@ -682,7 +684,7 @@ class SafetyCenterMultiUsersTest {
dynamicBareboneUpdated,
dynamicDisabledUpdated,
dynamicDisabledForWorkDefault,
- dynamicHiddenUpdated
+ dynamicHiddenUpdated,
)
)
.setSeverityUnspecifiedIconType(
@@ -696,7 +698,7 @@ class SafetyCenterMultiUsersTest {
listOf(staticBarebone, staticAllOptional, staticAllOptionalForWork)
)
.build()
- )
+ ),
),
listOf(
SafetyCenterStaticEntryGroup(
@@ -705,10 +707,10 @@ class SafetyCenterMultiUsersTest {
staticEntryUpdated,
createStaticEntryForWork(),
createStaticEntry(explicit = false),
- createStaticEntryForWork(explicit = false)
- )
+ createStaticEntryForWork(explicit = false),
+ ),
)
- )
+ ),
)
assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig)
}
@@ -730,56 +732,56 @@ class SafetyCenterMultiUsersTest {
listOf(
safetyCenterTestData.safetyCenterIssueCritical(
DYNAMIC_BAREBONE_ID,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueCritical(
ISSUE_ONLY_BAREBONE_ID,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
DYNAMIC_DISABLED_ID,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_DISABLED_ID,
managedUserId,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_HIDDEN_ID,
managedUserId,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
managedUserId,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_IN_STATELESS_ID,
managedUserId,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_IN_STATELESS_ID,
managedUserId,
- groupId = MIXED_STATELESS_GROUP_ID
- )
+ groupId = MIXED_STATELESS_GROUP_ID,
+ ),
),
listOf(
SafetyCenterEntryOrGroup(
@@ -792,7 +794,7 @@ class SafetyCenterMultiUsersTest {
dynamicDisabledUpdated,
dynamicDisabledForWorkUpdated,
dynamicHiddenUpdated,
- dynamicHiddenForWorkUpdated
+ dynamicHiddenForWorkUpdated,
)
)
.setSeverityUnspecifiedIconType(
@@ -806,7 +808,7 @@ class SafetyCenterMultiUsersTest {
listOf(staticBarebone, staticAllOptional, staticAllOptionalForWork)
)
.build()
- )
+ ),
),
listOf(
SafetyCenterStaticEntryGroup(
@@ -815,10 +817,10 @@ class SafetyCenterMultiUsersTest {
staticEntryUpdated,
staticEntryForWorkUpdated,
createStaticEntry(explicit = false),
- createStaticEntryForWork(explicit = false)
- )
+ createStaticEntryForWork(explicit = false),
+ ),
)
- )
+ ),
)
assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig)
}
@@ -842,56 +844,56 @@ class SafetyCenterMultiUsersTest {
listOf(
safetyCenterTestData.safetyCenterIssueCritical(
DYNAMIC_BAREBONE_ID,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueCritical(
ISSUE_ONLY_BAREBONE_ID,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
DYNAMIC_DISABLED_ID,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_DISABLED_ID,
managedUserId,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_HIDDEN_ID,
managedUserId,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
managedUserId,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_IN_STATELESS_ID,
managedUserId,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_IN_STATELESS_ID,
managedUserId,
- groupId = MIXED_STATELESS_GROUP_ID
- )
+ groupId = MIXED_STATELESS_GROUP_ID,
+ ),
),
listOf(
SafetyCenterEntryOrGroup(
@@ -904,7 +906,7 @@ class SafetyCenterMultiUsersTest {
dynamicDisabledUpdated,
dynamicDisabledForWorkUpdated,
dynamicHiddenUpdated,
- dynamicHiddenForWorkUpdated
+ dynamicHiddenForWorkUpdated,
)
)
.setSeverityUnspecifiedIconType(
@@ -918,7 +920,7 @@ class SafetyCenterMultiUsersTest {
listOf(staticBarebone, staticAllOptional, staticAllOptionalForWork)
)
.build()
- )
+ ),
),
listOf(
SafetyCenterStaticEntryGroup(
@@ -927,10 +929,10 @@ class SafetyCenterMultiUsersTest {
staticEntryUpdated,
staticEntryForWorkUpdated,
createStaticEntry(explicit = false),
- createStaticEntryForWork(explicit = false)
- )
+ createStaticEntryForWork(explicit = false),
+ ),
)
- )
+ ),
)
assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig)
}
@@ -956,82 +958,82 @@ class SafetyCenterMultiUsersTest {
listOf(
safetyCenterTestData.safetyCenterIssueCritical(
DYNAMIC_BAREBONE_ID,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueCritical(
ISSUE_ONLY_BAREBONE_ID,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
DYNAMIC_DISABLED_ID,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_DISABLED_ID,
managedUserId,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_HIDDEN_ID,
managedUserId,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
managedUserId,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_IN_STATELESS_ID,
managedUserId,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_IN_STATELESS_ID,
managedUserId,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_DISABLED_ID,
privateProfileId,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_HIDDEN_ID,
privateProfileId,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
privateProfileId,
attributionTitle = null,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_IN_STATELESS_ID,
privateProfileId,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_IN_STATELESS_ID,
privateProfileId,
- groupId = MIXED_STATELESS_GROUP_ID
- )
+ groupId = MIXED_STATELESS_GROUP_ID,
+ ),
),
listOf(
SafetyCenterEntryOrGroup(
@@ -1046,7 +1048,7 @@ class SafetyCenterMultiUsersTest {
dynamicDisabledForPrivateUpdated,
dynamicHiddenUpdated,
dynamicHiddenForWorkUpdated,
- dynamicHiddenForPrivateUpdated
+ dynamicHiddenForPrivateUpdated,
)
)
.setSeverityUnspecifiedIconType(
@@ -1061,11 +1063,11 @@ class SafetyCenterMultiUsersTest {
staticBarebone,
staticAllOptional,
staticAllOptionalForWork,
- staticAllOptionalForPrivate
+ staticAllOptionalForPrivate,
)
)
.build()
- )
+ ),
),
listOf(
SafetyCenterStaticEntryGroup(
@@ -1076,10 +1078,10 @@ class SafetyCenterMultiUsersTest {
staticEntryForPrivateUpdated,
createStaticEntry(explicit = false),
createStaticEntryForWork(explicit = false),
- createStaticEntryForPrivate(explicit = false)
- )
+ createStaticEntryForPrivate(explicit = false),
+ ),
)
- )
+ ),
)
assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig)
}
@@ -1124,11 +1126,11 @@ class SafetyCenterMultiUsersTest {
listOf(
staticBarebone,
staticAllOptional,
- staticAllOptionalForWorkPaused
+ staticAllOptionalForWorkPaused,
)
)
.build()
- )
+ ),
),
listOf(
SafetyCenterStaticEntryGroup(
@@ -1137,10 +1139,10 @@ class SafetyCenterMultiUsersTest {
staticEntryUpdated,
staticEntryForWorkPausedUpdated,
createStaticEntry(explicit = false),
- createStaticEntryForWorkPaused()
- )
+ createStaticEntryForWorkPaused(),
+ ),
)
- )
+ ),
)
assertThat(apiSafetyCenterData.withoutExtras()).isEqualTo(safetyCenterDataFromComplexConfig)
}
@@ -1157,7 +1159,7 @@ class SafetyCenterMultiUsersTest {
getSafetyCenterManagerForUser(deviceState.workProfile().userHandle())
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForPrimaryUserWorkProfile
+ dataForPrimaryUserWorkProfile,
)
val additionalUserSafetyCenterManager =
@@ -1199,8 +1201,8 @@ class SafetyCenterMultiUsersTest {
pendingIntent =
createTestActivityRedirectPendingIntentForUser(
deviceState.workProfile().userHandle()
- )
- )
+ ),
+ ),
)
)
.setSeverityUnspecifiedIconType(
@@ -1209,7 +1211,7 @@ class SafetyCenterMultiUsersTest {
.build()
)
),
- emptyList()
+ emptyList(),
)
checkState(
safetyCenterManager.getSafetyCenterDataWithPermission() ==
@@ -1227,11 +1229,13 @@ class SafetyCenterMultiUsersTest {
safetyCenterTestData.safetyCenterStatusUnknown,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ALL_PROFILE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ALL_PROFILE_ID),
+ "No info yet",
)
),
- emptyList()
+ emptyList(),
)
assertThat(safetyCenterManager.getSafetyCenterDataWithPermission())
.isEqualTo(safetyCenterDataForPrimaryUser)
@@ -1271,8 +1275,8 @@ class SafetyCenterMultiUsersTest {
pendingIntent =
createTestActivityRedirectPendingIntentForUser(
deviceState.privateProfile().userHandle()
- )
- )
+ ),
+ ),
)
)
.setSeverityUnspecifiedIconType(
@@ -1281,7 +1285,7 @@ class SafetyCenterMultiUsersTest {
.build()
)
),
- emptyList()
+ emptyList(),
)
assertThat(safetyCenterManager.getSafetyCenterDataWithPermission())
@@ -1298,11 +1302,13 @@ class SafetyCenterMultiUsersTest {
safetyCenterTestData.safetyCenterStatusUnknown,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ALL_PROFILE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ALL_PROFILE_ID),
+ "No info yet",
)
),
- emptyList()
+ emptyList(),
)
assertThat(safetyCenterManager.getSafetyCenterDataWithPermission())
.isEqualTo(safetyCenterDataForPrimaryUser)
@@ -1345,7 +1351,7 @@ class SafetyCenterMultiUsersTest {
assertFailsWith(IllegalArgumentException::class) {
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- dataForWork
+ dataForWork,
)
}
}
@@ -1362,7 +1368,7 @@ class SafetyCenterMultiUsersTest {
managedSafetyCenterManager.setSafetySourceData(
SINGLE_SOURCE_ALL_PROFILE_ID,
dataForWork,
- EVENT_SOURCE_STATE_CHANGED
+ EVENT_SOURCE_STATE_CHANGED,
)
}
}
@@ -1378,7 +1384,7 @@ class SafetyCenterMultiUsersTest {
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForWork
+ dataForWork,
)
val safetySourceData =
@@ -1399,7 +1405,7 @@ class SafetyCenterMultiUsersTest {
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForWork
+ dataForWork,
)
val safetySourceData =
@@ -1419,7 +1425,7 @@ class SafetyCenterMultiUsersTest {
clonedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForClone
+ dataForClone,
)
val safetySourceData =
@@ -1440,7 +1446,7 @@ class SafetyCenterMultiUsersTest {
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForWork
+ dataForWork,
)
val safetySourceData =
@@ -1464,7 +1470,7 @@ class SafetyCenterMultiUsersTest {
SafetySourceTestData.issuesOnly(safetySourceTestData.criticalResolvingGeneralIssue)
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
ISSUE_ONLY_ALL_PROFILE_SOURCE_ID,
- dataForWorkProfile
+ dataForWorkProfile,
)
val apiSafetySourceData =
@@ -1488,7 +1494,7 @@ class SafetyCenterMultiUsersTest {
assertFailsWith(IllegalArgumentException::class) {
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ID,
- dataForWork
+ dataForWork,
)
}
}
@@ -1505,7 +1511,7 @@ class SafetyCenterMultiUsersTest {
getSafetyCenterManagerForUser(deviceState.workProfile().userHandle())
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForWork
+ dataForWork,
)
val apiSafetySourceData =
@@ -1532,14 +1538,14 @@ class SafetyCenterMultiUsersTest {
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ALL_PROFILE_ID,
- dataForWork
+ dataForWork,
)
TestNotificationListener.waitForNotificationsMatching(
NotificationCharacteristics(
title = "Information issue title",
text = "Information issue summary",
- actions = listOf("Review")
+ actions = listOf("Review"),
)
)
}
@@ -1555,7 +1561,7 @@ class SafetyCenterMultiUsersTest {
getSafetyCenterManagerForUser(deviceState.additionalUser().userHandle())
additionalUserSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ID,
- dataForPrimaryUser
+ dataForPrimaryUser,
)
val apiSafetySourceData =
@@ -1577,7 +1583,7 @@ class SafetyCenterMultiUsersTest {
getSafetyCenterManagerForUser(deviceState.additionalUser().userHandle())
additionalUserSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ID,
- dataForAdditionalUser
+ dataForAdditionalUser,
)
val apiSafetySourceDataForPrimaryUser =
@@ -1600,7 +1606,7 @@ class SafetyCenterMultiUsersTest {
getSafetyCenterManagerForUser(deviceState.additionalUser().userHandle())
additionalUserSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
SINGLE_SOURCE_ID,
- dataForAdditionalUser
+ dataForAdditionalUser,
)
val apiSafetySourceDataForPrimaryUser =
@@ -1627,12 +1633,12 @@ class SafetyCenterMultiUsersTest {
private fun createTestActivityRedirectPendingIntentForUser(
user: UserHandle,
- explicit: Boolean = true
+ explicit: Boolean = true,
): PendingIntent {
return callWithShellPermissionIdentity(INTERACT_ACROSS_USERS) {
SafetySourceTestData.createRedirectPendingIntent(
getContextForUser(user),
- SafetySourceTestData.createTestActivityIntent(getContextForUser(user), explicit)
+ SafetySourceTestData.createTestActivityIntent(getContextForUser(user), explicit),
)
}
}
@@ -1647,7 +1653,7 @@ class SafetyCenterMultiUsersTest {
private fun SafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
id: String,
dataToSet: SafetySourceData,
- safetyEvent: SafetyEvent = EVENT_SOURCE_STATE_CHANGED
+ safetyEvent: SafetyEvent = EVENT_SOURCE_STATE_CHANGED,
) =
callWithShellPermissionIdentity(INTERACT_ACROSS_USERS_FULL) {
setSafetySourceDataWithPermission(id, dataToSet, safetyEvent)
@@ -1691,28 +1697,28 @@ class SafetyCenterMultiUsersTest {
private fun updatePrimaryProfileSources() {
safetyCenterTestHelper.setData(
DYNAMIC_BAREBONE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
DYNAMIC_DISABLED_ID,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(DYNAMIC_HIDDEN_ID, safetySourceTestData.unspecified)
safetyCenterTestHelper.setData(
ISSUE_ONLY_BAREBONE_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.criticalResolvingGeneralIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.criticalResolvingGeneralIssue),
)
safetyCenterTestHelper.setData(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue),
)
safetyCenterTestHelper.setData(
DYNAMIC_IN_STATELESS_ID,
- safetySourceTestData.unspecifiedWithIssue
+ safetySourceTestData.unspecifiedWithIssue,
)
safetyCenterTestHelper.setData(
ISSUE_ONLY_IN_STATELESS_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue),
)
}
@@ -1721,23 +1727,23 @@ class SafetyCenterMultiUsersTest {
getSafetyCenterManagerForUser(deviceState.workProfile().userHandle())
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
DYNAMIC_DISABLED_ID,
- safetySourceTestData.informationWithIssueForWork
+ safetySourceTestData.informationWithIssueForWork,
)
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
DYNAMIC_HIDDEN_ID,
- safetySourceTestData.informationWithIssueForWork
+ safetySourceTestData.informationWithIssueForWork,
)
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue),
)
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
DYNAMIC_IN_STATELESS_ID,
- safetySourceTestData.unspecifiedWithIssueForWork
+ safetySourceTestData.unspecifiedWithIssueForWork,
)
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
ISSUE_ONLY_IN_STATELESS_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue),
)
}
@@ -1746,23 +1752,23 @@ class SafetyCenterMultiUsersTest {
getSafetyCenterManagerForUser(deviceState.privateProfile().userHandle())
privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
DYNAMIC_DISABLED_ID,
- safetySourceTestData.informationWithIssueForPrivate
+ safetySourceTestData.informationWithIssueForPrivate,
)
privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
DYNAMIC_HIDDEN_ID,
- safetySourceTestData.informationWithIssueForPrivate
+ safetySourceTestData.informationWithIssueForPrivate,
)
privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue),
)
privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
DYNAMIC_IN_STATELESS_ID,
- safetySourceTestData.unspecifiedWithIssueForPrivate
+ safetySourceTestData.unspecifiedWithIssueForPrivate,
)
privateSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
ISSUE_ONLY_IN_STATELESS_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue),
)
}
}
diff --git a/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml b/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml
index a1826653f..ee79dcd2a 100644
--- a/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml
+++ b/tests/functional/safetycenter/safetycenteractivity/AndroidTest.xml
@@ -47,6 +47,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
<option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
diff --git a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
index 73f435615..09a32f058 100644
--- a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
+++ b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
@@ -60,6 +60,7 @@ import com.android.safetycenter.testing.UiTestHelper.RESCAN_BUTTON_LABEL
import com.android.safetycenter.testing.UiTestHelper.clickConfirmDismissal
import com.android.safetycenter.testing.UiTestHelper.clickDismissIssueCard
import com.android.safetycenter.testing.UiTestHelper.clickMoreIssuesCard
+import com.android.safetycenter.testing.UiTestHelper.clickOpenSubpage
import com.android.safetycenter.testing.UiTestHelper.resetRotation
import com.android.safetycenter.testing.UiTestHelper.rotate
import com.android.safetycenter.testing.UiTestHelper.setAnimationsEnabled
@@ -73,7 +74,9 @@ import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceDataDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueNotDisplayed
+import java.util.regex.Pattern
import org.junit.After
+import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Rule
import org.junit.Test
@@ -99,13 +102,13 @@ class SafetyCenterActivityTest {
}
@Test
- fun launchActivity_allowingSettingsTrampoline() {
+ fun launchActivity_allowingSettingsTrampoline_showsSafetyCenter() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
val dataToDisplay = safetySourceTestData.criticalWithResolvingGeneralIssue
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, dataToDisplay)
context.launchSafetyCenterActivity(preventTrampolineToSettings = false) {
- waitSourceDataDisplayed(dataToDisplay)
+ waitSourceIssueDisplayed(dataToDisplay.issues[0])
}
}
@@ -120,7 +123,7 @@ class SafetyCenterActivityTest {
context.getString(safetyCenterTestConfigs.staticSource1.summaryResId),
context.getString(safetyCenterTestConfigs.staticSourceGroup2.titleResId),
context.getString(safetyCenterTestConfigs.staticSource2.titleResId),
- context.getString(safetyCenterTestConfigs.staticSource2.summaryResId)
+ context.getString(safetyCenterTestConfigs.staticSource2.summaryResId),
)
}
}
@@ -131,7 +134,13 @@ class SafetyCenterActivityTest {
val dataToDisplay = safetySourceTestData.criticalWithResolvingGeneralIssue
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, dataToDisplay)
- context.launchSafetyCenterActivity { waitSourceDataDisplayed(dataToDisplay) }
+ context.launchSafetyCenterActivity {
+ if (SafetyCenterFlags.showSubpages) {
+ waitSourceIssueDisplayed(dataToDisplay.issues[0])
+ } else {
+ waitSourceDataDisplayed(dataToDisplay)
+ }
+ }
}
@Test
@@ -144,8 +153,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_1_TITLE,
entrySummary = SAFETY_SOURCE_1_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_2,
@@ -153,8 +162,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_2_TITLE,
entrySummary = SAFETY_SOURCE_2_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_3,
@@ -162,8 +171,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_3_TITLE,
entrySummary = SAFETY_SOURCE_3_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_4,
@@ -171,8 +180,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_4_TITLE,
entrySummary = SAFETY_SOURCE_4_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_5,
@@ -180,8 +189,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_5_TITLE,
entrySummary = SAFETY_SOURCE_5_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
}
@@ -190,7 +199,7 @@ class SafetyCenterActivityTest {
context.getString(safetyCenterTestConfigs.dynamicSourceGroup1.titleResId),
context.getString(safetyCenterTestConfigs.dynamicSourceGroup1.summaryResId),
context.getString(safetyCenterTestConfigs.dynamicSourceGroup3.titleResId),
- context.getString(safetyCenterTestConfigs.dynamicSourceGroup3.summaryResId)
+ context.getString(safetyCenterTestConfigs.dynamicSourceGroup3.summaryResId),
)
waitAllTextNotDisplayed(
SAFETY_SOURCE_1_TITLE,
@@ -200,7 +209,7 @@ class SafetyCenterActivityTest {
SAFETY_SOURCE_4_TITLE,
SAFETY_SOURCE_4_SUMMARY,
SAFETY_SOURCE_5_TITLE,
- SAFETY_SOURCE_5_SUMMARY
+ SAFETY_SOURCE_5_SUMMARY,
)
}
}
@@ -215,8 +224,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_1_TITLE,
entrySummary = SAFETY_SOURCE_1_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_2,
@@ -224,8 +233,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_RECOMMENDATION,
entryTitle = SAFETY_SOURCE_2_TITLE,
entrySummary = SAFETY_SOURCE_2_SUMMARY,
- withIssue = true
- )
+ withIssue = true,
+ ),
)
setData(
SOURCE_ID_3,
@@ -233,8 +242,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_3_TITLE,
entrySummary = SAFETY_SOURCE_3_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_4,
@@ -242,8 +251,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_RECOMMENDATION,
entryTitle = SAFETY_SOURCE_4_TITLE,
entrySummary = SAFETY_SOURCE_4_SUMMARY,
- withIssue = true
- )
+ withIssue = true,
+ ),
)
setData(
SOURCE_ID_5,
@@ -251,8 +260,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_CRITICAL_WARNING,
entryTitle = SAFETY_SOURCE_5_TITLE,
entrySummary = SAFETY_SOURCE_5_SUMMARY,
- withIssue = true
- )
+ withIssue = true,
+ ),
)
}
@@ -261,7 +270,7 @@ class SafetyCenterActivityTest {
context.getString(safetyCenterTestConfigs.dynamicSourceGroup1.titleResId),
SAFETY_SOURCE_2_SUMMARY,
context.getString(safetyCenterTestConfigs.dynamicSourceGroup3.titleResId),
- SAFETY_SOURCE_5_SUMMARY
+ SAFETY_SOURCE_5_SUMMARY,
)
waitAllTextNotDisplayed(
SAFETY_SOURCE_1_TITLE,
@@ -269,13 +278,15 @@ class SafetyCenterActivityTest {
SAFETY_SOURCE_1_SUMMARY,
SAFETY_SOURCE_4_TITLE,
SAFETY_SOURCE_5_TITLE,
- SAFETY_SOURCE_4_SUMMARY
+ SAFETY_SOURCE_4_SUMMARY,
)
}
}
@Test
fun launchActivity_displaysGroupsOfSingleSourceAsEntity() {
+ // Single source groups are displayed in the subpage when subpages enabled
+ assumeFalse(SafetyCenterFlags.showSubpages)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourceGroupsConfig)
context.launchSafetyCenterActivity {
@@ -297,7 +308,11 @@ class SafetyCenterActivityTest {
val dataToDisplay = safetySourceTestData.recommendationWithGeneralIssue
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, dataToDisplay)
- waitSourceDataDisplayed(dataToDisplay)
+ if (SafetyCenterFlags.showSubpages) {
+ waitSourceIssueDisplayed(dataToDisplay.issues[0])
+ } else {
+ waitSourceDataDisplayed(dataToDisplay)
+ }
}
}
@@ -338,6 +353,8 @@ class SafetyCenterActivityTest {
@Test
fun entryListWithEntryGroup_informationState_hasContentDescription() {
+ // No custom content descriptions when using subpages
+ assumeFalse(SafetyCenterFlags.showSubpages)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(SOURCE_ID_1, safetySourceTestData.information)
safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
@@ -355,10 +372,12 @@ class SafetyCenterActivityTest {
@Test
fun entryListWithEntryGroup_recommendationState_hasActionsNeededContentDescription() {
+ // No custom content descriptions when using subpages
+ assumeFalse(SafetyCenterFlags.showSubpages)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.information)
@@ -394,6 +413,8 @@ class SafetyCenterActivityTest {
@Test
fun entryListWithEntryGroup_unclickableDisabledEntry_hasContentDescription() {
+ // No custom content descriptions when using subpages
+ assumeFalse(SafetyCenterFlags.showSubpages)
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesConfigWithSourceWithInvalidIntent
)
@@ -412,7 +433,7 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.unspecifiedDisabledWithTestActivityRedirect
+ safetySourceTestData.unspecifiedDisabledWithTestActivityRedirect,
)
context.launchSafetyCenterActivity {
@@ -424,10 +445,12 @@ class SafetyCenterActivityTest {
@Test
fun entryListWithEntryGroup_clickableDisabledEntry_hasContentDescription() {
+ // No custom content descriptions when using subpages
+ assumeFalse(SafetyCenterFlags.showSubpages)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.unspecifiedDisabledWithTestActivityRedirect
+ safetySourceTestData.unspecifiedDisabledWithTestActivityRedirect,
)
context.launchSafetyCenterActivity {
@@ -442,6 +465,8 @@ class SafetyCenterActivityTest {
@Test
fun entryListWithSingleSource_informationState_hasContentDescription() {
+ // No custom content descriptions when using subpages
+ assumeFalse(SafetyCenterFlags.showSubpages)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
@@ -456,6 +481,12 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
context.launchSafetyCenterActivity {
+ if (SafetyCenterFlags.showSubpages) {
+ clickOpenSubpage(
+ context,
+ safetyCenterTestConfigs.singleSourceConfig.safetySourcesGroups.first(),
+ )
+ }
waitDisplayed(By.text("OK")) { it.click() }
waitButtonDisplayed("Exit test activity") { it.click() }
waitDisplayed(By.text("OK"))
@@ -467,6 +498,13 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.implicitIntentSingleSourceConfig)
context.launchSafetyCenterActivity {
+ if (SafetyCenterFlags.showSubpages) {
+ clickOpenSubpage(
+ context,
+ safetyCenterTestConfigs.implicitIntentSingleSourceConfig.safetySourcesGroups
+ .first(),
+ )
+ }
waitDisplayed(By.text("OK")) { it.click() }
waitButtonDisplayed("Exit test activity") { it.click() }
}
@@ -478,6 +516,12 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
context.launchSafetyCenterActivity {
+ if (SafetyCenterFlags.showSubpages) {
+ clickOpenSubpage(
+ context,
+ safetyCenterTestConfigs.singleSourceConfig.safetySourcesGroups.first(),
+ )
+ }
waitDisplayed(By.text("Ok title")) { it.click() }
waitButtonDisplayed("Exit test activity") { it.click() }
waitDisplayed(By.text("Ok title"))
@@ -489,10 +533,16 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.informationWithIconAction
+ safetySourceTestData.informationWithIconAction,
)
context.launchSafetyCenterActivity {
+ if (SafetyCenterFlags.showSubpages) {
+ clickOpenSubpage(
+ context,
+ safetyCenterTestConfigs.singleSourceConfig.safetySourcesGroups.first(),
+ )
+ }
waitDisplayed(By.desc("Information")) { it.click() }
waitButtonDisplayed("Exit test activity") { it.click() }
waitDisplayed(By.text("Ok title"))
@@ -523,7 +573,7 @@ class SafetyCenterActivityTest {
val issue = safetySourceTestData.recommendationGeneralIssue
safetyCenterTestHelper.setData(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(issue)
+ SafetySourceTestData.issuesOnly(issue),
)
context.launchSafetyCenterActivity { waitDisplayed(By.desc("Alert. ${issue.title}")) }
@@ -553,7 +603,6 @@ class SafetyCenterActivityTest {
clickDismissIssueCard()
waitSourceIssueNotDisplayed(safetySourceTestData.informationIssue)
- waitSourceDataDisplayed(safetySourceTestData.information)
waitButtonDisplayed(RESCAN_BUTTON_LABEL)
}
}
@@ -563,7 +612,7 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
context.launchSafetyCenterActivity {
@@ -581,21 +630,21 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
context.launchSafetyCenterActivity {
clickDismissIssueCard()
waitAllTextDisplayed(
"Dismiss this alert?",
- "Review your security and privacy settings any time to add more protection"
+ "Review your security and privacy settings any time to add more protection",
)
getUiDevice().rotate()
waitAllTextDisplayed(
"Dismiss this alert?",
- "Review your security and privacy settings any time to add more protection"
+ "Review your security and privacy settings any time to add more protection",
)
clickConfirmDismissal()
@@ -609,7 +658,7 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
context.launchSafetyCenterActivity {
@@ -626,7 +675,7 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
context.launchSafetyCenterActivity {
@@ -650,13 +699,13 @@ class SafetyCenterActivityTest {
// Set the initial data for the source
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage
+ safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage,
)
// Clear the data when action is triggered to simulate resolution.
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.ClearData
+ Response.ClearData,
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
@@ -680,13 +729,13 @@ class SafetyCenterActivityTest {
// Set the initial data for the source
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage
+ safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage,
)
// Clear the data when action is triggered to simulate resolution.
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.ClearData
+ Response.ClearData,
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
@@ -708,13 +757,13 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssueWithConfirmation
+ safetySourceTestData.criticalWithResolvingGeneralIssueWithConfirmation,
)
// Clear the data when action is triggered to simulate resolution.
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.ClearData
+ Response.ClearData,
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
@@ -734,13 +783,13 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssueWithConfirmation
+ safetySourceTestData.criticalWithResolvingGeneralIssueWithConfirmation,
)
// Clear the data when action is triggered to simulate resolution.
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.ClearData
+ Response.ClearData,
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
@@ -764,7 +813,7 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssueWithConfirmation
+ safetySourceTestData.criticalWithResolvingGeneralIssueWithConfirmation,
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
@@ -786,13 +835,13 @@ class SafetyCenterActivityTest {
// Set the initial data for the source
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
// Clear the data when action is triggered to simulate resolution.
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.ClearData
+ Response.ClearData,
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
@@ -909,11 +958,11 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.informationWithIssue)
@@ -923,7 +972,7 @@ class SafetyCenterActivityTest {
waitExpandedIssuesDisplayed(
safetySourceTestData.criticalResolvingGeneralIssue,
safetySourceTestData.recommendationGeneralIssue,
- safetySourceTestData.informationIssue
+ safetySourceTestData.informationIssue,
)
}
}
@@ -933,11 +982,11 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.informationWithIssue)
@@ -948,7 +997,7 @@ class SafetyCenterActivityTest {
waitCollapsedIssuesDisplayed(
safetySourceTestData.criticalResolvingGeneralIssue,
safetySourceTestData.recommendationGeneralIssue,
- safetySourceTestData.informationIssue
+ safetySourceTestData.informationIssue,
)
}
}
@@ -958,11 +1007,11 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.criticalWithRedirectingIssue
+ safetySourceTestData.criticalWithRedirectingIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.informationWithIssue)
@@ -973,7 +1022,7 @@ class SafetyCenterActivityTest {
waitCollapsedIssuesDisplayed(
safetySourceTestData.criticalRedirectingIssue,
safetySourceTestData.criticalResolvingGeneralIssue,
- safetySourceTestData.informationIssue
+ safetySourceTestData.informationIssue,
)
}
}
@@ -983,11 +1032,11 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.informationWithIssue)
@@ -1007,11 +1056,11 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.informationWithIssue)
@@ -1022,7 +1071,7 @@ class SafetyCenterActivityTest {
waitCollapsedIssuesDisplayed(
safetySourceTestData.criticalResolvingGeneralIssue,
safetySourceTestData.recommendationGeneralIssue,
- safetySourceTestData.informationIssue
+ safetySourceTestData.informationIssue,
)
}
}
@@ -1032,7 +1081,7 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
context.launchSafetyCenterActivity {
@@ -1046,11 +1095,11 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.informationWithIssue)
@@ -1058,7 +1107,7 @@ class SafetyCenterActivityTest {
waitCollapsedIssuesDisplayed(
safetySourceTestData.criticalResolvingGeneralIssue,
safetySourceTestData.recommendationGeneralIssue,
- safetySourceTestData.informationIssue
+ safetySourceTestData.informationIssue,
)
}
}
@@ -1068,11 +1117,11 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.informationWithIssue)
@@ -1084,7 +1133,7 @@ class SafetyCenterActivityTest {
waitExpandedIssuesDisplayed(
safetySourceTestData.criticalResolvingGeneralIssue,
safetySourceTestData.recommendationGeneralIssue,
- safetySourceTestData.informationIssue
+ safetySourceTestData.informationIssue,
)
}
}
@@ -1094,11 +1143,11 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.informationWithIssue)
@@ -1118,7 +1167,7 @@ class SafetyCenterActivityTest {
waitExpandedIssuesDisplayed(
safetySourceTestData.criticalResolvingGeneralIssue,
safetySourceTestData.recommendationGeneralIssue,
- safetySourceTestData.informationIssue
+ safetySourceTestData.informationIssue,
)
}
}
@@ -1128,11 +1177,11 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.informationWithIssue)
@@ -1152,11 +1201,11 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_3, safetySourceTestData.informationWithIssue)
@@ -1173,7 +1222,7 @@ class SafetyCenterActivityTest {
}
@Test
- fun collapsedEntryGroup_expandsWhenClicked() {
+ fun entryGroup_showsEntriesWhenClicked() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourceGroupsConfig)
with(safetyCenterTestHelper) {
setConfig(safetyCenterTestConfigs.multipleSourceGroupsConfig)
@@ -1183,8 +1232,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_1_TITLE,
entrySummary = SAFETY_SOURCE_1_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_2,
@@ -1192,8 +1241,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_2_TITLE,
entrySummary = SAFETY_SOURCE_2_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_3,
@@ -1201,8 +1250,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_3_TITLE,
entrySummary = SAFETY_SOURCE_3_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_4,
@@ -1210,8 +1259,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_4_TITLE,
entrySummary = SAFETY_SOURCE_4_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_5,
@@ -1219,8 +1268,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_5_TITLE,
entrySummary = SAFETY_SOURCE_5_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
}
@@ -1238,13 +1287,14 @@ class SafetyCenterActivityTest {
SAFETY_SOURCE_1_TITLE,
SAFETY_SOURCE_1_SUMMARY,
SAFETY_SOURCE_2_TITLE,
- SAFETY_SOURCE_2_SUMMARY
+ SAFETY_SOURCE_2_SUMMARY,
)
}
}
@Test
fun expandedEntryGroup_collapsesWhenClicked() {
+ assumeFalse(SafetyCenterFlags.showSubpages) // No collapsible groups when using subpages
with(safetyCenterTestHelper) {
setConfig(safetyCenterTestConfigs.multipleSourceGroupsConfig)
setData(
@@ -1253,8 +1303,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_1_TITLE,
entrySummary = SAFETY_SOURCE_1_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_2,
@@ -1262,8 +1312,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_2_TITLE,
entrySummary = SAFETY_SOURCE_2_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_3,
@@ -1271,8 +1321,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_3_TITLE,
entrySummary = SAFETY_SOURCE_3_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_4,
@@ -1280,8 +1330,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_4_TITLE,
entrySummary = SAFETY_SOURCE_4_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_5,
@@ -1289,8 +1339,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_5_TITLE,
entrySummary = SAFETY_SOURCE_5_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
}
@@ -1332,13 +1382,14 @@ class SafetyCenterActivityTest {
)
waitAllTextDisplayed(
context.getString(safetyCenterTestConfigs.dynamicSource1.titleResId),
- context.getString(safetyCenterTestConfigs.dynamicSource2.titleResId)
+ context.getString(safetyCenterTestConfigs.dynamicSource2.titleResId),
)
}
}
@Test
fun expandedEntryGroup_otherGroupRemainsCollapsed() {
+ assumeFalse(SafetyCenterFlags.showSubpages) // No collapsible groups when using subpages
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourceGroupsConfig)
with(safetyCenterTestHelper) {
setConfig(safetyCenterTestConfigs.multipleSourceGroupsConfig)
@@ -1348,8 +1399,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_1_TITLE,
entrySummary = SAFETY_SOURCE_1_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_2,
@@ -1357,8 +1408,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_2_TITLE,
entrySummary = SAFETY_SOURCE_2_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_3,
@@ -1366,8 +1417,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_3_TITLE,
entrySummary = SAFETY_SOURCE_3_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_4,
@@ -1375,8 +1426,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_4_TITLE,
entrySummary = SAFETY_SOURCE_4_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
setData(
SOURCE_ID_5,
@@ -1384,8 +1435,8 @@ class SafetyCenterActivityTest {
severityLevel = SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_5_TITLE,
entrySummary = SAFETY_SOURCE_5_SUMMARY,
- withIssue = false
- )
+ withIssue = false,
+ ),
)
}
@@ -1425,7 +1476,7 @@ class SafetyCenterActivityTest {
context.launchSafetyCenterActivity {
waitAllTextDisplayed(
context.getString(lastGroup.titleResId),
- context.getString(lastGroup.summaryResId)
+ context.getString(lastGroup.summaryResId),
)
waitDisplayed(By.text(context.getString(firstGroup.titleResId))) { it.click() }
@@ -1444,6 +1495,13 @@ class SafetyCenterActivityTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.implicitIntentSingleSourceConfig)
context.launchSafetyCenterActivity {
+ if (SafetyCenterFlags.showSubpages) {
+ clickOpenSubpage(
+ context,
+ safetyCenterTestConfigs.implicitIntentSingleSourceConfig.safetySourcesGroups
+ .first(),
+ )
+ }
waitDisplayed(By.text("OK")) { it.click() }
waitDisplayed(By.text("is_from_settings_homepage false"))
waitButtonDisplayed("Exit test activity") { it.click() }
@@ -1477,9 +1535,10 @@ class SafetyCenterActivityTest {
)
safetyCenterTestHelper.setEnabled(false)
+ val containsPrivacyPattern = Pattern.compile(".*[Pp]rivacy|[Pp]ermission.*") // NOTYPO
context.launchSafetyCenterActivity(intentAction = PRIVACY_CONTROLS_ACTION) {
waitDisplayed(By.pkg(context.getSettingsPackageName()))
- waitPageTitleDisplayed("Privacy")
+ waitDisplayed(By.text(containsPrivacyPattern))
}
}
diff --git a/tests/functional/safetycenter/singleuser/AndroidTest.xml b/tests/functional/safetycenter/singleuser/AndroidTest.xml
index 3aa173508..f778ca93e 100644
--- a/tests/functional/safetycenter/singleuser/AndroidTest.xml
+++ b/tests/functional/safetycenter/singleuser/AndroidTest.xml
@@ -47,6 +47,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
<option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
index cb3935ec5..f5d230deb 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
@@ -75,6 +75,7 @@ import com.android.safetycenter.internaldata.SafetyCenterIds
import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
+import com.android.safetycenter.testing.Coroutines.assertWithTimeout
import com.android.safetycenter.testing.Coroutines.waitForWithTimeout
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.dismissSafetyCenterIssueWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetyCenterConfigWithPermission
@@ -104,6 +105,7 @@ import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.MIXED_
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.MIXED_STATELESS_GROUP_ID
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.MULTIPLE_SOURCES_GROUP_ID_1
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.MULTIPLE_SOURCES_GROUP_ID_2
+import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_GROUP_ID
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_1
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_2
@@ -160,7 +162,7 @@ class SafetyCenterManagerTest {
get() =
SafetyCenterStatus.Builder(
safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary")
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary"),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
.build()
@@ -169,7 +171,7 @@ class SafetyCenterManagerTest {
get() =
SafetyCenterStatus.Builder(
safetyCenterResourcesApk.getStringByName("scanning_title"),
- safetyCenterResourcesApk.getStringByName("loading_summary")
+ safetyCenterResourcesApk.getStringByName("loading_summary"),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_UNKNOWN)
.setRefreshStatus(REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS)
@@ -179,7 +181,7 @@ class SafetyCenterManagerTest {
get() =
SafetyCenterStatus.Builder(
safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
.build()
@@ -190,7 +192,7 @@ class SafetyCenterManagerTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_ok_review_title"
),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
.build()
@@ -203,7 +205,7 @@ class SafetyCenterManagerTest {
),
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_ok_review_summary"
- )
+ ),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
.build()
@@ -214,7 +216,7 @@ class SafetyCenterManagerTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_safety_recommendation_title"
),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
.build()
@@ -225,7 +227,7 @@ class SafetyCenterManagerTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_account_recommendation_title"
),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
.build()
@@ -236,7 +238,7 @@ class SafetyCenterManagerTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_device_recommendation_title"
),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
.build()
@@ -247,7 +249,7 @@ class SafetyCenterManagerTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_critical_safety_warning_title"
),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
.build()
@@ -258,7 +260,7 @@ class SafetyCenterManagerTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_critical_safety_warning_title"
),
- safetyCenterTestData.getAlertString(2)
+ safetyCenterTestData.getAlertString(2),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
.build()
@@ -269,7 +271,7 @@ class SafetyCenterManagerTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_critical_account_warning_title"
),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
.build()
@@ -280,7 +282,7 @@ class SafetyCenterManagerTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_critical_account_warning_title"
),
- safetyCenterTestData.getAlertString(2)
+ safetyCenterTestData.getAlertString(2),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
.build()
@@ -291,7 +293,7 @@ class SafetyCenterManagerTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_critical_device_warning_title"
),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
.build()
@@ -302,21 +304,23 @@ class SafetyCenterManagerTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_critical_device_warning_title"
),
- safetyCenterTestData.getAlertString(2)
+ safetyCenterTestData.getAlertString(2),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
.build()
private val safetyCenterEntryOrGroupRecommendation: SafetyCenterEntryOrGroup
get() =
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID),
)
private val safetyCenterEntryOrGroupCritical: SafetyCenterEntryOrGroup
get() =
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryCritical(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryCritical(SINGLE_SOURCE_ID),
)
private val safetyCenterEntryGroupMixedFromComplexConfig: SafetyCenterEntryOrGroup
@@ -330,7 +334,7 @@ class SafetyCenterManagerTest {
safetyCenterTestData.safetyCenterEntryDefault(DYNAMIC_IN_STATEFUL_ID),
SafetyCenterEntry.Builder(
SafetyCenterTestData.entryId(STATIC_IN_STATEFUL_ID),
- "OK"
+ "OK",
)
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
.setSummary("OK")
@@ -342,7 +346,7 @@ class SafetyCenterManagerTest {
.setSeverityUnspecifiedIconType(
SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON
)
- .build()
+ .build(),
)
)
.setSeverityUnspecifiedIconType(
@@ -370,8 +374,8 @@ class SafetyCenterManagerTest {
explicit = false
)
)
- .build()
- )
+ .build(),
+ ),
)
private val safetyCenterStaticEntryGroupMixedFromComplexConfig: SafetyCenterStaticEntryGroup
@@ -392,8 +396,8 @@ class SafetyCenterManagerTest {
explicit = false
)
)
- .build()
- )
+ .build(),
+ ),
)
private val safetyCenterStaticEntryGroupMixedUpdatedFromComplexConfig:
@@ -415,8 +419,8 @@ class SafetyCenterManagerTest {
explicit = false
)
)
- .build()
- )
+ .build(),
+ ),
)
private val safetyCenterDataFromConfigScanning: SafetyCenterData
@@ -425,11 +429,13 @@ class SafetyCenterManagerTest {
safetyCenterStatusUnknownScanning,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ID),
+ "No info yet",
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataFromConfig: SafetyCenterData
@@ -438,11 +444,13 @@ class SafetyCenterManagerTest {
safetyCenterTestData.safetyCenterStatusUnknown,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryDefault(SINGLE_SOURCE_ID),
+ "No info yet",
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataUnspecified: SafetyCenterData
@@ -451,11 +459,12 @@ class SafetyCenterManagerTest {
safetyCenterStatusOk,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryUnspecified(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryUnspecified(SINGLE_SOURCE_ID),
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataOk: SafetyCenterData
@@ -464,11 +473,12 @@ class SafetyCenterManagerTest {
safetyCenterStatusOk,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryOk(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryOk(SINGLE_SOURCE_ID),
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataOkWithIconAction: SafetyCenterData
@@ -477,17 +487,18 @@ class SafetyCenterManagerTest {
safetyCenterStatusOk,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
safetyCenterTestData
.safetyCenterEntryOkBuilder(SINGLE_SOURCE_ID)
.setIconAction(
ICON_ACTION_TYPE_INFO,
- safetySourceTestData.createTestActivityRedirectPendingIntent()
+ safetySourceTestData.createTestActivityRedirectPendingIntent(),
)
- .build()
+ .build(),
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataUnknownScanningWithError: SafetyCenterData
@@ -496,11 +507,13 @@ class SafetyCenterManagerTest {
safetyCenterStatusUnknownScanning,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryError(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryError(SINGLE_SOURCE_ID),
+ "Couldn’t check setting",
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataUnknownReviewError: SafetyCenterData
@@ -509,11 +522,13 @@ class SafetyCenterManagerTest {
safetyCenterTestData.safetyCenterStatusUnknown,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryError(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryError(SINGLE_SOURCE_ID),
+ "Couldn’t check setting",
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataOkOneAlert: SafetyCenterData
@@ -522,20 +537,40 @@ class SafetyCenterManagerTest {
safetyCenterStatusOkOneAlert,
listOf(safetyCenterTestData.safetyCenterIssueInformation(SINGLE_SOURCE_ID)),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryOk(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryOk(SINGLE_SOURCE_ID),
+ "Ok summary", // When an issue is present, entry summary is used
)
),
- emptyList()
+ emptyList(),
)
+ private val safetyCenterDataOkOneDismissedAlert: SafetyCenterData
+ get() =
+ SafetyCenterData(
+ safetyCenterStatusOk,
+ emptyList(),
+ listOf(
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryOk(SINGLE_SOURCE_ID),
+ "Ok summary", // When an issue is present, entry summary is used
+ )
+ ),
+ emptyList(),
+ )
+ .withDismissedIssuesIfAtLeastU(
+ listOf(safetyCenterTestData.safetyCenterIssueInformation(SINGLE_SOURCE_ID))
+ )
+
private val safetyCenterDataOkReviewCriticalEntry: SafetyCenterData
get() =
SafetyCenterData(
safetyCenterStatusOkReview,
emptyList(),
listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataOkReviewRecommendationEntry: SafetyCenterData
@@ -544,7 +579,7 @@ class SafetyCenterManagerTest {
safetyCenterStatusOkReview,
emptyList(),
listOf(safetyCenterEntryOrGroupRecommendation),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataOkReviewOneAlert: SafetyCenterData
@@ -553,7 +588,7 @@ class SafetyCenterManagerTest {
safetyCenterStatusOkReviewOneAlert,
listOf(safetyCenterTestData.safetyCenterIssueInformation(SINGLE_SOURCE_ID)),
listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataGeneralRecommendationOneAlert: SafetyCenterData
@@ -562,11 +597,12 @@ class SafetyCenterManagerTest {
safetyCenterStatusGeneralRecommendationOneAlert,
listOf(safetyCenterTestData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID)),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID),
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataGeneralRecommendationAlertWithConfirmation: SafetyCenterData
@@ -576,15 +612,16 @@ class SafetyCenterManagerTest {
listOf(
safetyCenterTestData.safetyCenterIssueRecommendation(
SINGLE_SOURCE_ID,
- confirmationDialog = true
+ confirmationDialog = true,
)
),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID),
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataAccountRecommendationOneAlert: SafetyCenterData
@@ -593,11 +630,12 @@ class SafetyCenterManagerTest {
safetyCenterStatusAccountRecommendationOneAlert,
listOf(safetyCenterTestData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID)),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID),
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataDeviceRecommendationOneAlert: SafetyCenterData
@@ -606,11 +644,12 @@ class SafetyCenterManagerTest {
safetyCenterStatusDeviceRecommendationOneAlert,
listOf(safetyCenterTestData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID)),
listOf(
- SafetyCenterEntryOrGroup(
- safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID)
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
+ safetyCenterTestData.safetyCenterEntryRecommendation(SINGLE_SOURCE_ID),
)
),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataGeneralCriticalOneAlert: SafetyCenterData
@@ -619,7 +658,7 @@ class SafetyCenterManagerTest {
safetyCenterStatusGeneralCriticalOneAlert,
listOf(safetyCenterTestData.safetyCenterIssueCritical(SINGLE_SOURCE_ID)),
listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataAccountCriticalOneAlert: SafetyCenterData
@@ -628,7 +667,7 @@ class SafetyCenterManagerTest {
safetyCenterStatusAccountCriticalOneAlert,
listOf(safetyCenterTestData.safetyCenterIssueCritical(SINGLE_SOURCE_ID)),
listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataDeviceCriticalOneAlert: SafetyCenterData
@@ -637,7 +676,7 @@ class SafetyCenterManagerTest {
safetyCenterStatusDeviceCriticalOneAlert,
listOf(safetyCenterTestData.safetyCenterIssueCritical(SINGLE_SOURCE_ID)),
listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataCriticalOneAlertInFlight: SafetyCenterData
@@ -647,11 +686,11 @@ class SafetyCenterManagerTest {
listOf(
safetyCenterTestData.safetyCenterIssueCritical(
SINGLE_SOURCE_ID,
- isActionInFlight = true
+ isActionInFlight = true,
)
),
listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
+ emptyList(),
)
private val safetyCenterDataOkReviewOneDismissedAlertInFlight: SafetyCenterData
@@ -660,13 +699,13 @@ class SafetyCenterManagerTest {
safetyCenterStatusOkReview,
emptyList(),
listOf(safetyCenterEntryOrGroupCritical),
- emptyList()
+ emptyList(),
)
.withDismissedIssuesIfAtLeastU(
listOf(
safetyCenterTestData.safetyCenterIssueCritical(
SINGLE_SOURCE_ID,
- isActionInFlight = true
+ isActionInFlight = true,
)
)
)
@@ -702,17 +741,17 @@ class SafetyCenterManagerTest {
.safetyCenterEntryDefaultBuilder(DYNAMIC_OTHER_PACKAGE_ID)
.setPendingIntent(null)
.setEnabled(false)
- .build()
+ .build(),
)
)
.build()
),
- safetyCenterEntryGroupMixedFromComplexConfig
+ safetyCenterEntryGroupMixedFromComplexConfig,
),
listOf(
safetyCenterStaticEntryGroupFromComplexConfig,
- safetyCenterStaticEntryGroupMixedFromComplexConfig
- )
+ safetyCenterStaticEntryGroupMixedFromComplexConfig,
+ ),
)
private val safetyCenterDataFromComplexConfigUpdated: SafetyCenterData
@@ -722,28 +761,28 @@ class SafetyCenterManagerTest {
listOf(
safetyCenterTestData.safetyCenterIssueCritical(
DYNAMIC_BAREBONE_ID,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueCritical(
ISSUE_ONLY_BAREBONE_ID,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
DYNAMIC_DISABLED_ID,
- groupId = DYNAMIC_GROUP_ID
+ groupId = DYNAMIC_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- groupId = ISSUE_ONLY_GROUP_ID
+ groupId = ISSUE_ONLY_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
DYNAMIC_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
+ groupId = MIXED_STATELESS_GROUP_ID,
),
safetyCenterTestData.safetyCenterIssueInformation(
ISSUE_ONLY_IN_STATELESS_ID,
- groupId = MIXED_STATELESS_GROUP_ID
- )
+ groupId = MIXED_STATELESS_GROUP_ID,
+ ),
),
listOf(
SafetyCenterEntryOrGroup(
@@ -765,7 +804,7 @@ class SafetyCenterManagerTest {
),
safetyCenterTestData.safetyCenterEntryUnspecified(
DYNAMIC_HIDDEN_ID,
- pendingIntent = null
+ pendingIntent = null,
),
safetyCenterTestData.safetyCenterEntryOk(
DYNAMIC_HIDDEN_WITH_SEARCH_ID
@@ -774,17 +813,17 @@ class SafetyCenterManagerTest {
.safetyCenterEntryDefaultBuilder(DYNAMIC_OTHER_PACKAGE_ID)
.setPendingIntent(null)
.setEnabled(false)
- .build()
+ .build(),
)
)
.build()
),
- safetyCenterEntryGroupMixedFromComplexConfig
+ safetyCenterEntryGroupMixedFromComplexConfig,
),
listOf(
safetyCenterStaticEntryGroupFromComplexConfig,
- safetyCenterStaticEntryGroupMixedUpdatedFromComplexConfig
- )
+ safetyCenterStaticEntryGroupMixedUpdatedFromComplexConfig,
+ ),
)
@get:Rule(order = 1) val flagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@@ -838,7 +877,7 @@ class SafetyCenterManagerTest {
SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_LONG)
SafetySourceReceiver.setResponse(
Request.Rescan(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_RESCAN_BUTTON_CLICK
@@ -869,7 +908,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Rescan(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
val listener = safetyCenterTestHelper.addListener()
@@ -894,7 +933,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
val listener = safetyCenterTestHelper.addListener()
@@ -918,7 +957,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
val listener = safetyCenterTestHelper.addListener()
@@ -944,7 +983,7 @@ class SafetyCenterManagerTest {
SafetyCenterFlags.overrideRefreshOnPageOpenSources = setOf(SINGLE_SOURCE_ID)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.informationWithIssue)
+ Response.SetData(safetySourceTestData.informationWithIssue),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -962,13 +1001,13 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.information)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.informationWithIssue)
+ Response.SetData(safetySourceTestData.informationWithIssue),
)
assertFailsWith(TimeoutCancellationException::class) {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
REFRESH_REASON_PAGE_OPEN,
- timeout = TIMEOUT_SHORT
+ timeout = TIMEOUT_SHORT,
)
}
val apiSafetySourceDataBeforeSettingFlag =
@@ -990,7 +1029,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.noPageOpenConfig)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
@@ -1020,10 +1059,16 @@ class SafetyCenterManagerTest {
val expectedExplicitPendingIntent =
SafetySourceTestData.createRedirectPendingIntent(
context,
- Intent(ACTION_TEST_ACTIVITY).setPackage(context.packageName)
+ Intent(ACTION_TEST_ACTIVITY).setPackage(context.packageName),
)
val defaultEntryPendingIntent =
apiSafetyCenterData.entriesOrGroups.firstOrNull()?.entry?.pendingIntent
+ ?: apiSafetyCenterData.entriesOrGroups
+ .firstOrNull()
+ ?.entryGroup
+ ?.entries
+ ?.firstOrNull()
+ ?.pendingIntent
val defaultEntryIntentFilterEqualsToExplicitIntent =
callWithShellPermissionIdentity("android.permission.GET_INTENT_SENDER_INTENT") {
expectedExplicitPendingIntent.intentFilterEquals(defaultEntryPendingIntent)
@@ -1040,10 +1085,16 @@ class SafetyCenterManagerTest {
val expectedImplicitPendingIntent =
SafetySourceTestData.createRedirectPendingIntent(
context,
- Intent(ACTION_TEST_ACTIVITY_EXPORTED)
+ Intent(ACTION_TEST_ACTIVITY_EXPORTED),
)
val defaultEntryPendingIntent =
apiSafetyCenterData.entriesOrGroups.firstOrNull()?.entry?.pendingIntent
+ ?: apiSafetyCenterData.entriesOrGroups
+ .firstOrNull()
+ ?.entryGroup
+ ?.entries
+ ?.firstOrNull()
+ ?.pendingIntent
val defaultEntryIntentFilterEqualsToImplicitIntent =
callWithShellPermissionIdentity("android.permission.GET_INTENT_SENDER_INTENT") {
expectedImplicitPendingIntent.intentFilterEquals(defaultEntryPendingIntent)
@@ -1060,7 +1111,7 @@ class SafetyCenterManagerTest {
val expectedImplicitPendingIntent =
SafetySourceTestData.createRedirectPendingIntent(
context,
- Intent(ACTION_TEST_ACTIVITY_EXPORTED)
+ Intent(ACTION_TEST_ACTIVITY_EXPORTED),
)
val staticEntryPendingIntent =
apiSafetyCenterData.staticEntryGroups
@@ -1086,7 +1137,7 @@ class SafetyCenterManagerTest {
val expectedExplicitPendingIntent =
SafetySourceTestData.createRedirectPendingIntent(
context,
- Intent(ACTION_TEST_ACTIVITY).setPackage(context.packageName)
+ Intent(ACTION_TEST_ACTIVITY).setPackage(context.packageName),
)
val staticEntryPendingIntent =
apiSafetyCenterData.staticEntryGroups
@@ -1146,7 +1197,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.informationWithIconAction
+ safetySourceTestData.informationWithIconAction,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1160,7 +1211,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.informationWithIssueWithAttributionTitle
+ safetySourceTestData.informationWithIssueWithAttributionTitle,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1189,7 +1240,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceNoGroupTitleConfig)
safetyCenterTestHelper.setData(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue),
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1200,11 +1251,11 @@ class SafetyCenterManagerTest {
listOf(
safetyCenterTestData.safetyCenterIssueRecommendation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- attributionTitle = null
+ attributionTitle = null,
)
),
emptyList(),
- emptyList()
+ emptyList(),
)
assertThat(apiSafetyCenterData).isEqualTo(expectedSafetyCenterData)
}
@@ -1227,7 +1278,7 @@ class SafetyCenterManagerTest {
val previousApiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1241,7 +1292,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterManager.reportSafetySourceErrorWithPermission(
SINGLE_SOURCE_ID,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1264,7 +1315,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithInformationIssue
+ safetySourceTestData.criticalWithInformationIssue,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1277,7 +1328,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1291,7 +1342,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.recommendationWithIssueWithActionConfirmation
+ safetySourceTestData.recommendationWithIssueWithActionConfirmation,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1305,7 +1356,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.recommendationWithAccountIssue
+ safetySourceTestData.recommendationWithAccountIssue,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1318,7 +1369,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.recommendationWithDeviceIssue
+ safetySourceTestData.recommendationWithDeviceIssue,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1331,7 +1382,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1344,7 +1395,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingAccountIssue
+ safetySourceTestData.criticalWithResolvingAccountIssue,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1357,7 +1408,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingDeviceIssue
+ safetySourceTestData.criticalWithResolvingDeviceIssue,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1376,7 +1427,7 @@ class SafetyCenterManagerTest {
.defaultRecommendationIssueBuilder()
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DATA)
.build()
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1385,7 +1436,7 @@ class SafetyCenterManagerTest {
.isEqualTo(
safetyCenterTestData.safetyCenterStatusOneAlert(
"overall_severity_level_data_recommendation_title",
- OVERALL_SEVERITY_LEVEL_RECOMMENDATION
+ OVERALL_SEVERITY_LEVEL_RECOMMENDATION,
)
)
}
@@ -1401,7 +1452,7 @@ class SafetyCenterManagerTest {
.defaultCriticalResolvingIssueBuilder()
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DATA)
.build()
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1410,7 +1461,7 @@ class SafetyCenterManagerTest {
.isEqualTo(
safetyCenterTestData.safetyCenterStatusOneAlert(
"overall_severity_level_critical_data_warning_title",
- OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING
+ OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING,
)
)
}
@@ -1426,7 +1477,7 @@ class SafetyCenterManagerTest {
.defaultRecommendationIssueBuilder()
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS)
.build()
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1435,7 +1486,7 @@ class SafetyCenterManagerTest {
.isEqualTo(
safetyCenterTestData.safetyCenterStatusOneAlert(
"overall_severity_level_passwords_recommendation_title",
- OVERALL_SEVERITY_LEVEL_RECOMMENDATION
+ OVERALL_SEVERITY_LEVEL_RECOMMENDATION,
)
)
}
@@ -1451,7 +1502,7 @@ class SafetyCenterManagerTest {
.defaultCriticalResolvingIssueBuilder()
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS)
.build()
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1460,7 +1511,7 @@ class SafetyCenterManagerTest {
.isEqualTo(
safetyCenterTestData.safetyCenterStatusOneAlert(
"overall_severity_level_critical_passwords_warning_title",
- OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING
+ OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING,
)
)
}
@@ -1476,7 +1527,7 @@ class SafetyCenterManagerTest {
.defaultRecommendationIssueBuilder()
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY)
.build()
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1485,7 +1536,7 @@ class SafetyCenterManagerTest {
.isEqualTo(
safetyCenterTestData.safetyCenterStatusOneAlert(
"overall_severity_level_personal_recommendation_title",
- OVERALL_SEVERITY_LEVEL_RECOMMENDATION
+ OVERALL_SEVERITY_LEVEL_RECOMMENDATION,
)
)
}
@@ -1501,7 +1552,7 @@ class SafetyCenterManagerTest {
.defaultCriticalResolvingIssueBuilder()
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY)
.build()
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1510,7 +1561,7 @@ class SafetyCenterManagerTest {
.isEqualTo(
safetyCenterTestData.safetyCenterStatusOneAlert(
"overall_severity_level_critical_personal_warning_title",
- OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING
+ OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING,
)
)
}
@@ -1526,7 +1577,7 @@ class SafetyCenterManagerTest {
.defaultInformationIssueBuilder()
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_TIP)
.build()
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1562,7 +1613,7 @@ class SafetyCenterManagerTest {
.defaultInformationIssueBuilder("id_5")
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_TIP)
.build(),
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1582,7 +1633,7 @@ class SafetyCenterManagerTest {
.defaultInformationIssueBuilder()
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
.build()
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1618,7 +1669,7 @@ class SafetyCenterManagerTest {
.defaultInformationIssueBuilder("id_5")
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_MANUAL)
.build(),
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1637,8 +1688,8 @@ class SafetyCenterManagerTest {
safetySourceTestData
.defaultInformationIssueBuilder()
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_MANUAL)
- .build(),
- )
+ .build()
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1648,7 +1699,7 @@ class SafetyCenterManagerTest {
safetyCenterTestData.safetyCenterStatusNAlerts(
"overall_severity_level_ok_title",
OVERALL_SEVERITY_LEVEL_OK,
- numAlerts = 1
+ numAlerts = 1,
)
)
}
@@ -1676,7 +1727,7 @@ class SafetyCenterManagerTest {
.defaultInformationIssueBuilder("id_4")
.setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
.build(),
- )
+ ),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1686,7 +1737,7 @@ class SafetyCenterManagerTest {
safetyCenterTestData.safetyCenterStatusNAlerts(
"overall_severity_level_ok_title",
OVERALL_SEVERITY_LEVEL_OK,
- numAlerts = 2
+ numAlerts = 2,
)
)
}
@@ -1701,7 +1752,7 @@ class SafetyCenterManagerTest {
assertThat(
SafetyCenterBundles.getStaticEntryId(
apiSafetyCenterData,
- apiSafetyCenterData.staticEntryGroups[0].staticEntries[0]
+ apiSafetyCenterData.staticEntryGroups[0].staticEntries[0],
)
)
.isEqualTo(
@@ -1713,7 +1764,7 @@ class SafetyCenterManagerTest {
assertThat(
SafetyCenterBundles.getStaticEntryId(
apiSafetyCenterData,
- apiSafetyCenterData.staticEntryGroups[1].staticEntries[0]
+ apiSafetyCenterData.staticEntryGroups[1].staticEntries[0],
)
)
.isEqualTo(
@@ -1729,7 +1780,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingDeviceIssueAndRecommendationIssue
+ safetySourceTestData.criticalWithResolvingDeviceIssueAndRecommendationIssue,
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1742,11 +1793,11 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.recommendationWithAccountIssue
+ safetySourceTestData.recommendationWithAccountIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_3,
- safetySourceTestData.criticalWithResolvingDeviceIssue
+ safetySourceTestData.criticalWithResolvingDeviceIssue,
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1759,11 +1810,11 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_3,
- safetySourceTestData.criticalWithResolvingDeviceIssue
+ safetySourceTestData.criticalWithResolvingDeviceIssue,
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -1782,14 +1833,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
@@ -1798,7 +1849,7 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_1,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
)
)
}
@@ -1814,14 +1865,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1 and source group MULTIPLE_SOURCES_GROUP_ID_2
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1844,14 +1895,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1 and source group MULTIPLE_SOURCES_GROUP_ID_1
safetyCenterTestHelper.setData(
SOURCE_ID_2,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1874,14 +1925,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_3 and source group MULTIPLE_SOURCES_GROUP_ID_2
safetyCenterTestHelper.setData(
SOURCE_ID_6,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -1902,14 +1953,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("different")
- )
+ ),
)
val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
@@ -1918,12 +1969,12 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_1,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
),
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2
- )
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
+ ),
)
.inOrder()
}
@@ -1939,14 +1990,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_3
safetyCenterTestHelper.setData(
SOURCE_ID_6,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
@@ -1955,12 +2006,12 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_1,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
),
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_6,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2
- )
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
+ ),
)
.inOrder()
}
@@ -1976,21 +2027,21 @@ class SafetyCenterManagerTest {
SOURCE_ID_4,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_3
safetyCenterTestHelper.setData(
SOURCE_ID_6,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_3
safetyCenterTestHelper.setData(
SOURCE_ID_7,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
@@ -1999,7 +2050,7 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_4,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
)
)
}
@@ -2015,14 +2066,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_2,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
@@ -2031,7 +2082,7 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
)
}
@@ -2047,49 +2098,49 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("A")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_2,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("A")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_2
safetyCenterTestHelper.setData(
SOURCE_ID_3,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("B")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_3
safetyCenterTestHelper.setData(
SOURCE_ID_4,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("B")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("A")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_3
safetyCenterTestHelper.setData(
SOURCE_ID_6,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("B")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_3
safetyCenterTestHelper.setData(
SOURCE_ID_7,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("B")
- )
+ ),
)
val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
@@ -2098,16 +2149,16 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
SOURCE_ID_3,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
),
safetyCenterTestData.safetyCenterIssueRecommendation(
SOURCE_ID_4,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
- )
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
+ ),
)
.inOrder()
}
@@ -2123,14 +2174,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
@@ -2148,7 +2199,7 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_1,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
)
)
}
@@ -2164,14 +2215,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SOURCE_ID_5, RECOMMENDATION_ISSUE_ID)
@@ -2185,7 +2236,7 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_1,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
)
)
assertThat(apiSafetyCenterDismissedIssues).isEmpty()
@@ -2202,14 +2253,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
@@ -2224,7 +2275,7 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_1,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
)
)
}
@@ -2240,14 +2291,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SOURCE_ID_5, CRITICAL_ISSUE_ID)
@@ -2262,7 +2313,7 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_1,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
)
)
}
@@ -2282,14 +2333,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
@@ -2303,21 +2354,17 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
)
- waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
- val hasResurfaced =
- safetyCenterManager
- .getSafetyCenterDataWithPermission()
- .issues
- .contains(
- safetyCenterTestData.safetyCenterIssueCritical(
- SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2
- )
+ assertWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
+ assertThat(safetyCenterManager.getSafetyCenterDataWithPermission().issues)
+ .contains(
+ safetyCenterTestData.safetyCenterIssueCritical(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
- hasResurfaced
+ )
}
}
@@ -2329,13 +2376,13 @@ class SafetyCenterManagerTest {
mapOf(
SEVERITY_LEVEL_INFORMATION to 0L,
SEVERITY_LEVEL_RECOMMENDATION to 99L,
- SEVERITY_LEVEL_CRITICAL_WARNING to 0L
+ SEVERITY_LEVEL_CRITICAL_WARNING to 0L,
)
SafetyCenterFlags.resurfaceIssueDelays =
mapOf(
SEVERITY_LEVEL_INFORMATION to Duration.ZERO,
SEVERITY_LEVEL_RECOMMENDATION to RESURFACE_DELAY,
- SEVERITY_LEVEL_CRITICAL_WARNING to Duration.ZERO
+ SEVERITY_LEVEL_CRITICAL_WARNING to Duration.ZERO,
)
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -2345,14 +2392,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
@@ -2366,7 +2413,7 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueRecommendation(
SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
)
waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
@@ -2377,7 +2424,7 @@ class SafetyCenterManagerTest {
.contains(
safetyCenterTestData.safetyCenterIssueRecommendation(
SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
)
hasResurfaced
@@ -2391,13 +2438,13 @@ class SafetyCenterManagerTest {
mapOf(
SEVERITY_LEVEL_INFORMATION to 0L,
SEVERITY_LEVEL_RECOMMENDATION to 99L,
- SEVERITY_LEVEL_CRITICAL_WARNING to 99L
+ SEVERITY_LEVEL_CRITICAL_WARNING to 99L,
)
SafetyCenterFlags.resurfaceIssueDelays =
mapOf(
SEVERITY_LEVEL_INFORMATION to Duration.ZERO,
SEVERITY_LEVEL_RECOMMENDATION to RESURFACE_DELAY,
- SEVERITY_LEVEL_CRITICAL_WARNING to RESURFACE_DELAY.multipliedBy(100)
+ SEVERITY_LEVEL_CRITICAL_WARNING to RESURFACE_DELAY.multipliedBy(100),
)
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -2407,14 +2454,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
@@ -2427,7 +2474,7 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_1,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
)
)
assertFailsWith(TimeoutCancellationException::class) {
@@ -2439,7 +2486,7 @@ class SafetyCenterManagerTest {
.contains(
safetyCenterTestData.safetyCenterIssueRecommendation(
SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
)
hasResurfaced
@@ -2454,13 +2501,13 @@ class SafetyCenterManagerTest {
mapOf(
SEVERITY_LEVEL_INFORMATION to 0L,
SEVERITY_LEVEL_RECOMMENDATION to 99L,
- SEVERITY_LEVEL_CRITICAL_WARNING to 99L
+ SEVERITY_LEVEL_CRITICAL_WARNING to 99L,
)
SafetyCenterFlags.resurfaceIssueDelays =
mapOf(
SEVERITY_LEVEL_INFORMATION to Duration.ZERO,
SEVERITY_LEVEL_RECOMMENDATION to RESURFACE_DELAY.multipliedBy(100),
- SEVERITY_LEVEL_CRITICAL_WARNING to RESURFACE_DELAY
+ SEVERITY_LEVEL_CRITICAL_WARNING to RESURFACE_DELAY,
)
safetyCenterTestHelper.setConfig(
safetyCenterTestConfigs.multipleSourcesWithDeduplicationInfoConfig
@@ -2470,14 +2517,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
@@ -2490,7 +2537,7 @@ class SafetyCenterManagerTest {
.containsExactly(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_1,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
)
)
waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
@@ -2501,7 +2548,7 @@ class SafetyCenterManagerTest {
.contains(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_1,
- groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1,
)
)
hasResurfaced
@@ -2520,14 +2567,14 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
// Belongs to DEDUPLICATION_GROUP_1
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
val listener = safetyCenterTestHelper.addListener()
@@ -2545,7 +2592,7 @@ class SafetyCenterManagerTest {
.contains(
safetyCenterTestData.safetyCenterIssueCritical(
SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
)
hasResurfaced
@@ -2571,7 +2618,7 @@ class SafetyCenterManagerTest {
.setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DEVICE)
.build()
)
- .build()
+ .build(),
)
val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
@@ -2584,32 +2631,32 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.complexConfig)
safetyCenterTestHelper.setData(
DYNAMIC_BAREBONE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterTestHelper.setData(
DYNAMIC_DISABLED_ID,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(DYNAMIC_HIDDEN_ID, safetySourceTestData.unspecified)
safetyCenterTestHelper.setData(
DYNAMIC_HIDDEN_WITH_SEARCH_ID,
- safetySourceTestData.information
+ safetySourceTestData.information,
)
safetyCenterTestHelper.setData(
ISSUE_ONLY_BAREBONE_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.criticalResolvingGeneralIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.criticalResolvingGeneralIssue),
)
safetyCenterTestHelper.setData(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue),
)
safetyCenterTestHelper.setData(
DYNAMIC_IN_STATELESS_ID,
- safetySourceTestData.unspecifiedWithIssue
+ safetySourceTestData.unspecifiedWithIssue,
)
safetyCenterTestHelper.setData(
ISSUE_ONLY_IN_STATELESS_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue),
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -2623,50 +2670,50 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.summaryTestConfig)
safetyCenterManager.reportSafetySourceErrorWithPermission(
SOURCE_ID_1,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified"
- )
+ entrySummary = "unspecified",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_3,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
- entrySummary = "information without issues"
- )
+ entrySummary = "information without issues",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_4,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
entrySummary = "information with issue",
- withIssue = true
- )
+ withIssue = true,
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_5,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_RECOMMENDATION,
- entrySummary = "recommendation"
- )
+ entrySummary = "recommendation",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_6,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_CRITICAL_WARNING,
- entrySummary = "critical 1"
- )
+ entrySummary = "critical 1",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_7,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_CRITICAL_WARNING,
- entrySummary = "critical 2"
- )
+ entrySummary = "critical 2",
+ ),
)
// STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
@@ -2682,43 +2729,43 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.summaryTestConfig)
safetyCenterManager.reportSafetySourceErrorWithPermission(
SOURCE_ID_1,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified"
- )
+ entrySummary = "unspecified",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_3,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
- entrySummary = "information without issues"
- )
+ entrySummary = "information without issues",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_4,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
entrySummary = "information with issue",
- withIssue = true
- )
+ withIssue = true,
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_5,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_RECOMMENDATION,
- entrySummary = "recommendation 1"
- )
+ entrySummary = "recommendation 1",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_6,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_RECOMMENDATION,
- entrySummary = "recommendation 2"
- )
+ entrySummary = "recommendation 2",
+ ),
)
// SOURCE_ID_7 leave as an UNKNOWN dynamic entry
// STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
@@ -2737,52 +2784,52 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 1"
- )
+ entrySummary = "unspecified 1",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 2"
- )
+ entrySummary = "unspecified 2",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_3,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
- entrySummary = "information without issues 1"
- )
+ entrySummary = "information without issues 1",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_4,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
entrySummary = "information with issue 1",
- withIssue = true
- )
+ withIssue = true,
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_5,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
entrySummary = "information with issue 2",
- withIssue = true
- )
+ withIssue = true,
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_6,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
- entrySummary = "information without issues 2"
- )
+ entrySummary = "information without issues 2",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_7,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 3"
- )
+ entrySummary = "unspecified 3",
+ ),
)
// STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
@@ -2800,50 +2847,50 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 1"
- )
+ entrySummary = "unspecified 1",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 2"
- )
+ entrySummary = "unspecified 2",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_3,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
- entrySummary = "information without issues 1"
- )
+ entrySummary = "information without issues 1",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_4,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
- entrySummary = "information without issues 2"
- )
+ entrySummary = "information without issues 2",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_5,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 3"
- )
+ entrySummary = "unspecified 3",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_6,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
- entrySummary = "information without issues 3"
- )
+ entrySummary = "information without issues 3",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_7,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 4"
- )
+ entrySummary = "unspecified 4",
+ ),
)
// STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
@@ -2861,51 +2908,51 @@ class SafetyCenterManagerTest {
SOURCE_ID_1,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 1"
- )
+ entrySummary = "unspecified 1",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 2"
- )
+ entrySummary = "unspecified 2",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_3,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 3"
- )
+ entrySummary = "unspecified 3",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_4,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 4"
- )
+ entrySummary = "unspecified 4",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_5,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
entrySummary = "unspecified with issue",
- withIssue = true
- )
+ withIssue = true,
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_6,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 6"
- )
+ entrySummary = "unspecified 6",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_7,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified 7"
- )
+ entrySummary = "unspecified 7",
+ ),
)
// STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
@@ -2921,34 +2968,34 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.summaryTestConfig)
safetyCenterManager.reportSafetySourceErrorWithPermission(
SOURCE_ID_1,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
safetyCenterManager.reportSafetySourceErrorWithPermission(
SOURCE_ID_2,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
safetyCenterTestHelper.setData(
SOURCE_ID_3,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
entrySummary = "information with issue",
- withIssue = true
- )
+ withIssue = true,
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_4,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified"
- )
+ entrySummary = "unspecified",
+ ),
)
// SOURCE_ID_5 leave as an UNKNOWN dynamic entry
safetyCenterTestHelper.setData(
SOURCE_ID_6,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
- entrySummary = "information"
- )
+ entrySummary = "information",
+ ),
)
// SOURCE_ID_7 leave as an UNKNOWN dynamic entry
// STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
@@ -2965,7 +3012,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.summaryTestConfig)
safetyCenterManager.reportSafetySourceErrorWithPermission(
SOURCE_ID_1,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
// SOURCE_ID_2 leave as an UNKNOWN dynamic entry
safetyCenterTestHelper.setData(
@@ -2973,15 +3020,15 @@ class SafetyCenterManagerTest {
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
entrySummary = "information with issue",
- withIssue = true
- )
+ withIssue = true,
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_4,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified"
- )
+ entrySummary = "unspecified",
+ ),
)
// SOURCE_ID_5 leave as an UNKNOWN dynamic entry
// SOURCE_ID_6 leave as an UNKNOWN dynamic entry
@@ -2989,8 +3036,8 @@ class SafetyCenterManagerTest {
SOURCE_ID_7,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
- entrySummary = "information"
- )
+ entrySummary = "information",
+ ),
)
// STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
@@ -3011,22 +3058,22 @@ class SafetyCenterManagerTest {
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
entrySummary = "information with issue",
- withIssue = true
- )
+ withIssue = true,
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_4,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
- entrySummary = "unspecified"
- )
+ entrySummary = "unspecified",
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_5,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_INFORMATION,
- entrySummary = "information"
- )
+ entrySummary = "information",
+ ),
)
// SOURCE_ID_6 leave as an UNKNOWN dynamic entry
safetyCenterTestHelper.setData(
@@ -3034,8 +3081,8 @@ class SafetyCenterManagerTest {
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SEVERITY_LEVEL_UNSPECIFIED,
entrySummary = "unspecified with issue",
- withIssue = true
- )
+ withIssue = true,
+ ),
)
// STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
@@ -3083,7 +3130,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(
DYNAMIC_DISABLED_ID,
- safetySourceTestData.informationWithIssue
+ safetySourceTestData.informationWithIssue,
)
val informationWithIssueGroup =
@@ -3115,15 +3162,17 @@ class SafetyCenterManagerTest {
safetyCenterTestData.safetyCenterStatusUnknown,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
safetyCenterTestData
.safetyCenterEntryDefaultBuilder(SINGLE_SOURCE_ID)
.setPendingIntent(null)
.setEnabled(false)
- .build()
+ .build(),
+ "No info yet",
)
),
- emptyList()
+ emptyList(),
)
assertThat(safetyCenterData).isEqualTo(expectedSafetyCenterData)
}
@@ -3133,7 +3182,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceInvalidIntentConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.informationWithNullIntent
+ safetySourceTestData.informationWithNullIntent,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -3143,16 +3192,17 @@ class SafetyCenterManagerTest {
safetyCenterStatusOk,
emptyList(),
listOf(
- SafetyCenterEntryOrGroup(
+ safetyCenterTestData.singletonSafetyCenterEntryOrGroup(
+ SINGLE_SOURCE_GROUP_ID,
safetyCenterTestData
.safetyCenterEntryOkBuilder(SINGLE_SOURCE_ID)
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
.setPendingIntent(null)
.setEnabled(false)
- .build()
+ .build(),
)
),
- emptyList()
+ emptyList(),
)
assertThat(apiSafetyCenterData).isEqualTo(expectedSafetyCenterData)
}
@@ -3165,7 +3215,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
safetyCenterTestHelper.setData(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue),
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -3184,7 +3234,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
safetyCenterTestHelper.setData(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue),
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -3200,12 +3250,12 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.entryGroupWithIssueOnlyConfig)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
safetyCenterTestHelper.setData(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue),
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -3223,7 +3273,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
safetyCenterTestHelper.setData(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue),
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(ISSUE_ONLY_ALL_OPTIONAL_ID, RECOMMENDATION_ISSUE_ID)
@@ -3245,7 +3295,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
safetyCenterTestHelper.setData(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue)
+ SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue),
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
@@ -3287,7 +3337,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val safetyCenterDataFromListener = listener.receiveSafetyCenterData()
@@ -3313,7 +3363,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
@@ -3334,7 +3384,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(SINGLE_SOURCE_ID, CRITICAL_ISSUE_ID)
@@ -3350,7 +3400,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val safetyCenterDataAfterSourcePushesDismissedIssueAgain =
@@ -3364,14 +3414,14 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- issueTypeId = "some_other_issue_type_id"
+ issueTypeId = "some_other_issue_type_id",
)
)
@@ -3388,11 +3438,11 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.recommendationDismissPendingIntentIssue
+ safetySourceTestData.recommendationDismissPendingIntentIssue,
)
SafetySourceReceiver.setResponse(
Request.DismissIssue(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
val listener = safetyCenterTestHelper.addListener()
@@ -3446,15 +3496,11 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueId(SINGLE_SOURCE_ID, INFORMATION_ISSUE_ID)
)
- val expectedSafetyCenterData =
- safetyCenterDataOk.withDismissedIssuesIfAtLeastU(
- listOf(safetyCenterTestData.safetyCenterIssueInformation(SINGLE_SOURCE_ID))
- )
assertFailsWith(TimeoutCancellationException::class) {
waitForWithTimeout(timeout = TIMEOUT_SHORT) {
val hasResurfaced =
safetyCenterManager.getSafetyCenterDataWithPermission() !=
- expectedSafetyCenterData
+ safetyCenterDataOkOneDismissedAlert
hasResurfaced
}
}
@@ -3466,13 +3512,13 @@ class SafetyCenterManagerTest {
mapOf(
SEVERITY_LEVEL_INFORMATION to 0L,
SEVERITY_LEVEL_RECOMMENDATION to 99L,
- SEVERITY_LEVEL_CRITICAL_WARNING to 99L
+ SEVERITY_LEVEL_CRITICAL_WARNING to 99L,
)
SafetyCenterFlags.resurfaceIssueDelays =
mapOf(
SEVERITY_LEVEL_INFORMATION to Duration.ZERO,
SEVERITY_LEVEL_RECOMMENDATION to Duration.ZERO,
- SEVERITY_LEVEL_CRITICAL_WARNING to Duration.ZERO
+ SEVERITY_LEVEL_CRITICAL_WARNING to Duration.ZERO,
)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, safetySourceTestData.informationWithIssue)
@@ -3483,15 +3529,11 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueId(SINGLE_SOURCE_ID, INFORMATION_ISSUE_ID)
)
- val expectedSafetyCenterData =
- safetyCenterDataOk.withDismissedIssuesIfAtLeastU(
- listOf(safetyCenterTestData.safetyCenterIssueInformation(SINGLE_SOURCE_ID))
- )
assertFailsWith(TimeoutCancellationException::class) {
waitForWithTimeout(timeout = TIMEOUT_SHORT) {
val hasResurfaced =
safetyCenterManager.getSafetyCenterDataWithPermission() !=
- expectedSafetyCenterData
+ safetyCenterDataOkOneDismissedAlert
hasResurfaced
}
}
@@ -3503,18 +3545,18 @@ class SafetyCenterManagerTest {
mapOf(
SEVERITY_LEVEL_INFORMATION to 0L,
SEVERITY_LEVEL_RECOMMENDATION to 0L,
- SEVERITY_LEVEL_CRITICAL_WARNING to 2L
+ SEVERITY_LEVEL_CRITICAL_WARNING to 2L,
)
SafetyCenterFlags.resurfaceIssueDelays =
mapOf(
SEVERITY_LEVEL_INFORMATION to RESURFACE_DELAY,
SEVERITY_LEVEL_RECOMMENDATION to RESURFACE_DELAY,
- SEVERITY_LEVEL_CRITICAL_WARNING to Duration.ZERO
+ SEVERITY_LEVEL_CRITICAL_WARNING to Duration.ZERO,
)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingDeviceIssue
+ safetySourceTestData.criticalWithResolvingDeviceIssue,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
checkState(apiSafetyCenterData == safetyCenterDataDeviceCriticalOneAlert)
@@ -3559,18 +3601,18 @@ class SafetyCenterManagerTest {
mapOf(
SEVERITY_LEVEL_INFORMATION to 0L,
SEVERITY_LEVEL_RECOMMENDATION to 99L,
- SEVERITY_LEVEL_CRITICAL_WARNING to 0L
+ SEVERITY_LEVEL_CRITICAL_WARNING to 0L,
)
SafetyCenterFlags.resurfaceIssueDelays =
mapOf(
SEVERITY_LEVEL_INFORMATION to Duration.ZERO,
SEVERITY_LEVEL_RECOMMENDATION to RESURFACE_DELAY,
- SEVERITY_LEVEL_CRITICAL_WARNING to Duration.ZERO
+ SEVERITY_LEVEL_CRITICAL_WARNING to Duration.ZERO,
)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.recommendationWithDeviceIssue
+ safetySourceTestData.recommendationWithDeviceIssue,
)
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
checkState(apiSafetyCenterData == safetyCenterDataDeviceRecommendationOneAlert)
@@ -3598,12 +3640,12 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.executeSafetyCenterIssueActionWithPermissionAndWait(
@@ -3611,8 +3653,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
val safetyCenterDataFromListenerDuringResolveAction = listener.receiveSafetyCenterData()
@@ -3627,7 +3669,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
SafetySourceReceiver.setResponse(Request.ResolveAction(SINGLE_SOURCE_ID), Response.Error)
@@ -3637,8 +3679,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
val safetyCenterDataFromListenerDuringResolveAction = listener.receiveSafetyCenterData()
@@ -3662,7 +3704,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
@@ -3671,8 +3713,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
val safetyCenterDataFromListenerDuringResolveAction = listener.receiveSafetyCenterData()
@@ -3696,7 +3738,7 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
safetyCenterManager.executeSafetyCenterIssueActionWithPermissionAndWait(
@@ -3704,8 +3746,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
listener.receiveSafetyCenterData()
listener.receiveSafetyCenterData()
@@ -3713,7 +3755,7 @@ class SafetyCenterManagerTest {
SafetyCenterFlags.resolveActionTimeout = TIMEOUT_LONG
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
safetyCenterManager.executeSafetyCenterIssueActionWithPermissionAndWait(
@@ -3721,8 +3763,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
val safetyCenterDataFromListenerDuringResolveAction = listener.receiveSafetyCenterData()
@@ -3737,11 +3779,11 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
val issueId = SafetyCenterTestData.issueId(SINGLE_SOURCE_ID, CRITICAL_ISSUE_ID)
val listener = safetyCenterTestHelper.addListener()
@@ -3754,8 +3796,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
if (SdkLevel.isAtLeastU()) {
@@ -3775,12 +3817,12 @@ class SafetyCenterManagerTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
val listener = safetyCenterTestHelper.addListener()
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
assertFailsWith(IllegalArgumentException::class) {
safetyCenterManager.executeSafetyCenterIssueActionWithPermissionAndWait(
@@ -3788,9 +3830,9 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID + "invalid",
- CRITICAL_ISSUE_ACTION_ID
+ CRITICAL_ISSUE_ACTION_ID,
),
- TIMEOUT_SHORT
+ TIMEOUT_SHORT,
)
}
@@ -3799,8 +3841,8 @@ class SafetyCenterManagerTest {
SafetyCenterTestData.issueActionId(
SINGLE_SOURCE_ID,
CRITICAL_ISSUE_ID,
- CRITICAL_ISSUE_ACTION_ID
- )
+ CRITICAL_ISSUE_ACTION_ID,
+ ),
)
val safetyCenterDataFromListenerDuringResolveAction = listener.receiveSafetyCenterData()
@@ -3891,7 +3933,7 @@ class SafetyCenterManagerTest {
safetyCenterManager.reportSafetySourceErrorWithPermission(
SINGLE_SOURCE_ID,
- SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED),
)
val lastUpdated = dumpLastUpdated()
@@ -3941,9 +3983,9 @@ class SafetyCenterManagerTest {
companion object {
private val RESURFACE_DELAY = Duration.ofMillis(500)
- // Wait 3 times the RESURFACE_DELAY before asserting whether an issue has or has not
+ // Wait 5 times the RESURFACE_DELAY before asserting whether an issue has or has not
// resurfaced. Use a constant additive error buffer if we increase the delay considerably.
- private val RESURFACE_TIMEOUT = RESURFACE_DELAY.multipliedBy(3)
+ private val RESURFACE_TIMEOUT = RESURFACE_DELAY.multipliedBy(5)
// Check more than once during a RESURFACE_DELAY before asserting whether an issue has or
// has not resurfaced. Use a different check logic (focused at the expected resurface time)
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt
index 1678ccced..ce75b00f5 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt
@@ -116,7 +116,7 @@ class SafetyCenterNotificationTest {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.recommendationWithAccountIssue
+ safetySourceTestData.recommendationWithAccountIssue,
)
TestNotificationListener.waitForZeroNotifications()
@@ -128,7 +128,7 @@ class SafetyCenterNotificationTest {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.recommendationWithAccountIssue
+ safetySourceTestData.recommendationWithAccountIssue,
)
TestNotificationListener.waitForZeroNotifications()
@@ -140,7 +140,7 @@ class SafetyCenterNotificationTest {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.recommendationWithAccountIssue
+ safetySourceTestData.recommendationWithAccountIssue,
)
TestNotificationListener.waitForZeroNotifications()
@@ -401,7 +401,7 @@ class SafetyCenterNotificationTest {
SafetySourceIssue.Action.Builder(
"default_action",
"Default action",
- safetySourceTestData.createTestActivityRedirectPendingIntent()
+ safetySourceTestData.createTestActivityRedirectPendingIntent(),
)
.build()
)
@@ -440,7 +440,7 @@ class SafetyCenterNotificationTest {
SafetySourceIssue.Action.Builder(
"default_action",
"Default action",
- safetySourceTestData.createTestActivityRedirectPendingIntent()
+ safetySourceTestData.createTestActivityRedirectPendingIntent(),
)
.build()
)
@@ -497,7 +497,7 @@ class SafetyCenterNotificationTest {
"New action",
safetySourceTestData.createTestActivityRedirectPendingIntent(
identifier = "new_action"
- )
+ ),
)
.build()
)
@@ -697,13 +697,13 @@ class SafetyCenterNotificationTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
TestNotificationListener.waitForNotificationsMatching(
@@ -728,7 +728,7 @@ class SafetyCenterNotificationTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
val notificationWithChannel =
@@ -745,7 +745,7 @@ class SafetyCenterNotificationTest {
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
TestNotificationListener.waitForNotificationsMatching(
@@ -762,7 +762,7 @@ class SafetyCenterNotificationTest {
fun setSafetySourceData_withInformationIssue_lowImportanceBlockableNotification() {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.informationWithIssue
+ safetySourceTestData.informationWithIssue,
)
TestNotificationListener.waitForNotificationsMatching(
@@ -781,7 +781,7 @@ class SafetyCenterNotificationTest {
fun setSafetySourceData_withRecommendationIssue_defaultImportanceUnblockableNotification() {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.recommendationWithAccountIssue
+ safetySourceTestData.recommendationWithAccountIssue,
)
TestNotificationListener.waitForNotificationsMatching(
@@ -800,7 +800,7 @@ class SafetyCenterNotificationTest {
fun setSafetySourceData_withCriticalIssue_highImportanceUnblockableNotification() {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.criticalWithResolvingDeviceIssue(sourceId = uniqueSafetySourceId)
+ safetySourceTestData.criticalWithResolvingDeviceIssue(sourceId = uniqueSafetySourceId),
)
TestNotificationListener.waitForNotificationsMatching(
@@ -819,7 +819,7 @@ class SafetyCenterNotificationTest {
fun dismissSafetyCenterIssue_dismissesNotification() {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.recommendationWithAccountIssue
+ safetySourceTestData.recommendationWithAccountIssue,
)
TestNotificationListener.waitForSingleNotificationMatching(
@@ -832,7 +832,7 @@ class SafetyCenterNotificationTest {
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterTestData.issueId(
uniqueSafetySourceId,
- SafetySourceTestData.RECOMMENDATION_ISSUE_ID
+ SafetySourceTestData.RECOMMENDATION_ISSUE_ID,
)
)
@@ -843,7 +843,7 @@ class SafetyCenterNotificationTest {
fun dismissingNotification_doesNotUpdateSafetyCenterData() {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId)
+ safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId),
)
// Add the listener after setting the initial data so that we don't need to consume/receive
// an update for that
@@ -876,13 +876,13 @@ class SafetyCenterNotificationTest {
SOURCE_ID_1,
SafetySourceTestData.issuesOnly(
safetySourceTestData.criticalIssueWithDeduplicationId("same")
- )
+ ),
)
safetyCenterTestHelper.setData(
SOURCE_ID_5,
SafetySourceTestData.issuesOnly(
safetySourceTestData.recommendationIssueWithDeduplicationId("same")
- )
+ ),
)
val notificationWithChannel =
@@ -926,7 +926,7 @@ class SafetyCenterNotificationTest {
fun sendActionPendingIntent_successful_updatesListener() {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId)
+ safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId),
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -940,7 +940,7 @@ class SafetyCenterNotificationTest {
checkNotNull(action) { "Notification action unexpectedly null" }
SafetySourceReceiver.setResponse(
Request.ResolveAction(uniqueSafetySourceId),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
val listener = safetyCenterTestHelper.addListener()
@@ -958,7 +958,7 @@ class SafetyCenterNotificationTest {
fun sendActionPendingIntent_successfulNoSuccessMessage_removesNotification() {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId)
+ safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId),
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -972,7 +972,7 @@ class SafetyCenterNotificationTest {
checkNotNull(action) { "Notification action unexpectedly null" }
SafetySourceReceiver.setResponse(
Request.ResolveAction(uniqueSafetySourceId),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
sendActionPendingIntentAndWaitWithPermission(action)
@@ -986,7 +986,7 @@ class SafetyCenterNotificationTest {
uniqueSafetySourceId,
safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage(
sourceId = uniqueSafetySourceId
- )
+ ),
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -1000,7 +1000,7 @@ class SafetyCenterNotificationTest {
checkNotNull(action) { "Notification action unexpectedly null" }
SafetySourceReceiver.setResponse(
Request.ResolveAction(uniqueSafetySourceId),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
sendActionPendingIntentAndWaitWithPermission(action)
@@ -1017,7 +1017,7 @@ class SafetyCenterNotificationTest {
uniqueSafetySourceId,
safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage(
sourceId = uniqueSafetySourceId
- )
+ ),
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -1031,7 +1031,7 @@ class SafetyCenterNotificationTest {
checkNotNull(action) { "Notification action unexpectedly null" }
SafetySourceReceiver.setResponse(
Request.ResolveAction(uniqueSafetySourceId),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
sendActionPendingIntentAndWaitWithPermission(action)
@@ -1049,7 +1049,7 @@ class SafetyCenterNotificationTest {
uniqueSafetySourceId,
safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage(
sourceId = uniqueSafetySourceId
- )
+ ),
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -1063,7 +1063,7 @@ class SafetyCenterNotificationTest {
checkNotNull(action) { "Notification action unexpectedly null" }
SafetySourceReceiver.setResponse(
Request.ResolveAction(uniqueSafetySourceId),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
SafetyCenterFlags.notificationsEnabled = false
@@ -1078,7 +1078,7 @@ class SafetyCenterNotificationTest {
uniqueSafetySourceId,
safetySourceTestData.criticalWithResolvingIssueWithSuccessMessage(
sourceId = uniqueSafetySourceId
- )
+ ),
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -1095,8 +1095,8 @@ class SafetyCenterNotificationTest {
Response.SetData(
safetySourceTestData.information,
overrideSafetyEvent =
- SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build()
- )
+ SafetyEvent.Builder(SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build(),
+ ),
)
sendActionPendingIntentAndWaitWithPermission(action)
@@ -1118,8 +1118,8 @@ class SafetyCenterNotificationTest {
"Solve now!",
safetySourceTestData.resolvingActionPendingIntent(
sourceId = uniqueSafetySourceId,
- sourceIssueActionId = "notification_action_id"
- )
+ sourceIssueActionId = "notification_action_id",
+ ),
)
.setWillResolve(true)
.setSuccessMessage("Solved via notification action :)")
@@ -1140,8 +1140,8 @@ class SafetyCenterNotificationTest {
"Default action",
safetySourceTestData.resolvingActionPendingIntent(
sourceId = uniqueSafetySourceId,
- sourceIssueActionId = "issue_action_id"
- )
+ sourceIssueActionId = "issue_action_id",
+ ),
)
.setWillResolve(true)
.setSuccessMessage("Solved via issue action :(")
@@ -1168,7 +1168,7 @@ class SafetyCenterNotificationTest {
checkNotNull(action) { "Notification action unexpectedly null" }
SafetySourceReceiver.setResponse(
Request.ResolveAction(uniqueSafetySourceId),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
sendActionPendingIntentAndWaitWithPermission(action)
@@ -1185,7 +1185,7 @@ class SafetyCenterNotificationTest {
// to resolve that action successfully.
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId)
+ safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = uniqueSafetySourceId),
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -1199,7 +1199,7 @@ class SafetyCenterNotificationTest {
checkNotNull(action) { "Notification action unexpectedly null" }
SafetySourceReceiver.setResponse(
Request.ResolveAction(uniqueSafetySourceId),
- Response.Error
+ Response.Error,
)
val listener = safetyCenterTestHelper.addListener()
@@ -1224,7 +1224,7 @@ class SafetyCenterNotificationTest {
fun sendContentPendingIntent_singleIssue_opensSafetyCenterWithIssueVisible() {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.recommendationWithDeviceIssue
+ safetySourceTestData.recommendationWithDeviceIssue,
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -1245,11 +1245,11 @@ class SafetyCenterNotificationTest {
setFlagsForImmediateNotifications(SOURCE_ID_1)
safetyCenterTestHelper.setData(
SOURCE_ID_1,
- safetySourceTestData.recommendationWithDeviceIssue
+ safetySourceTestData.recommendationWithDeviceIssue,
)
safetyCenterTestHelper.setData(
SOURCE_ID_2,
- safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = SOURCE_ID_2)
+ safetySourceTestData.criticalWithResolvingGeneralIssue(sourceId = SOURCE_ID_2),
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -1269,7 +1269,7 @@ class SafetyCenterNotificationTest {
fun whenGreenIssue_notificationHasAutoCancel() {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.informationWithIssue
+ safetySourceTestData.informationWithIssue,
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -1286,7 +1286,7 @@ class SafetyCenterNotificationTest {
fun whenNotGreenIssue_notificationDoesntHaveAutoCancel() {
safetyCenterTestHelper.setData(
uniqueSafetySourceId,
- safetySourceTestData.recommendationWithDeviceIssue
+ safetySourceTestData.recommendationWithDeviceIssue,
)
val notificationWithChannel =
TestNotificationListener.waitForSingleNotificationMatching(
@@ -1305,7 +1305,7 @@ class SafetyCenterNotificationTest {
fun sendActionPendingIntentAndWaitWithPermission(
action: Notification.Action,
- timeout: Duration = Coroutines.TIMEOUT_LONG
+ timeout: Duration = Coroutines.TIMEOUT_LONG,
) {
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
PendingIntentSender.send(action.actionIntent)
@@ -1328,13 +1328,13 @@ class SafetyCenterNotificationTest {
fun sendContentPendingIntent(
statusBarNotificationWithChannel: StatusBarNotificationWithChannel,
- andExecuteBlock: () -> Unit = {}
+ andExecuteBlock: () -> Unit = {},
) {
val contentIntent =
statusBarNotificationWithChannel.statusBarNotification.notification.contentIntent
executeBlockAndExit(
launchActivity = { PendingIntentSender.send(contentIntent) },
- block = andExecuteBlock
+ block = andExecuteBlock,
)
}
}
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetySourceDataFixesTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetySourceDataFixesTest.kt
index 4ba293eb9..5cafe3d48 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetySourceDataFixesTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetySourceDataFixesTest.kt
@@ -105,7 +105,7 @@ class SafetySourceDataFixesTest {
.addAction(
safetySourceTestData.action(
id = targetActionId,
- pendingIntent = originalPendingIntent
+ pendingIntent = originalPendingIntent,
)
)
.build()
@@ -126,6 +126,7 @@ class SafetySourceDataFixesTest {
)
assertThat(intentsFilterEqual(overriddenPendingIntent, expectedPendingIntent)).isTrue()
}
+
@Test
@SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
fun defaultActionOverride_notification_overridesMatchingActions() {
@@ -143,7 +144,7 @@ class SafetySourceDataFixesTest {
notification(
safetySourceTestData.action(
id = targetActionId,
- pendingIntent = originalPendingIntent
+ pendingIntent = originalPendingIntent,
)
)
)
@@ -183,7 +184,7 @@ class SafetySourceDataFixesTest {
.addAction(
safetySourceTestData.action(
id = targetActionId,
- pendingIntent = originalPendingIntent
+ pendingIntent = originalPendingIntent,
)
)
.build()
@@ -192,7 +193,7 @@ class SafetySourceDataFixesTest {
safetyCenterTestHelper.setData(
SOURCE_ID_2, // Different source ID
- dataWithoutActionToOverride
+ dataWithoutActionToOverride,
)
val actualPendingIntent =
@@ -219,7 +220,7 @@ class SafetySourceDataFixesTest {
.addAction(
safetySourceTestData.action(
id = "DifferentActionId",
- pendingIntent = originalPendingIntent
+ pendingIntent = originalPendingIntent,
)
)
.build()
@@ -253,7 +254,7 @@ class SafetySourceDataFixesTest {
.addAction(
safetySourceTestData.action(
id = targetActionId,
- pendingIntent = originalPendingIntent
+ pendingIntent = originalPendingIntent,
)
)
.build()
@@ -289,7 +290,7 @@ class SafetySourceDataFixesTest {
private fun intentsFilterEqual(
actualPendingIntent: PendingIntent,
- expectedPendingIntent: PendingIntent?
+ expectedPendingIntent: PendingIntent?,
) =
callWithShellPermissionIdentity("android.permission.GET_INTENT_SENDER_INTENT") {
actualPendingIntent.intentFilterEquals(expectedPendingIntent)
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt
index 73d6a0737..1d971194e 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterQsActivityTest.kt
@@ -46,12 +46,12 @@ class SafetyCenterQsActivityTest {
private val safetyCenterTestHelper = SafetyCenterTestHelper(context)
private val safetyCenterTestConfigs = SafetyCenterTestConfigs(context)
+ @get:Rule(order = 0) val freezeRotationRule = FreezeRotationRule()
@get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
@get:Rule(order = 2) val enableCameraRule = EnableSensorRule(context, CAMERA)
@get:Rule(order = 3) val enableMicrophoneRule = EnableSensorRule(context, MICROPHONE)
@get:Rule(order = 4) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
@get:Rule(order = 5) val disableAnimationRule = DisableAnimationRule()
- @get:Rule(order = 6) val freezeRotationRule = FreezeRotationRule()
@Before
fun setTestConfigBeforeTest() {
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt
index f76a52256..6f147dfbf 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/ui/SafetyCenterStatusCardTest.kt
@@ -69,7 +69,7 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity {
waitAllTextDisplayed(
safetyCenterResourcesApk.getStringByName("scanning_title"),
- safetyCenterResourcesApk.getStringByName("loading_summary")
+ safetyCenterResourcesApk.getStringByName("loading_summary"),
)
}
}
@@ -79,13 +79,13 @@ class SafetyCenterStatusCardTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.informationWithIconAction
+ safetySourceTestData.informationWithIconAction,
)
context.launchSafetyCenterActivity {
waitAllTextDisplayed(
safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesApk.getStringByName("loading_summary")
+ safetyCenterResourcesApk.getStringByName("loading_summary"),
)
}
}
@@ -98,7 +98,7 @@ class SafetyCenterStatusCardTest {
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_review_title"),
- safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_review_summary")
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_review_summary"),
)
waitButtonDisplayed(RESCAN_BUTTON_LABEL)
}
@@ -110,13 +110,13 @@ class SafetyCenterStatusCardTest {
preSetDataOnT(SINGLE_SOURCE_ID, safetySourceTestData.information)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary")
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary"),
)
waitButtonDisplayed(RESCAN_BUTTON_LABEL)
}
@@ -128,7 +128,7 @@ class SafetyCenterStatusCardTest {
preSetDataOnT(SINGLE_SOURCE_ID, safetySourceTestData.information)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
@@ -143,13 +143,13 @@ class SafetyCenterStatusCardTest {
preSetDataOnT(SINGLE_SOURCE_ID, safetySourceTestData.informationWithIssue)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.informationWithIssue)
+ Response.SetData(safetySourceTestData.informationWithIssue),
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
waitButtonNotDisplayed(RESCAN_BUTTON_LABEL)
}
@@ -160,11 +160,11 @@ class SafetyCenterStatusCardTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.recommendationWithGeneralIssue
+ safetySourceTestData.recommendationWithGeneralIssue,
)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.recommendationWithGeneralIssue)
+ Response.SetData(safetySourceTestData.recommendationWithGeneralIssue),
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
@@ -172,7 +172,7 @@ class SafetyCenterStatusCardTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_safety_recommendation_title"
),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
waitButtonNotDisplayed(RESCAN_BUTTON_LABEL)
}
@@ -183,11 +183,11 @@ class SafetyCenterStatusCardTest {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
safetyCenterTestHelper.setData(
SINGLE_SOURCE_ID,
- safetySourceTestData.criticalWithResolvingGeneralIssue
+ safetySourceTestData.criticalWithResolvingGeneralIssue,
)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue)
+ Response.SetData(safetySourceTestData.criticalWithResolvingGeneralIssue),
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
@@ -195,7 +195,7 @@ class SafetyCenterStatusCardTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_critical_safety_warning_title"
),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
waitButtonNotDisplayed(RESCAN_BUTTON_LABEL)
}
@@ -207,20 +207,20 @@ class SafetyCenterStatusCardTest {
preSetDataOnT(SINGLE_SOURCE_ID, safetySourceTestData.information)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary")
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary"),
)
waitButtonDisplayed(RESCAN_BUTTON_LABEL) { it.click() }
waitAllTextDisplayed(
safetyCenterResourcesApk.getStringByName("scanning_title"),
- safetyCenterResourcesApk.getStringByName("loading_summary")
+ safetyCenterResourcesApk.getStringByName("loading_summary"),
)
}
}
@@ -231,17 +231,17 @@ class SafetyCenterStatusCardTest {
preSetDataOnT(SINGLE_SOURCE_ID, safetySourceTestData.information)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.information)
+ Response.SetData(safetySourceTestData.information),
)
SafetySourceReceiver.setResponse(
Request.Rescan(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceTestData.recommendationWithGeneralIssue)
+ Response.SetData(safetySourceTestData.recommendationWithGeneralIssue),
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
waitAllTextDisplayed(
safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
- safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary")
+ safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_summary"),
)
waitButtonDisplayed(RESCAN_BUTTON_LABEL) { it.click() }
@@ -250,7 +250,7 @@ class SafetyCenterStatusCardTest {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_safety_recommendation_title"
),
- safetyCenterTestData.getAlertString(1)
+ safetyCenterTestData.getAlertString(1),
)
}
}
diff --git a/tests/functional/safetycenter/subpages/AndroidTest.xml b/tests/functional/safetycenter/subpages/AndroidTest.xml
index c3245e9d7..ac493841f 100644
--- a/tests/functional/safetycenter/subpages/AndroidTest.xml
+++ b/tests/functional/safetycenter/subpages/AndroidTest.xml
@@ -47,6 +47,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
<option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
diff --git a/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt b/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt
index 7ad83b949..c3e104504 100644
--- a/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt
+++ b/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/PrivacySubpageTest.kt
@@ -47,9 +47,9 @@ import com.android.safetycenter.testing.UiTestHelper.waitAllTextDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitAllTextNotDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitButtonDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitDisplayed
-import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueNotDisplayed
+import java.util.regex.Pattern
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -98,7 +98,7 @@ class PrivacySubpageTest {
context.getString(firstSource.summaryResId),
"Controls",
context.getString(lastSource.titleResId),
- context.getString(lastSource.summaryResId)
+ context.getString(lastSource.summaryResId),
)
}
}
@@ -117,7 +117,7 @@ class PrivacySubpageTest {
waitButtonDisplayed("Exit test activity") { it.click() }
waitAllTextDisplayed(
context.getString(source.titleResId),
- context.getString(source.summaryResId)
+ context.getString(source.summaryResId),
)
}
}
@@ -156,16 +156,16 @@ class PrivacySubpageTest {
context.launchSafetyCenterActivity(extras) {
waitAllText(
displayed = sensorPrivacyManager.supportsSensorToggle(CAMERA),
- text = "Camera access"
+ text = "Camera access",
)
waitAllText(
displayed = sensorPrivacyManager.supportsSensorToggle(MICROPHONE),
- text = "Microphone access"
+ text = "Microphone access",
)
waitAllTextDisplayed("Show clipboard access")
waitAllText(
displayed = getPermissionControllerBool("config_display_show_password_toggle"),
- text = "Show passwords"
+ text = "Show passwords",
)
waitAllTextDisplayed("Location access")
}
@@ -179,16 +179,14 @@ class PrivacySubpageTest {
val source: SafetySource = sourcesGroup.safetySources.first()
val extras = Bundle()
extras.putString(EXTRA_SAFETY_SOURCES_GROUP_ID, sourcesGroup.id)
+ val containsLocationPattern = Pattern.compile(".*[Ll]ocation.*") // NOTYPO
context.launchSafetyCenterActivity(extras) {
- openPageAndExit("Location access") {
- waitPageTitleDisplayed("Location")
- waitAllTextDisplayed("Use location")
- }
+ openPageAndExit("Location access") { waitDisplayed(By.text(containsLocationPattern)) }
waitAllTextDisplayed(
context.getString(source.titleResId),
- context.getString(source.summaryResId)
+ context.getString(source.summaryResId),
)
}
}
@@ -225,7 +223,7 @@ class PrivacySubpageTest {
permissionControllerContext.resources.getIdentifier(
resourceName,
"bool",
- "com.android.permissioncontroller"
+ "com.android.permissioncontroller",
)
return permissionControllerContext.resources.getBoolean(resourceId)
}
diff --git a/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt b/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt
index 236beb34e..5c770cc92 100644
--- a/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt
+++ b/tests/functional/safetycenter/subpages/src/android/safetycenter/functional/ui/SafetyCenterSubpagesTest.kt
@@ -160,7 +160,7 @@ class SafetyCenterSubpagesTest {
context.getString(firstGroup.titleResId),
context.getString(firstGroup.summaryResId),
context.getString(lastGroup.titleResId),
- context.getString(lastGroup.summaryResId)
+ context.getString(lastGroup.summaryResId),
)
openPageAndExit(context.getString(lastGroup.titleResId)) {
@@ -191,7 +191,7 @@ class SafetyCenterSubpagesTest {
waitAllTextDisplayed(
context.getString(firstGroup.titleResId),
context.getString(firstGroup.summaryResId),
- context.getString(lastGroup.titleResId)
+ context.getString(lastGroup.titleResId),
)
waitDisplayed(By.text(context.getString(lastGroup.summaryResId))) { it.click() }
@@ -232,24 +232,24 @@ class SafetyCenterSubpagesTest {
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SafetySourceData.SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_1_TITLE,
- entrySummary = SAFETY_SOURCE_1_SUMMARY
- )
+ entrySummary = SAFETY_SOURCE_1_SUMMARY,
+ ),
)
setData(
SOURCE_ID_2,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SafetySourceData.SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_2_TITLE,
- entrySummary = SAFETY_SOURCE_2_SUMMARY
- )
+ entrySummary = SAFETY_SOURCE_2_SUMMARY,
+ ),
)
setData(
SOURCE_ID_3,
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SafetySourceData.SEVERITY_LEVEL_INFORMATION,
entryTitle = SAFETY_SOURCE_3_TITLE,
- entrySummary = SAFETY_SOURCE_3_SUMMARY
- )
+ entrySummary = SAFETY_SOURCE_3_SUMMARY,
+ ),
)
}
val firstGroup = safetyCenterTestConfigs.multipleSourcesConfig.safetySourcesGroups[0]
@@ -263,7 +263,7 @@ class SafetyCenterSubpagesTest {
SAFETY_SOURCE_1_TITLE,
SAFETY_SOURCE_1_SUMMARY,
SAFETY_SOURCE_2_TITLE,
- SAFETY_SOURCE_2_SUMMARY
+ SAFETY_SOURCE_2_SUMMARY,
)
}
@@ -287,7 +287,7 @@ class SafetyCenterSubpagesTest {
waitButtonDisplayed("Exit test activity") { it.click() }
waitAllTextDisplayed(
context.getString(source.titleResId),
- context.getString(source.summaryResId)
+ context.getString(source.summaryResId),
)
}
}
@@ -370,7 +370,7 @@ class SafetyCenterSubpagesTest {
openPageAndExit(context.getString(sourcesGroup.titleResId)) {
waitAllTextDisplayed(
context.getString(source.titleResId),
- context.getString(source.summaryResId)
+ context.getString(source.summaryResId),
)
}
@@ -380,15 +380,15 @@ class SafetyCenterSubpagesTest {
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION,
entryTitle = "Updated title",
- entrySummary = "Updated summary"
+ entrySummary = "Updated summary",
)
- )
+ ),
)
openPageAndExit(context.getString(sourcesGroup.titleResId)) {
waitAllTextNotDisplayed(
context.getString(source.titleResId),
- context.getString(source.summaryResId)
+ context.getString(source.summaryResId),
)
waitAllTextDisplayed("Updated title", "Updated summary")
}
@@ -405,7 +405,7 @@ class SafetyCenterSubpagesTest {
openPageAndExit(context.getString(sourcesGroup.titleResId)) {
waitAllTextDisplayed(
context.getString(source.titleResId),
- context.getString(source.summaryResId)
+ context.getString(source.summaryResId),
)
SafetySourceReceiver.setResponse(
@@ -414,15 +414,15 @@ class SafetyCenterSubpagesTest {
safetySourceTestData.buildSafetySourceDataWithSummary(
severityLevel = SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION,
entryTitle = "Updated title",
- entrySummary = "Updated summary"
+ entrySummary = "Updated summary",
)
- )
+ ),
)
UiAutomatorUtils2.getUiDevice().rotate()
waitAllTextDisplayed(
context.getString(source.titleResId),
- context.getString(source.summaryResId)
+ context.getString(source.summaryResId),
)
waitAllTextNotDisplayed("Updated title", "Updated summary")
}
@@ -489,7 +489,7 @@ class SafetyCenterSubpagesTest {
// Clear the data when action is triggered to simulate resolution.
SafetySourceReceiver.setResponse(
Request.ResolveAction(SINGLE_SOURCE_ID),
- Response.ClearData
+ Response.ClearData,
)
context.launchSafetyCenterActivity(withReceiverPermission = true) {
@@ -921,7 +921,7 @@ class SafetyCenterSubpagesTest {
waitPageTitleDisplayed(context.getString(sourcesGroup.titleResId))
waitAllTextDisplayed(
context.getString(source.titleResId),
- context.getString(source.summaryResId)
+ context.getString(source.summaryResId),
)
}
}
@@ -952,7 +952,7 @@ class SafetyCenterSubpagesTest {
waitAllTextDisplayed(
context.getString(source.titleResId),
context.getString(source.summaryResId),
- safetyCenterResourcesApk.getStringByName("test_single_source_group_id_footer")
+ safetyCenterResourcesApk.getStringByName("test_single_source_group_id_footer"),
)
}
}
@@ -975,7 +975,7 @@ class SafetyCenterSubpagesTest {
private fun checkOnDismissedIssue(
sourcesGroup: SafetySourcesGroup,
issue: SafetySourceIssue,
- block: () -> Unit
+ block: () -> Unit,
) {
val safetyCenterIssueId = SafetyCenterTestData.issueId(SINGLE_SOURCE_ID, issue.id)
safetyCenterTestHelper.dismissSafetyCenterIssue(safetyCenterIssueId)
diff --git a/tests/hostside/safetycenter/AndroidTest.xml b/tests/hostside/safetycenter/AndroidTest.xml
index a28b70c3c..41f0bcc40 100644
--- a/tests/hostside/safetycenter/AndroidTest.xml
+++ b/tests/hostside/safetycenter/AndroidTest.xml
@@ -32,6 +32,10 @@
<!-- Disable syncing to prevent overwriting flags during testing. -->
<option name="run-command" value="device_config set_sync_disabled_for_tests persistent" />
<option name="teardown-command" value="device_config set_sync_disabled_for_tests none" />
+ <!-- Belt-and-braces attempt to dismiss keyguard. Tradefed should have already done this
+ for us, but this is a precaution in an attempt to mitigate b/379620557. -->
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
<!-- Dismiss any system dialogs (e.g. crashes, ANR). -->
<option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS --receiver-foreground" />
</target_preparer>
diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
index 60e6e41ec..c56c913b6 100644
--- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
+++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
@@ -69,6 +69,12 @@ class SafetyCenterNotificationLoggingHelperTests {
@Test
fun sendNotification() {
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, newTestDataWithNotifiableIssue())
+ TestNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ actions = listOf("See issue"),
+ safetySourceId = SINGLE_SOURCE_ID,
+ )
+ )
}
@Test
@@ -104,7 +110,7 @@ class SafetyCenterNotificationLoggingHelperTests {
statusBarNotificationWithChannel.statusBarNotification.notification.contentIntent
SafetyCenterActivityLauncher.executeBlockAndExit(
launchActivity = { PendingIntentSender.send(contentIntent) },
- block = {} // No action required
+ block = {}, // No action required
)
}
}
diff --git a/tests/utils/safetycenter/AndroidManifest.xml b/tests/utils/safetycenter/AndroidManifest.xml
index f0a4fcbb6..ce3724318 100644
--- a/tests/utils/safetycenter/AndroidManifest.xml
+++ b/tests/utils/safetycenter/AndroidManifest.xml
@@ -39,7 +39,6 @@
android:exported="false"/>
<activity android:name=".TestActivity"
- android:theme="@style/OptOutEdgeToEdgeEnforcement"
android:exported="false">
<intent-filter android:priority="-1">
<action android:name="com.android.safetycenter.testing.action.TEST_ACTIVITY"/>
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
index a7009b19e..47f5165e2 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
@@ -22,6 +22,7 @@ import java.time.Duration
import kotlinx.coroutines.DEBUG_PROPERTY_NAME
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_AUTO
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON
+import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
@@ -56,23 +57,37 @@ object Coroutines {
/** Shorthand for [runBlocking] combined with [withTimeoutOrNull] */
fun <T> runBlockingWithTimeoutOrNull(
timeout: Duration = TIMEOUT_LONG,
- block: suspend () -> T
+ block: suspend () -> T,
): T? = runBlocking { withTimeoutOrNull(timeout.toMillis()) { block() } }
/** Check a condition using coroutines with a timeout. */
fun waitForWithTimeout(
timeout: Duration = TIMEOUT_LONG,
checkPeriod: Duration = CHECK_PERIOD,
- condition: () -> Boolean
+ condition: () -> Boolean,
) {
runBlockingWithTimeout(timeout) { waitFor(checkPeriod, condition) }
}
+ /** Check an assertion passes, with a timeout if it does not. */
+ fun assertWithTimeout(
+ timeout: Duration = TIMEOUT_LONG,
+ checkPeriod: Duration = CHECK_PERIOD,
+ assertion: () -> Unit,
+ ) {
+ try {
+ runBlockingWithTimeout(timeout) { assertThatWaiting(checkPeriod, assertion) }
+ } catch (ex: TimeoutCancellationException) {
+ // Rerun the assertion to generate a meaningful error message that isn't just "timeout"
+ assertion()
+ }
+ }
+
/** Retries a [fallibleAction] until no errors are thrown or a timeout occurs. */
fun waitForSuccessWithTimeout(
timeout: Duration = TIMEOUT_LONG,
checkPeriod: Duration = CHECK_PERIOD,
- fallibleAction: () -> Unit
+ fallibleAction: () -> Unit,
) {
waitForWithTimeout(timeout, checkPeriod) {
try {
@@ -105,6 +120,21 @@ object Coroutines {
}
}
+ /** Check an assertion passes using coroutines. */
+ private suspend fun assertThatWaiting(
+ checkPeriod: Duration = CHECK_PERIOD,
+ assertion: () -> Unit,
+ ) {
+ while (true) {
+ try {
+ assertion()
+ break
+ } catch (ex: AssertionError) {
+ delay(checkPeriod.toMillis())
+ }
+ }
+ }
+
private const val TAG: String = "Coroutines"
/** A medium period, to be used for conditions that are expected to change. */
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt
index 1ed0ecbc3..7303d631b 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/EnableSensorRule.kt
@@ -42,7 +42,7 @@ class EnableSensorRule(context: Context, val sensor: Int) : TestRule {
override fun evaluate() {
assumeTrue(
"Test device does not support toggling sensor $sensor",
- supportsSensorToggle()
+ supportsSensorToggle(),
)
val oldSensorPrivacy = isSensorPrivacyEnabled()
setSensorPrivacy(false)
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/EqualsHashCodeToStringTester.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/EqualsHashCodeToStringTester.kt
index 2dedfc853..77577d504 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/EqualsHashCodeToStringTester.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/EqualsHashCodeToStringTester.kt
@@ -45,7 +45,7 @@ private constructor(
private val ignoreHashCode: Boolean = false,
private val ignoreToString: Boolean = false,
private val parcelRoundTripEqualsEquivalence: Equivalence<T>? = null,
- private val createCopy: ((T) -> T)? = null
+ private val createCopy: ((T) -> T)? = null,
) {
private val equalsTester = EqualsTester()
@@ -85,13 +85,13 @@ private constructor(
parcelableCreator: Parcelable.Creator<T>,
ignoreHashCode: Boolean = false,
ignoreToString: Boolean = false,
- createCopy: ((T) -> T)? = null
+ createCopy: ((T) -> T)? = null,
): EqualsHashCodeToStringTester<T> =
EqualsHashCodeToStringTester(
ignoreHashCode,
ignoreToString,
parcelRoundTripEqualsEquivalence(parcelableCreator),
- createCopy
+ createCopy,
)
/**
@@ -103,13 +103,13 @@ private constructor(
fun <T> of(
ignoreHashCode: Boolean = false,
ignoreToString: Boolean = false,
- createCopy: ((T) -> T)? = null
+ createCopy: ((T) -> T)? = null,
): EqualsHashCodeToStringTester<T> =
EqualsHashCodeToStringTester(
ignoreHashCode,
ignoreToString,
parcelRoundTripEqualsEquivalence = null,
- createCopy
+ createCopy,
)
/**
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt
index 81b752bca..11827a5f4 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/NotificationCharacteristics.kt
@@ -38,7 +38,7 @@ data class NotificationCharacteristics(
private fun importanceMatches(
statusBarNotificationWithChannel: StatusBarNotificationWithChannel,
- characteristicImportance: Int
+ characteristicImportance: Int,
): Boolean {
return characteristicImportance == IMPORTANCE_ANY ||
statusBarNotificationWithChannel.channel.importance == characteristicImportance
@@ -46,7 +46,7 @@ data class NotificationCharacteristics(
private fun blockableMatches(
statusBarNotificationWithChannel: StatusBarNotificationWithChannel,
- characteristicBlockable: Boolean?
+ characteristicBlockable: Boolean?,
): Boolean {
return characteristicBlockable == null ||
statusBarNotificationWithChannel.channel.isBlockable == characteristicBlockable
@@ -54,7 +54,7 @@ data class NotificationCharacteristics(
fun safetySourceIdMatches(
statusBarNotification: StatusBarNotification,
- safetySourceId: String?
+ safetySourceId: String?,
): Boolean {
return safetySourceId == null ||
SafetyCenterIds.issueKeyFromString(statusBarNotification.tag).safetySourceId ==
@@ -63,7 +63,7 @@ data class NotificationCharacteristics(
private fun isMatch(
statusBarNotificationWithChannel: StatusBarNotificationWithChannel,
- characteristic: NotificationCharacteristics
+ characteristic: NotificationCharacteristics,
): Boolean {
val notif = statusBarNotificationWithChannel.statusBarNotification.notification
val extras = notif.extras
@@ -75,13 +75,13 @@ data class NotificationCharacteristics(
blockableMatches(statusBarNotificationWithChannel, characteristic.blockable) &&
safetySourceIdMatches(
statusBarNotificationWithChannel.statusBarNotification,
- characteristic.safetySourceId
+ characteristic.safetySourceId,
)
}
fun areMatching(
statusBarNotifications: List<StatusBarNotificationWithChannel>,
- characteristics: List<NotificationCharacteristics>
+ characteristics: List<NotificationCharacteristics>,
): Boolean {
if (statusBarNotifications.size != characteristics.size) {
return false
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterActivityLauncher.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterActivityLauncher.kt
index 40515fa33..62f324d89 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterActivityLauncher.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterActivityLauncher.kt
@@ -48,13 +48,13 @@ object SafetyCenterActivityLauncher {
intentAction: String = ACTION_SAFETY_CENTER,
withReceiverPermission: Boolean = false,
preventTrampolineToSettings: Boolean = true,
- block: () -> Unit
+ block: () -> Unit,
) {
val launchSafetyCenterIntent =
createIntent(
intentAction,
intentExtras,
- preventTrampolineToSettings = preventTrampolineToSettings
+ preventTrampolineToSettings = preventTrampolineToSettings,
)
if (withReceiverPermission) {
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
@@ -83,7 +83,7 @@ object SafetyCenterActivityLauncher {
private fun createIntent(
intentAction: String,
intentExtras: Bundle?,
- preventTrampolineToSettings: Boolean = false
+ preventTrampolineToSettings: Boolean = false,
): Intent {
val launchIntent =
Intent(intentAction).addFlags(FLAG_ACTIVITY_NEW_TASK).addFlags(FLAG_ACTIVITY_CLEAR_TASK)
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt
index 961d03c47..b5f732bad 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt
@@ -52,7 +52,7 @@ object SafetyCenterApisWithShellPermissions {
fun SafetyCenterManager.setSafetySourceDataWithPermission(
safetySourceId: String,
safetySourceData: SafetySourceData?,
- safetyEvent: SafetyEvent
+ safetyEvent: SafetyEvent,
) {
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
setSafetySourceData(safetySourceId, safetySourceData, safetyEvent)
@@ -72,7 +72,7 @@ object SafetyCenterApisWithShellPermissions {
*/
fun SafetyCenterManager.reportSafetySourceErrorWithPermission(
safetySourceId: String,
- safetySourceErrorDetails: SafetySourceErrorDetails
+ safetySourceErrorDetails: SafetySourceErrorDetails,
) {
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
reportSafetySourceError(safetySourceId, safetySourceErrorDetails)
@@ -85,7 +85,7 @@ object SafetyCenterApisWithShellPermissions {
*/
fun SafetyCenterManager.refreshSafetySourcesWithPermission(
refreshReason: Int,
- safetySourceIds: List<String>? = null
+ safetySourceIds: List<String>? = null,
) {
callWithShellPermissionIdentity(MANAGE_SAFETY_CENTER) {
if (safetySourceIds != null) {
@@ -116,7 +116,7 @@ object SafetyCenterApisWithShellPermissions {
*/
fun SafetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
executor: Executor,
- listener: OnSafetyCenterDataChangedListener
+ listener: OnSafetyCenterDataChangedListener,
) {
callWithShellPermissionIdentity(MANAGE_SAFETY_CENTER) {
addOnSafetyCenterDataChangedListener(executor, listener)
@@ -151,7 +151,7 @@ object SafetyCenterApisWithShellPermissions {
*/
fun SafetyCenterManager.executeSafetyCenterIssueActionWithPermission(
safetyCenterIssueId: String,
- safetyCenterIssueActionId: String
+ safetyCenterIssueActionId: String,
) {
callWithShellPermissionIdentity(MANAGE_SAFETY_CENTER) {
executeSafetyCenterIssueAction(safetyCenterIssueId, safetyCenterIssueActionId)
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterEnabledChangedReceiver.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterEnabledChangedReceiver.kt
index f8926caac..e454b6e25 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterEnabledChangedReceiver.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterEnabledChangedReceiver.kt
@@ -54,16 +54,14 @@ class SafetyCenterEnabledChangedReceiver(private val context: Context) : Broadca
fun setSafetyCenterEnabledWithReceiverPermissionAndWait(
value: Boolean,
- timeout: Duration = TIMEOUT_LONG
+ timeout: Duration = TIMEOUT_LONG,
): Boolean =
callWithShellPermissionIdentity(READ_SAFETY_CENTER_STATUS) {
SafetyCenterFlags.isEnabled = value
receiveSafetyCenterEnabledChanged(timeout)
}
- fun setSafetyCenterEnabledWithoutReceiverPermissionAndWait(
- value: Boolean,
- ) {
+ fun setSafetyCenterEnabledWithoutReceiverPermissionAndWait(value: Boolean) {
SafetyCenterFlags.isEnabled = value
WaitForBroadcasts.waitForBroadcasts()
receiveSafetyCenterEnabledChanged(TIMEOUT_SHORT)
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
index 912ea44ad..7efbba7a0 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
@@ -17,6 +17,7 @@
package com.android.safetycenter.testing
import android.Manifest.permission.READ_DEVICE_CONFIG
+import android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG
import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.annotation.TargetApi
import android.app.job.JobInfo
@@ -61,7 +62,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_notifications_min_delay",
defaultValue = Duration.ofHours(2),
- DurationParser()
+ DurationParser(),
)
/**
@@ -72,7 +73,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_notifications_allowed_sources",
defaultValue = emptySet(),
- SetParser(StringParser())
+ SetParser(StringParser()),
)
/**
@@ -83,7 +84,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_notifications_immediate_behavior_issues",
defaultValue = emptySet(),
- SetParser(StringParser())
+ SetParser(StringParser()),
)
/**
@@ -98,7 +99,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_notification_resurface_interval",
defaultValue = Duration.ofDays(-1),
- DurationParser()
+ DurationParser(),
)
/** Flag that determines whether we should replace the IconAction of the lock screen source. */
@@ -119,7 +120,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_refresh_sources_timeouts_millis",
defaultValue = getAllRefreshTimeoutsMap(TEST_TIMEOUT),
- MapParser(IntParser(), DurationParser())
+ MapParser(IntParser(), DurationParser()),
)
/**
@@ -130,7 +131,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_resolve_action_timeout_millis",
defaultValue = TIMEOUT_LONG,
- DurationParser()
+ DurationParser(),
)
/** Flag that determines a duration after which a temporarily hidden issue will resurface. */
@@ -138,7 +139,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_temp_hidden_issue_resurface_delay_millis",
defaultValue = Duration.ofDays(2),
- DurationParser()
+ DurationParser(),
)
/**
@@ -149,7 +150,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_hide_resolved_ui_transition_delay_millis",
defaultValue = Duration.ofMillis(400),
- DurationParser()
+ DurationParser(),
)
/**
@@ -161,7 +162,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_untracked_sources",
defaultValue = emptySet(),
- SetParser(StringParser())
+ SetParser(StringParser()),
)
/**
@@ -173,7 +174,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_resurface_issue_max_counts",
defaultValue = emptyMap(),
- MapParser(IntParser(), LongParser())
+ MapParser(IntParser(), LongParser()),
)
/**
@@ -187,7 +188,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_resurface_issue_delays_millis",
defaultValue = emptyMap(),
- MapParser(IntParser(), DurationParser())
+ MapParser(IntParser(), DurationParser()),
)
/**
@@ -199,7 +200,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_issue_category_allowlists",
defaultValue = emptyMap(),
- MapParser(IntParser(), SetParser(StringParser(), delimiter = "|"))
+ MapParser(IntParser(), SetParser(StringParser(), delimiter = "|")),
)
/**
@@ -211,7 +212,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_actions_to_override_with_default_intent",
defaultValue = emptyMap(),
- MapParser(StringParser(), SetParser(StringParser(), delimiter = "|"))
+ MapParser(StringParser(), SetParser(StringParser(), delimiter = "|")),
)
/**
@@ -223,7 +224,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_background_refresh_denied_sources",
defaultValue = emptySet(),
- SetParser(StringParser())
+ SetParser(StringParser()),
)
/**
@@ -245,7 +246,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_qs_tile_component_setting_flags",
defaultValue = PackageManager.DONT_KILL_APP,
- IntParser()
+ IntParser(),
)
/**
@@ -253,13 +254,13 @@ object SafetyCenterFlags {
* expand-and-collapse list.
*/
private val showSubpagesFlag =
- Flag("safety_center_show_subpages", defaultValue = false, BooleanParser())
+ Flag("safety_center_show_subpages", defaultValue = SdkLevel.isAtLeastU(), BooleanParser())
private val overrideRefreshOnPageOpenSourcesFlag =
Flag(
"safety_center_override_refresh_on_page_open_sources",
defaultValue = setOf(),
- SetParser(StringParser())
+ SetParser(StringParser()),
)
/**
@@ -272,7 +273,7 @@ object SafetyCenterFlags {
// do not set defaultValue to true, do not want background refreshes running
// during other tests
defaultValue = false,
- BooleanParser()
+ BooleanParser(),
)
/**
@@ -286,7 +287,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_periodic_background_interval_millis",
defaultValue = Duration.ofDays(1),
- DurationParser()
+ DurationParser(),
)
/** Flag for allowlisting additional certificates for a given package. */
@@ -294,7 +295,7 @@ object SafetyCenterFlags {
Flag(
"safety_center_additional_allow_package_certs",
defaultValue = emptyMap(),
- MapParser(StringParser(), SetParser(StringParser(), delimiter = "|"))
+ MapParser(StringParser(), SetParser(StringParser(), delimiter = "|")),
)
/** Every Safety Center flag. */
@@ -323,7 +324,7 @@ object SafetyCenterFlags {
showSubpagesFlag,
overrideRefreshOnPageOpenSourcesFlag,
backgroundRefreshIsEnabledFlag,
- periodicBackgroundRefreshIntervalFlag
+ periodicBackgroundRefreshIntervalFlag,
)
/** A property that allows getting and setting the [isEnabledFlag]. */
@@ -453,7 +454,7 @@ object SafetyCenterFlags {
REFRESH_REASON_DEVICE_LOCALE_CHANGE to refreshTimeout,
REFRESH_REASON_SAFETY_CENTER_ENABLED to refreshTimeout,
REFRESH_REASON_OTHER to refreshTimeout,
- REFRESH_REASON_PERIODIC to refreshTimeout
+ REFRESH_REASON_PERIODIC to refreshTimeout,
)
private interface Parser<T> {
@@ -486,7 +487,7 @@ object SafetyCenterFlags {
private class SetParser<T>(
private val elementParser: Parser<T>,
- private val delimiter: String = ","
+ private val delimiter: String = ",",
) : Parser<Set<T>> {
override fun parseFromString(stringValue: String) =
stringValue.split(delimiter).map(elementParser::parseFromString).toSet()
@@ -499,7 +500,7 @@ object SafetyCenterFlags {
private val keyParser: Parser<K>,
private val valueParser: Parser<V>,
private val entriesDelimiter: String = ",",
- private val pairDelimiter: String = ":"
+ private val pairDelimiter: String = ":",
) : Parser<Map<K, V>> {
override fun parseFromString(stringValue: String) =
stringValue.split(entriesDelimiter).associate { pair ->
@@ -532,13 +533,13 @@ object SafetyCenterFlags {
}
private fun writeDeviceConfigProperty(name: String, stringValue: String?) {
- callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG) {
+ callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG) {
val valueWasSet =
DeviceConfig.setProperty(
NAMESPACE_PRIVACY,
name,
stringValue, /* makeDefault */
- false
+ false,
)
require(valueWasSet) { "Could not set $name to: $stringValue" }
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt
index 261e179dd..231cec821 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt
@@ -48,7 +48,7 @@ class SafetyCenterTestConfigs(private val context: Context) {
context.packageManager
.getPackageInfo(
context.packageName,
- PackageInfoFlags.of(GET_SIGNING_CERTIFICATES.toLong())
+ PackageInfoFlags.of(GET_SIGNING_CERTIFICATES.toLong()),
)
.signingInfo!!
.apkContentsSigners[0]
@@ -220,25 +220,25 @@ class SafetyCenterTestConfigs(private val context: Context) {
.addSafetySource(
issueOnlySafetySourceWithDuplicationInfo(
SOURCE_ID_1,
- DEDUPLICATION_GROUP_1
+ DEDUPLICATION_GROUP_1,
)
)
.addSafetySource(
issueOnlySafetySourceWithDuplicationInfo(
SOURCE_ID_2,
- DEDUPLICATION_GROUP_1
+ DEDUPLICATION_GROUP_1,
)
)
.addSafetySource(
issueOnlySafetySourceWithDuplicationInfo(
SOURCE_ID_3,
- DEDUPLICATION_GROUP_2
+ DEDUPLICATION_GROUP_2,
)
)
.addSafetySource(
issueOnlySafetySourceWithDuplicationInfo(
SOURCE_ID_4,
- DEDUPLICATION_GROUP_3
+ DEDUPLICATION_GROUP_3,
)
)
.build()
@@ -248,13 +248,13 @@ class SafetyCenterTestConfigs(private val context: Context) {
.addSafetySource(
issueOnlySafetySourceWithDuplicationInfo(
SOURCE_ID_5,
- DEDUPLICATION_GROUP_1
+ DEDUPLICATION_GROUP_1,
)
)
.addSafetySource(
issueOnlySafetySourceWithDuplicationInfo(
SOURCE_ID_6,
- DEDUPLICATION_GROUP_3
+ DEDUPLICATION_GROUP_3,
)
)
.build()
@@ -264,7 +264,7 @@ class SafetyCenterTestConfigs(private val context: Context) {
.addSafetySource(
issueOnlySafetySourceWithDuplicationInfo(
SOURCE_ID_7,
- DEDUPLICATION_GROUP_3
+ DEDUPLICATION_GROUP_3,
)
)
.build()
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestData.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestData.kt
index 289bc32a8..15f8d02ae 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestData.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestData.kt
@@ -32,6 +32,8 @@ import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN
import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED
import android.safetycenter.SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON
import android.safetycenter.SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION
+import android.safetycenter.SafetyCenterEntryGroup
+import android.safetycenter.SafetyCenterEntryOrGroup
import android.safetycenter.SafetyCenterIssue
import android.safetycenter.SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING
import android.safetycenter.SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK
@@ -80,7 +82,7 @@ class SafetyCenterTestData(context: Context) {
),
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_ok_review_summary"
- )
+ ),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_UNKNOWN)
.build()
@@ -91,7 +93,7 @@ class SafetyCenterTestData(context: Context) {
*/
fun safetyCenterStatusOneAlert(
statusResource: String,
- overallSeverityLevel: Int
+ overallSeverityLevel: Int,
): SafetyCenterStatus = safetyCenterStatusNAlerts(statusResource, overallSeverityLevel, 1)
/**
@@ -105,7 +107,7 @@ class SafetyCenterTestData(context: Context) {
): SafetyCenterStatus =
SafetyCenterStatus.Builder(
safetyCenterResourcesApk.getStringByName(statusResource),
- getAlertString(numAlerts)
+ getAlertString(numAlerts),
)
.setSeverityLevel(overallSeverityLevel)
.build()
@@ -114,12 +116,10 @@ class SafetyCenterTestData(context: Context) {
* Returns an information [SafetyCenterStatus] that has "Tip(s) available" as a summary for the
* given [numTipIssues].
*/
- fun safetyCenterStatusTips(
- numTipIssues: Int,
- ): SafetyCenterStatus =
+ fun safetyCenterStatusTips(numTipIssues: Int): SafetyCenterStatus =
SafetyCenterStatus.Builder(
safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
- getIcuPluralsString("overall_severity_level_tip_summary", numTipIssues)
+ getIcuPluralsString("overall_severity_level_tip_summary", numTipIssues),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
.build()
@@ -128,15 +128,13 @@ class SafetyCenterTestData(context: Context) {
* Returns an information [SafetyCenterStatus] that has "Action(s) taken" as a summary for the
* given [numAutomaticIssues].
*/
- fun safetyCenterStatusActionsTaken(
- numAutomaticIssues: Int,
- ): SafetyCenterStatus =
+ fun safetyCenterStatusActionsTaken(numAutomaticIssues: Int): SafetyCenterStatus =
SafetyCenterStatus.Builder(
safetyCenterResourcesApk.getStringByName("overall_severity_level_ok_title"),
getIcuPluralsString(
"overall_severity_level_action_taken_summary",
- numAutomaticIssues
- )
+ numAutomaticIssues,
+ ),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
.build()
@@ -150,7 +148,7 @@ class SafetyCenterTestData(context: Context) {
safetyCenterResourcesApk.getStringByName(
"overall_severity_level_critical_safety_warning_title"
),
- getAlertString(numAlerts)
+ getAlertString(numAlerts),
)
.setSeverityLevel(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
.build()
@@ -165,7 +163,7 @@ class SafetyCenterTestData(context: Context) {
userId: Int = UserHandle.myUserId(),
title: CharSequence = "OK",
pendingIntent: PendingIntent? =
- safetySourceTestData.createTestActivityRedirectPendingIntent()
+ safetySourceTestData.createTestActivityRedirectPendingIntent(),
) =
SafetyCenterEntry.Builder(entryId(sourceId, userId), title)
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
@@ -183,7 +181,7 @@ class SafetyCenterTestData(context: Context) {
userId: Int = UserHandle.myUserId(),
title: CharSequence = "OK",
pendingIntent: PendingIntent? =
- safetySourceTestData.createTestActivityRedirectPendingIntent()
+ safetySourceTestData.createTestActivityRedirectPendingIntent(),
) = safetyCenterEntryDefaultBuilder(sourceId, userId, title, pendingIntent).build()
/**
@@ -194,7 +192,7 @@ class SafetyCenterTestData(context: Context) {
fun safetyCenterEntryDefaultStaticBuilder(
sourceId: String,
userId: Int = UserHandle.myUserId(),
- title: CharSequence = "OK"
+ title: CharSequence = "OK",
) =
SafetyCenterEntry.Builder(entryId(sourceId, userId), title)
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
@@ -219,7 +217,7 @@ class SafetyCenterTestData(context: Context) {
fun safetyCenterEntryUnspecified(
sourceId: String,
pendingIntent: PendingIntent? =
- safetySourceTestData.createTestActivityRedirectPendingIntent()
+ safetySourceTestData.createTestActivityRedirectPendingIntent(),
) =
SafetyCenterEntry.Builder(entryId(sourceId), "Unspecified title")
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
@@ -237,7 +235,7 @@ class SafetyCenterTestData(context: Context) {
fun safetyCenterEntryOkBuilder(
sourceId: String,
userId: Int = UserHandle.myUserId(),
- title: CharSequence = "Ok title"
+ title: CharSequence = "Ok title",
) =
SafetyCenterEntry.Builder(entryId(sourceId, userId), title)
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_OK)
@@ -252,7 +250,7 @@ class SafetyCenterTestData(context: Context) {
fun safetyCenterEntryOk(
sourceId: String,
userId: Int = UserHandle.myUserId(),
- title: CharSequence = "Ok title"
+ title: CharSequence = "Ok title",
) = safetyCenterEntryOkBuilder(sourceId, userId, title).build()
/**
@@ -262,7 +260,7 @@ class SafetyCenterTestData(context: Context) {
*/
fun safetyCenterEntryRecommendation(
sourceId: String,
- summary: String = "Recommendation summary"
+ summary: String = "Recommendation summary",
) =
SafetyCenterEntry.Builder(entryId(sourceId), "Recommendation title")
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
@@ -283,6 +281,32 @@ class SafetyCenterTestData(context: Context) {
.setSeverityUnspecifiedIconType(SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION)
.build()
+ fun singletonSafetyCenterEntryOrGroup(
+ groupId: String,
+ entry: SafetyCenterEntry,
+ groupSummary: String? = null,
+ ) =
+ // TODO: b/361404288 - Replace with platform version check
+ if (SafetyCenterFlags.showSubpages) {
+ val summary =
+ if (groupSummary == null && entry.severityLevel > ENTRY_SEVERITY_LEVEL_OK) {
+ entry.summary
+ } else groupSummary ?: "OK"
+
+ SafetyCenterEntryOrGroup(
+ SafetyCenterEntryGroup.Builder(groupId, "OK")
+ .setSeverityLevel(entry.severityLevel)
+ .setSeverityUnspecifiedIconType(
+ SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION
+ )
+ .setSummary(summary)
+ .setEntries(listOf(entry))
+ .build()
+ )
+ } else {
+ SafetyCenterEntryOrGroup(entry)
+ }
+
/**
* Returns an information [SafetyCenterIssue] for the given source and user id that is
* consistent with information [SafetySourceIssue]s used in [SafetySourceTestData].
@@ -291,12 +315,12 @@ class SafetyCenterTestData(context: Context) {
sourceId: String,
userId: Int = UserHandle.myUserId(),
attributionTitle: String? = "OK",
- groupId: String? = SINGLE_SOURCE_GROUP_ID
+ groupId: String? = SINGLE_SOURCE_GROUP_ID,
) =
SafetyCenterIssue.Builder(
issueId(sourceId, INFORMATION_ISSUE_ID, userId = userId),
"Information issue title",
- "Information issue summary"
+ "Information issue summary",
)
.setSeverityLevel(ISSUE_SEVERITY_LEVEL_OK)
.setShouldConfirmDismissal(false)
@@ -307,10 +331,10 @@ class SafetyCenterTestData(context: Context) {
sourceId,
INFORMATION_ISSUE_ID,
INFORMATION_ISSUE_ACTION_ID,
- userId
+ userId,
),
"Review",
- safetySourceTestData.createTestActivityRedirectPendingIntent()
+ safetySourceTestData.createTestActivityRedirectPendingIntent(),
)
.build()
)
@@ -332,12 +356,12 @@ class SafetyCenterTestData(context: Context) {
userId: Int = UserHandle.myUserId(),
attributionTitle: String? = "OK",
groupId: String? = SINGLE_SOURCE_GROUP_ID,
- confirmationDialog: Boolean = false
+ confirmationDialog: Boolean = false,
) =
SafetyCenterIssue.Builder(
issueId(sourceId, RECOMMENDATION_ISSUE_ID, userId = userId),
"Recommendation issue title",
- "Recommendation issue summary"
+ "Recommendation issue summary",
)
.setSeverityLevel(ISSUE_SEVERITY_LEVEL_RECOMMENDATION)
.setActions(
@@ -347,10 +371,10 @@ class SafetyCenterTestData(context: Context) {
sourceId,
RECOMMENDATION_ISSUE_ID,
RECOMMENDATION_ISSUE_ACTION_ID,
- userId
+ userId,
),
"See issue",
- safetySourceTestData.createTestActivityRedirectPendingIntent()
+ safetySourceTestData.createTestActivityRedirectPendingIntent(),
)
.apply {
if (confirmationDialog && SdkLevel.isAtLeastU()) {
@@ -359,7 +383,7 @@ class SafetyCenterTestData(context: Context) {
"Confirmation title",
"Confirmation text",
"Confirmation yes",
- "Confirmation no"
+ "Confirmation no",
)
)
}
@@ -384,12 +408,12 @@ class SafetyCenterTestData(context: Context) {
isActionInFlight: Boolean = false,
userId: Int = UserHandle.myUserId(),
attributionTitle: String? = "OK",
- groupId: String? = SINGLE_SOURCE_GROUP_ID
+ groupId: String? = SINGLE_SOURCE_GROUP_ID,
) =
SafetyCenterIssue.Builder(
issueId(sourceId, CRITICAL_ISSUE_ID, userId = userId),
"Critical issue title",
- "Critical issue summary"
+ "Critical issue summary",
)
.setSeverityLevel(ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
.setActions(
@@ -399,10 +423,10 @@ class SafetyCenterTestData(context: Context) {
sourceId,
CRITICAL_ISSUE_ID,
CRITICAL_ISSUE_ACTION_ID,
- userId
+ userId,
),
"Solve issue",
- safetySourceTestData.criticalIssueActionPendingIntent
+ safetySourceTestData.criticalIssueActionPendingIntent,
)
.setWillResolve(true)
.setIsInFlight(isActionInFlight)
@@ -432,7 +456,7 @@ class SafetyCenterTestData(context: Context) {
val messageFormat =
MessageFormat(
safetyCenterResourcesApk.getStringByName(name, formatArgs),
- Locale.getDefault()
+ Locale.getDefault(),
)
val arguments = ArrayMap<String, Any>()
arguments["count"] = count
@@ -448,7 +472,7 @@ class SafetyCenterTestData(context: Context) {
.build(),
emptyList(),
emptyList(),
- emptyList()
+ emptyList(),
)
/** Creates an ID for a Safety Center entry. */
@@ -465,7 +489,7 @@ class SafetyCenterTestData(context: Context) {
sourceId: String,
sourceIssueId: String,
issueTypeId: String = ISSUE_TYPE_ID,
- userId: Int = UserHandle.myUserId()
+ userId: Int = UserHandle.myUserId(),
) =
SafetyCenterIds.encodeToString(
SafetyCenterIssueId.newBuilder()
@@ -485,7 +509,7 @@ class SafetyCenterTestData(context: Context) {
sourceId: String,
sourceIssueId: String,
sourceIssueActionId: String,
- userId: Int = UserHandle.myUserId()
+ userId: Int = UserHandle.myUserId(),
) =
SafetyCenterIds.encodeToString(
SafetyCenterIssueActionId.newBuilder()
@@ -546,7 +570,7 @@ class SafetyCenterTestData(context: Context) {
private fun SafetyCenterData.copy(
issues: List<SafetyCenterIssue> = this.issues,
dismissedIssues: List<SafetyCenterIssue> = this.dismissedIssues,
- extras: Bundle = this.extras
+ extras: Bundle = this.extras,
): SafetyCenterData =
SafetyCenterData.Builder(status)
.apply {
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestHelper.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestHelper.kt
index a138675d3..ac646648a 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestHelper.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestHelper.kt
@@ -108,7 +108,7 @@ class SafetyCenterTestHelper(val context: Context) {
}
assumeTrue(
"Cannot toggle SafetyCenter using DeviceConfig",
- safetyCenterCanBeToggledUsingDeviceConfig()
+ safetyCenterCanBeToggledUsingDeviceConfig(),
)
setEnabledWaitingForSafetyCenterBroadcastIdle(value, safetyCenterConfig)
}
@@ -132,7 +132,7 @@ class SafetyCenterTestHelper(val context: Context) {
val listener = SafetyCenterTestListener()
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
directExecutor(),
- listener
+ listener,
)
if (skipInitialData) {
listener.receiveSafetyCenterData()
@@ -145,14 +145,14 @@ class SafetyCenterTestHelper(val context: Context) {
fun setData(
safetySourceId: String,
safetySourceData: SafetySourceData?,
- safetyEvent: SafetyEvent = EVENT_SOURCE_STATE_CHANGED
+ safetyEvent: SafetyEvent = EVENT_SOURCE_STATE_CHANGED,
) {
Log.d(TAG, "setData for $safetySourceId")
require(isEnabled())
safetyCenterManager.setSafetySourceDataWithPermission(
safetySourceId,
safetySourceData,
- safetyEvent
+ safetyEvent,
)
}
@@ -173,7 +173,7 @@ class SafetyCenterTestHelper(val context: Context) {
private fun setEnabledWaitingForSafetyCenterBroadcastIdle(
value: Boolean,
- safetyCenterConfig: SafetyCenterConfig
+ safetyCenterConfig: SafetyCenterConfig,
) =
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE, READ_SAFETY_CENTER_STATUS) {
val enabledChangedReceiver = SafetyCenterEnabledChangedReceiver(context)
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt
index 8ce5c25d4..46e9b6593 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt
@@ -58,7 +58,7 @@ class SafetyCenterTestListener : OnSafetyCenterDataChangedListener {
*/
fun receiveSafetyCenterData(
timeout: Duration = TIMEOUT_LONG,
- matching: (SafetyCenterData) -> Boolean = { true }
+ matching: (SafetyCenterData) -> Boolean = { true },
): SafetyCenterData =
runBlockingWithTimeout(timeout) {
var safetyCenterData = dataChannel.receive()
@@ -78,7 +78,7 @@ class SafetyCenterTestListener : OnSafetyCenterDataChangedListener {
*/
fun waitForSafetyCenterRefresh(
timeout: Duration = TIMEOUT_LONG,
- withErrorEntry: Boolean? = null
+ withErrorEntry: Boolean? = null,
): SafetyCenterData {
receiveSafetyCenterData(timeout) {
it.status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS ||
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt
index dcbc4ebe9..c89b29b2a 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestRule.kt
@@ -23,7 +23,7 @@ import org.junit.runners.model.Statement
/** A JUnit [TestRule] that performs setup and reset steps before and after Safety Center tests. */
class SafetyCenterTestRule(
private val safetyCenterTestHelper: SafetyCenterTestHelper,
- private val withNotifications: Boolean = false
+ private val withNotifications: Boolean = false,
) : TestRule {
override fun apply(base: Statement, description: Description): Statement {
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceIntentHandler.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceIntentHandler.kt
index 8386228b8..77d338f90 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceIntentHandler.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceIntentHandler.kt
@@ -114,7 +114,7 @@ class SafetySourceIntentHandler {
private suspend fun SafetyCenterManager.processRefreshSafetySources(
intent: Intent,
- userId: Int
+ userId: Int,
) {
val broadcastId = intent.getStringExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID)
if (broadcastId.isNullOrEmpty()) {
@@ -190,7 +190,7 @@ class SafetySourceIntentHandler {
private fun createResolveActionSuccessEvent(
sourceIssueId: String,
- sourceIssueActionId: String
+ sourceIssueActionId: String,
) =
SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED)
.setSafetySourceIssueId(sourceIssueId)
@@ -218,7 +218,7 @@ class SafetySourceIntentHandler {
private suspend fun SafetyCenterManager.processRequest(
request: Request,
- safetyEventForResponse: (Response) -> SafetyEvent
+ safetyEventForResponse: (Response) -> SafetyEvent,
) {
val response = mutex.withLock { requestsToResponses[request] } ?: return
val safetyEvent = response.overrideSafetyEvent ?: safetyEventForResponse(response)
@@ -242,25 +242,25 @@ class SafetySourceIntentHandler {
/** Creates a refresh [Request] based on the given [sourceId] and [userId]. */
data class Refresh(
override val sourceId: String,
- override val userId: Int = UserHandle.myUserId()
+ override val userId: Int = UserHandle.myUserId(),
) : Request
/** Creates a rescan [Request] based on the given [sourceId] and [userId]. */
data class Rescan(
override val sourceId: String,
- override val userId: Int = UserHandle.myUserId()
+ override val userId: Int = UserHandle.myUserId(),
) : Request
/** Creates a resolve action [Request] based on the given [sourceId] and [userId]. */
data class ResolveAction(
override val sourceId: String,
- override val userId: Int = UserHandle.myUserId()
+ override val userId: Int = UserHandle.myUserId(),
) : Request
/** Creates an issue dismissal [Request] based on the given [sourceId] and [userId]. */
data class DismissIssue(
override val sourceId: String,
- override val userId: Int = UserHandle.myUserId()
+ override val userId: Int = UserHandle.myUserId(),
) : Request
}
@@ -295,7 +295,7 @@ class SafetySourceIntentHandler {
data class SetData(
val safetySourceData: SafetySourceData,
val overrideBroadcastId: String? = null,
- override val overrideSafetyEvent: SafetyEvent? = null
+ override val overrideSafetyEvent: SafetyEvent? = null,
) : Response
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceReceiver.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceReceiver.kt
index 29072c989..6b86df0dd 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceReceiver.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceReceiver.kt
@@ -86,7 +86,7 @@ class SafetySourceReceiver : BroadcastReceiver() {
NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_ID,
- IMPORTANCE_DEFAULT
+ IMPORTANCE_DEFAULT,
)
)
startForeground(
@@ -98,7 +98,7 @@ class SafetySourceReceiver : BroadcastReceiver() {
"ForegroundService"
)
.setSmallIcon(android.R.drawable.ic_info)
- .build()
+ .build(),
)
serviceScope.launch {
try {
@@ -150,7 +150,7 @@ class SafetySourceReceiver : BroadcastReceiver() {
componentName,
if (enabled) COMPONENT_ENABLED_STATE_ENABLED
else COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP
+ PackageManager.DONT_KILL_APP,
)
}
@@ -165,7 +165,7 @@ class SafetySourceReceiver : BroadcastReceiver() {
fun SafetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
refreshReason: Int,
safetySourceIds: List<String>? = null,
- timeout: Duration = TIMEOUT_LONG
+ timeout: Duration = TIMEOUT_LONG,
): String =
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
refreshSafetySourcesWithPermission(refreshReason, safetySourceIds)
@@ -174,7 +174,7 @@ class SafetySourceReceiver : BroadcastReceiver() {
fun SafetyCenterManager.refreshSafetySourcesWithoutReceiverPermissionAndWait(
refreshReason: Int,
- safetySourceIds: List<String>? = null
+ safetySourceIds: List<String>? = null,
) {
refreshSafetySourcesWithPermission(refreshReason, safetySourceIds)
WaitForBroadcasts.waitForBroadcasts()
@@ -183,16 +183,14 @@ class SafetySourceReceiver : BroadcastReceiver() {
fun setSafetyCenterEnabledWithReceiverPermissionAndWait(
value: Boolean,
- timeout: Duration = TIMEOUT_LONG
+ timeout: Duration = TIMEOUT_LONG,
): Boolean =
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
SafetyCenterFlags.isEnabled = value
receiveSafetyCenterEnabledChanged(timeout)
}
- fun setSafetyCenterEnabledWithoutReceiverPermissionAndWait(
- value: Boolean,
- ) {
+ fun setSafetyCenterEnabledWithoutReceiverPermissionAndWait(value: Boolean) {
SafetyCenterFlags.isEnabled = value
WaitForBroadcasts.waitForBroadcasts()
receiveSafetyCenterEnabledChanged(TIMEOUT_SHORT)
@@ -201,7 +199,7 @@ class SafetySourceReceiver : BroadcastReceiver() {
fun SafetyCenterManager.executeSafetyCenterIssueActionWithPermissionAndWait(
issueId: String,
issueActionId: String,
- timeout: Duration = TIMEOUT_LONG
+ timeout: Duration = TIMEOUT_LONG,
) {
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
executeSafetyCenterIssueActionWithPermission(issueId, issueActionId)
@@ -211,7 +209,7 @@ class SafetySourceReceiver : BroadcastReceiver() {
fun SafetyCenterManager.dismissSafetyCenterIssueWithPermissionAndWait(
issueId: String,
- timeout: Duration = TIMEOUT_LONG
+ timeout: Duration = TIMEOUT_LONG,
) {
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
dismissSafetyCenterIssueWithPermission(issueId)
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
index 7e77c0827..aad665004 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
@@ -63,11 +63,11 @@ class SafetySourceTestData(private val context: Context) {
*/
fun createTestActivityRedirectPendingIntent(
explicit: Boolean = true,
- identifier: String? = null
+ identifier: String? = null,
) =
createRedirectPendingIntent(
context,
- createTestActivityIntent(context, explicit).setIdentifier(identifier)
+ createTestActivityIntent(context, explicit).setIdentifier(identifier),
)
/** A [SafetySourceData] with a [SEVERITY_LEVEL_UNSPECIFIED] [SafetySourceStatus]. */
@@ -77,7 +77,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Unspecified title",
"Unspecified summary",
- SEVERITY_LEVEL_UNSPECIFIED
+ SEVERITY_LEVEL_UNSPECIFIED,
)
.setEnabled(false)
.build()
@@ -94,7 +94,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Clickable disabled title",
"Clickable disabled summary",
- SEVERITY_LEVEL_UNSPECIFIED
+ SEVERITY_LEVEL_UNSPECIFIED,
)
.setEnabled(false)
.setPendingIntent(createTestActivityRedirectPendingIntent())
@@ -111,7 +111,7 @@ class SafetySourceTestData(private val context: Context) {
fun defaultInformationIssueBuilder(
id: String = INFORMATION_ISSUE_ID,
title: String = "Information issue title",
- summary: String = "Information issue summary"
+ summary: String = "Information issue summary",
) =
SafetySourceIssue.Builder(id, title, summary, SEVERITY_LEVEL_INFORMATION, ISSUE_TYPE_ID)
.addAction(action())
@@ -120,7 +120,7 @@ class SafetySourceTestData(private val context: Context) {
fun action(
id: String = INFORMATION_ISSUE_ACTION_ID,
label: String = "Review",
- pendingIntent: PendingIntent = createTestActivityRedirectPendingIntent()
+ pendingIntent: PendingIntent = createTestActivityRedirectPendingIntent(),
) = Action.Builder(id, label, pendingIntent).build()
/**
@@ -133,7 +133,7 @@ class SafetySourceTestData(private val context: Context) {
"Information issue title",
"Information issue summary",
SEVERITY_LEVEL_INFORMATION,
- ISSUE_TYPE_ID
+ ISSUE_TYPE_ID,
)
.setSubtitle("Information issue subtitle")
.addAction(action())
@@ -149,7 +149,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Unspecified title",
"Unspecified summary",
- SEVERITY_LEVEL_UNSPECIFIED
+ SEVERITY_LEVEL_UNSPECIFIED,
)
.setPendingIntent(createTestActivityRedirectPendingIntent())
.build()
@@ -167,7 +167,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Unspecified title for Work",
"Unspecified summary",
- SEVERITY_LEVEL_UNSPECIFIED
+ SEVERITY_LEVEL_UNSPECIFIED,
)
.setPendingIntent(createTestActivityRedirectPendingIntent())
.build()
@@ -185,7 +185,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Unspecified title for Private",
"Unspecified summary",
- SEVERITY_LEVEL_UNSPECIFIED
+ SEVERITY_LEVEL_UNSPECIFIED,
)
.setPendingIntent(createTestActivityRedirectPendingIntent())
.build()
@@ -292,7 +292,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Ok title for Work",
"Ok summary",
- SEVERITY_LEVEL_INFORMATION
+ SEVERITY_LEVEL_INFORMATION,
)
.setPendingIntent(createTestActivityRedirectPendingIntent())
.build()
@@ -310,7 +310,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Ok title for Private",
"Ok summary",
- SEVERITY_LEVEL_INFORMATION
+ SEVERITY_LEVEL_INFORMATION,
)
.setPendingIntent(createTestActivityRedirectPendingIntent())
.build()
@@ -339,20 +339,20 @@ class SafetySourceTestData(private val context: Context) {
fun defaultRecommendationIssueBuilder(
title: String = "Recommendation issue title",
summary: String = "Recommendation issue summary",
- confirmationDialog: Boolean = false
+ confirmationDialog: Boolean = false,
) =
SafetySourceIssue.Builder(
RECOMMENDATION_ISSUE_ID,
title,
summary,
SEVERITY_LEVEL_RECOMMENDATION,
- ISSUE_TYPE_ID
+ ISSUE_TYPE_ID,
)
.addAction(
Action.Builder(
RECOMMENDATION_ISSUE_ACTION_ID,
"See issue",
- createTestActivityRedirectPendingIntent()
+ createTestActivityRedirectPendingIntent(),
)
.apply {
if (confirmationDialog && SdkLevel.isAtLeastU()) {
@@ -418,7 +418,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Recommendation title",
"Recommendation summary",
- SEVERITY_LEVEL_RECOMMENDATION
+ SEVERITY_LEVEL_RECOMMENDATION,
)
.setPendingIntent(createTestActivityRedirectPendingIntent())
.build()
@@ -476,7 +476,7 @@ class SafetySourceTestData(private val context: Context) {
fun resolvingActionPendingIntent(
sourceId: String = SINGLE_SOURCE_ID,
sourceIssueId: String = CRITICAL_ISSUE_ID,
- sourceIssueActionId: String = CRITICAL_ISSUE_ACTION_ID
+ sourceIssueActionId: String = CRITICAL_ISSUE_ACTION_ID,
) =
broadcastPendingIntent(
Intent(ACTION_RESOLVE_ACTION)
@@ -498,7 +498,7 @@ class SafetySourceTestData(private val context: Context) {
Action.Builder(
CRITICAL_ISSUE_ACTION_ID,
"Solve issue",
- criticalIssueActionPendingIntent(sourceId = sourceId)
+ criticalIssueActionPendingIntent(sourceId = sourceId),
)
.setWillResolve(true)
.build()
@@ -510,7 +510,7 @@ class SafetySourceTestData(private val context: Context) {
Action.Builder(
CRITICAL_ISSUE_ACTION_ID,
"Solve issue",
- criticalIssueActionPendingIntent
+ criticalIssueActionPendingIntent,
)
.setWillResolve(true)
.setConfirmationDialogDetails(CONFIRMATION_DETAILS)
@@ -521,7 +521,7 @@ class SafetySourceTestData(private val context: Context) {
Action.Builder(
CRITICAL_ISSUE_ACTION_ID,
"Redirect",
- createTestActivityRedirectPendingIntent()
+ createTestActivityRedirectPendingIntent(),
)
.build()
@@ -537,7 +537,7 @@ class SafetySourceTestData(private val context: Context) {
Action.Builder(
CRITICAL_ISSUE_ACTION_ID,
"Solve issue",
- criticalIssueActionPendingIntent(sourceId = sourceId)
+ criticalIssueActionPendingIntent(sourceId = sourceId),
)
.setWillResolve(true)
.setSuccessMessage("Issue solved")
@@ -550,7 +550,7 @@ class SafetySourceTestData(private val context: Context) {
"Critical issue title",
"Critical issue summary",
SEVERITY_LEVEL_CRITICAL_WARNING,
- ISSUE_TYPE_ID
+ ISSUE_TYPE_ID,
)
.addAction(criticalResolvingActionWithSuccessMessage)
.build()
@@ -562,7 +562,7 @@ class SafetySourceTestData(private val context: Context) {
"Critical issue title",
"Critical issue summary",
SEVERITY_LEVEL_CRITICAL_WARNING,
- ISSUE_TYPE_ID
+ ISSUE_TYPE_ID,
)
.addAction(criticalResolvingActionWithSuccessMessage(sourceId = sourceId))
.build()
@@ -577,13 +577,13 @@ class SafetySourceTestData(private val context: Context) {
"Critical issue title 2",
"Critical issue summary 2",
SEVERITY_LEVEL_CRITICAL_WARNING,
- ISSUE_TYPE_ID
+ ISSUE_TYPE_ID,
)
.addAction(
Action.Builder(
CRITICAL_ISSUE_ACTION_ID,
"Go solve issue",
- createTestActivityRedirectPendingIntent()
+ createTestActivityRedirectPendingIntent(),
)
.build()
)
@@ -620,7 +620,7 @@ class SafetySourceTestData(private val context: Context) {
"Critical issue title",
"Critical issue summary",
SEVERITY_LEVEL_CRITICAL_WARNING,
- ISSUE_TYPE_ID
+ ISSUE_TYPE_ID,
)
.addAction(criticalResolvingAction(sourceId))
@@ -679,7 +679,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Critical title",
"Critical summary",
- SEVERITY_LEVEL_CRITICAL_WARNING
+ SEVERITY_LEVEL_CRITICAL_WARNING,
)
.setPendingIntent(createTestActivityRedirectPendingIntent())
.build()
@@ -811,7 +811,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Critical title",
"Critical summary",
- SEVERITY_LEVEL_CRITICAL_WARNING
+ SEVERITY_LEVEL_CRITICAL_WARNING,
)
.setPendingIntent(createTestActivityRedirectPendingIntent())
.build()
@@ -829,7 +829,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Critical title",
"Critical summary",
- SEVERITY_LEVEL_CRITICAL_WARNING
+ SEVERITY_LEVEL_CRITICAL_WARNING,
)
.setPendingIntent(createTestActivityRedirectPendingIntent())
.build()
@@ -854,7 +854,7 @@ class SafetySourceTestData(private val context: Context) {
SafetySourceStatus.Builder(
"Critical title 2",
"Critical summary 2",
- SEVERITY_LEVEL_CRITICAL_WARNING
+ SEVERITY_LEVEL_CRITICAL_WARNING,
)
.setPendingIntent(createTestActivityRedirectPendingIntent())
.build()
@@ -870,7 +870,7 @@ class SafetySourceTestData(private val context: Context) {
severityLevel: Int,
entrySummary: String,
withIssue: Boolean = false,
- entryTitle: String = "Entry title"
+ entryTitle: String = "Entry title",
) =
SafetySourceData.Builder()
.setStatus(
@@ -886,13 +886,13 @@ class SafetySourceTestData(private val context: Context) {
"Issue title",
"Issue summary",
max(severityLevel, SEVERITY_LEVEL_INFORMATION),
- ISSUE_TYPE_ID
+ ISSUE_TYPE_ID,
)
.addAction(
Action.Builder(
"action_id",
"Action",
- createTestActivityRedirectPendingIntent()
+ createTestActivityRedirectPendingIntent(),
)
.build()
)
@@ -907,7 +907,7 @@ class SafetySourceTestData(private val context: Context) {
context,
0,
intent.addFlags(FLAG_RECEIVER_FOREGROUND).setPackage(context.packageName),
- PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_IMMUTABLE,
)
companion object {
@@ -945,7 +945,7 @@ class SafetySourceTestData(private val context: Context) {
CONFIRMATION_TITLE,
CONFIRMATION_TEXT,
CONFIRMATION_YES,
- CONFIRMATION_NO
+ CONFIRMATION_NO,
)
/** A [SafetyEvent] to push arbitrary changes to Safety Center. */
@@ -979,7 +979,7 @@ class SafetySourceTestData(private val context: Context) {
context,
0 /* requestCode */,
intent,
- PendingIntent.FLAG_IMMUTABLE
+ PendingIntent.FLAG_IMMUTABLE,
)
}
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SettingsPackage.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SettingsPackage.kt
index 2ba127f4e..8fef70ce3 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SettingsPackage.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SettingsPackage.kt
@@ -38,7 +38,7 @@ object SettingsPackage {
PackageManager.MATCH_DIRECT_BOOT_AWARE or
PackageManager.MATCH_DIRECT_BOOT_UNAWARE)
.toLong()
- )
+ ),
)!!
.activityInfo
.packageName
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/StatusBarNotificationWithChannel.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/StatusBarNotificationWithChannel.kt
index 53ea34362..a4569b72b 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/StatusBarNotificationWithChannel.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/StatusBarNotificationWithChannel.kt
@@ -22,5 +22,5 @@ import android.service.notification.StatusBarNotification
/** Tuple of [StatusBarNotification] and the [NotificationChannel] it was posted to. */
data class StatusBarNotificationWithChannel(
val statusBarNotification: StatusBarNotification,
- val channel: NotificationChannel
+ val channel: NotificationChannel,
)
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestActivity.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestActivity.kt
index eceffb74f..12b4f7fd2 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestActivity.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestActivity.kt
@@ -52,10 +52,12 @@ class TestActivity : Activity() {
fun enableHighPriorityAlias() {
setAliasEnabledState(COMPONENT_ENABLED_STATE_ENABLED)
}
+
/** @see [enableHighPriorityAlias] */
fun disableHighPriorityAlias() {
setAliasEnabledState(COMPONENT_ENABLED_STATE_DISABLED)
}
+
private fun setAliasEnabledState(state: Int) {
val name =
ComponentName(getApplicationContext(), TestActivity::class.java.name + "Priority")
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt
index 21bf76fad..17b520349 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/TestNotificationListener.kt
@@ -115,7 +115,7 @@ class TestNotificationListener : NotificationListenerService() {
*/
fun waitForSingleNotificationMatching(
characteristics: NotificationCharacteristics,
- timeout: Duration = TIMEOUT_LONG
+ timeout: Duration = TIMEOUT_LONG,
): StatusBarNotificationWithChannel {
return waitForNotificationsMatching(characteristics, timeout = timeout).first()
}
@@ -128,12 +128,12 @@ class TestNotificationListener : NotificationListenerService() {
*/
fun waitForNotificationsMatching(
vararg characteristics: NotificationCharacteristics,
- timeout: Duration = TIMEOUT_LONG
+ timeout: Duration = TIMEOUT_LONG,
): List<StatusBarNotificationWithChannel> {
val charsList = characteristics.toList()
return waitForNotificationsToSatisfy(
timeout = timeout,
- description = "notification(s) matching characteristics $charsList"
+ description = "notification(s) matching characteristics $charsList",
) {
NotificationCharacteristics.areMatching(it, charsList)
}
@@ -147,7 +147,7 @@ class TestNotificationListener : NotificationListenerService() {
*/
fun waitForSuccessNotification(
successMessage: String,
- onNotification: (StatusBarNotification) -> Unit = {}
+ onNotification: (StatusBarNotification) -> Unit = {},
) {
// Only wait for the notification event and don't wait for all notifications to "settle"
// as this notification is auto-cancelled after 10s; which can cause flakyness.
@@ -185,7 +185,7 @@ class TestNotificationListener : NotificationListenerService() {
timeout: Duration = TIMEOUT_LONG,
forAtLeast: Duration = TIMEOUT_SHORT,
description: String,
- predicate: (List<StatusBarNotificationWithChannel>) -> Boolean
+ predicate: (List<StatusBarNotificationWithChannel>) -> Boolean,
): List<StatusBarNotificationWithChannel> {
// First we wait at most timeout for the active notifications to satisfy the given
// predicate or otherwise we throw:
@@ -198,7 +198,7 @@ class TestNotificationListener : NotificationListenerService() {
throw AssertionError(
"Expected: $description, but notifications were " +
"${getSafetyCenterNotifications()} after waiting for $timeout",
- e
+ e,
)
}
@@ -298,7 +298,7 @@ class TestNotificationListener : NotificationListenerService() {
getInstanceOrThrow().cancelNotification(key)
waitForNotificationsToSatisfy(
timeout = TIMEOUT_LONG,
- description = "no notification with the key $key"
+ description = "no notification with the key $key",
) { notifications ->
notifications.none { it.statusBarNotification.key == key }
}
@@ -327,7 +327,7 @@ class TestNotificationListener : NotificationListenerService() {
throw IllegalStateException(
"Notification dismissal was not recorded in the issue cache: " +
dumpIssueDismissalsRepositoryState(),
- e
+ e,
)
}
}
@@ -362,7 +362,7 @@ class TestNotificationListener : NotificationListenerService() {
fun reset(context: Context) {
waitForNotificationsToSatisfy(
forAtLeast = Duration.ZERO,
- description = "all Safety Center notifications removed in tear down"
+ description = "all Safety Center notifications removed in tear down",
) {
it.isEmpty()
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
index 0e062692a..3dfefeecf 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
@@ -175,7 +175,7 @@ object UiTestHelper {
fun waitGroupShownOnHomepage(context: Context, group: SafetySourcesGroup) {
waitAllTextDisplayed(
context.getString(group.titleResId),
- context.getString(group.summaryResId)
+ context.getString(group.summaryResId),
)
}
@@ -197,6 +197,7 @@ object UiTestHelper {
/** Opens the subpage by clicking on the group title. */
fun clickOpenSubpage(context: Context, group: SafetySourcesGroup) {
waitDisplayed(By.text(context.getString(group.titleResId))) { it.click() }
+ getUiDevice().waitForIdle()
}
/** Clicks the more issues card button to show or hide additional issues. */
diff --git a/tests/utils/safetycenter/res/layout/test_activity.xml b/tests/utils/safetycenter/res/layout/test_activity.xml
index edbe3641a..b0b7523c8 100644
--- a/tests/utils/safetycenter/res/layout/test_activity.xml
+++ b/tests/utils/safetycenter/res/layout/test_activity.xml
@@ -19,6 +19,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:fitsSystemWindows="true"
android:orientation="vertical" >
<Button android:id="@+id/button"
android:layout_width="wrap_content"
diff --git a/tests/utils/safetycenter/res/values/styles.xml b/tests/utils/safetycenter/res/values/styles.xml
deleted file mode 100644
index ce54568ed..000000000
--- a/tests/utils/safetycenter/res/values/styles.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2024 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <!--
- TODO(b/309578419): Make activities handle insets properly and then remove this.
- -->
- <style name="OptOutEdgeToEdgeEnforcement">
- <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
- </style>
-</resources>