summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/OWNERS1
-rw-r--r--PermissionController/TEST_MAPPING27
-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/view_status_card.xml31
-rw-r--r--PermissionController/res/layout/permission_history_widget.xml2
-rw-r--r--PermissionController/res/values-ca/strings.xml2
-rw-r--r--PermissionController/res/values-es/strings.xml2
-rw-r--r--PermissionController/res/values-et/strings.xml2
-rw-r--r--PermissionController/res/values-eu/strings.xml4
-rw-r--r--PermissionController/res/values-fa/strings.xml4
-rw-r--r--PermissionController/res/values-gu/strings.xml4
-rw-r--r--PermissionController/res/values-hr/strings.xml2
-rw-r--r--PermissionController/res/values-it/strings.xml2
-rw-r--r--PermissionController/res/values-iw/strings.xml22
-rw-r--r--PermissionController/res/values-kk/strings.xml2
-rw-r--r--PermissionController/res/values-ko/strings.xml2
-rw-r--r--PermissionController/res/values-lv/strings.xml2
-rw-r--r--PermissionController/res/values-ne/strings.xml2
-rw-r--r--PermissionController/res/values-pl/strings.xml4
-rw-r--r--PermissionController/res/values-ro/strings.xml2
-rw-r--r--PermissionController/res/values-sk/strings.xml2
-rw-r--r--PermissionController/res/values-sl-watch/strings.xml2
-rw-r--r--PermissionController/res/values-sl/strings.xml2
-rw-r--r--PermissionController/res/values-sq/strings.xml2
-rw-r--r--PermissionController/res/values-uk/strings.xml2
-rw-r--r--PermissionController/res/values-vi/strings.xml2
-rw-r--r--PermissionController/res/values-watch/donottranslate.xml2
-rw-r--r--PermissionController/res/values-zh-rCN/strings.xml6
-rw-r--r--PermissionController/res/xml/roles.xml12
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java2
-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/ReservedForTestingProfileGroupExclusivityRoleBehavior.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/model/AppOp.java5
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Permission.java5
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Role.java24
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java8
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt15
-rw-r--r--PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt43
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt1
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt40
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt78
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt107
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt32
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt37
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionScreen.kt17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt33
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt65
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt15
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt247
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt58
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/AlertDialog.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt)116
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Chip.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt)31
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Icon.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt)21
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ListFooter.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt)6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ListHeader.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt)12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ResponsiveDialog.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ResponsiveDialog.kt)107
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ToggleChip.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt)48
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Wear2Scaffold.kt258
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/layout/ScalingLazyColumnDefaults.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnDefaults.kt)16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/layout/ScalingLazyColumnState.kt (renamed from PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnState.kt)64
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionConfirmationDialog.kt163
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListSubHeader.kt58
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt134
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffoldPaddingDefaults.kt68
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt43
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt4
-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/WearLocationProviderInterceptDialogViewModel.kt17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt26
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt19
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING9
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt48
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java56
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt25
-rw-r--r--PermissionController/tests/inprocess/Android.bp1
-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/util/ArrayUtilsTest.kt4
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt4
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt8
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt94
-rw-r--r--PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt72
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/NotificationListenerCheckInternalTest.kt5
-rw-r--r--PermissionController/tests/permissionui/Android.bp1
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageCustomPermissionsFragmentTest.kt38
-rw-r--r--SafetyCenter/Resources/res/raw-v36/safety_center_config.xml30
-rw-r--r--SafetyCenter/Resources/res/values-af-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-am-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ar-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-as-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-az-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-b+sr+Latn-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-be-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-bg-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-bn-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-bs-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ca-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-cs-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-da-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-de-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-el-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-en-rAU-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-en-rCA-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-en-rGB-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-en-rIN-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-es-rUS-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-es-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-et-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-eu-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-fa-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-fi-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-fr-rCA-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-fr-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-gl-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-gu-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-hi-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-hr-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-hu-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-hy-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-in-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-is-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-it-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-iw-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ja-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ka-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-kk-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-km-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-kn-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ko-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ky-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-lo-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-lt-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-lv-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-mk-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ml-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-mn-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-mr-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ms-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-my-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-nb-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ne-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-nl-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-or-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-pa-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-pl-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-pt-rBR-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-pt-rPT-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-pt-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ro-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ru-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-si-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-sk-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-sl-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-sq-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-sr-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-sv-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-sw-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ta-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-te-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-th-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-tl-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-tr-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-uk-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-ur-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-uz-v36/strings.xml41
-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.xml41
-rw-r--r--SafetyCenter/Resources/res/values-zh-rCN-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-zh-rHK-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-zh-rTW-v36/strings.xml41
-rw-r--r--SafetyCenter/Resources/res/values-zu-v36/strings.xml41
-rw-r--r--TEST_MAPPING3
-rw-r--r--flags/flags.aconfig11
-rw-r--r--framework-s/api/system-current.txt2
-rw-r--r--framework-s/java/android/app/ecm/EnhancedConfirmationManager.java25
-rw-r--r--framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl2
-rw-r--r--framework-s/java/android/app/role/RoleManager.java2
-rw-r--r--framework-s/java/android/app/role/TEST_MAPPING9
-rw-r--r--service/java/com/android/ecm/EnhancedConfirmationService.java84
-rw-r--r--service/java/com/android/role/TEST_MAPPING9
-rw-r--r--tests/apex/AndroidTest.xml1
-rw-r--r--tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt33
-rw-r--r--tests/cts/permissionpolicy/res/raw/android_manifest.xml16
-rw-r--r--tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml2
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt32
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt2
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt84
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt52
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt35
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt102
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt66
-rw-r--r--tests/cts/role/Android.bp10
-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.java65
-rw-r--r--tests/cts/rolemultiuser/Android.bp1
-rw-r--r--tests/cts/rolemultiuser/TEST_MAPPING9
-rw-r--r--tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt3
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt28
-rw-r--r--tests/utils/safetycenter/AndroidManifest.xml1
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt1
-rw-r--r--tests/utils/safetycenter/res/layout/test_activity.xml1
-rw-r--r--tests/utils/safetycenter/res/values/styles.xml24
227 files changed, 5753 insertions, 2875 deletions
diff --git a/PermissionController/OWNERS b/PermissionController/OWNERS
index 3872198cb..5a58ec2f5 100644
--- a/PermissionController/OWNERS
+++ b/PermissionController/OWNERS
@@ -19,5 +19,6 @@ per-file res/** = file:platform/packages/modules/Permission:/SafetyCenter/OWNERS
per-file WEAR_OWNERS = file:/PermissionController/WEAR_OWNERS
per-file src/com/android/permissioncontroller/permission/ui/wear/** = file:/PermissionController/WEAR_OWNERS
per-file src/com/android/permissioncontroller/role/ui/wear/** = file:/PermissionController/WEAR_OWNERS
+per-file src/com/android/permissioncontroller/incident/wear/** = file:/PermissionController/WEAR_OWNERS
per-file res/*-watch/* = file:/PermissionController/WEAR_OWNERS
per-file tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/wear/** = file:/PermissionController/WEAR_OWNERS
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/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/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/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-ca/strings.xml b/PermissionController/res/values-ca/strings.xml
index 720c0161d..fd1e23c51 100644
--- a/PermissionController/res/values-ca/strings.xml
+++ b/PermissionController/res/values-ca/strings.xml
@@ -346,7 +346,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>
diff --git a/PermissionController/res/values-es/strings.xml b/PermissionController/res/values-es/strings.xml
index d73329ef8..84900f171 100644
--- a/PermissionController/res/values-es/strings.xml
+++ b/PermissionController/res/values-es/strings.xml
@@ -250,7 +250,7 @@
<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_header" msgid="7769277978004790414">"Permitidas"</string>
<string name="allowed_always_header" msgid="6455903312589013545">"Permitidas 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>
diff --git a/PermissionController/res/values-et/strings.xml b/PermissionController/res/values-et/strings.xml
index 7622870c7..a289032c0 100644
--- a/PermissionController/res/values-et/strings.xml
+++ b/PermissionController/res/values-et/strings.xml
@@ -207,7 +207,7 @@
<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>
diff --git a/PermissionController/res/values-eu/strings.xml b/PermissionController/res/values-eu/strings.xml
index 4e81fb5f1..73c1015b6 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>
@@ -279,7 +279,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>
diff --git a/PermissionController/res/values-fa/strings.xml b/PermissionController/res/values-fa/strings.xml
index 307c1b5da..f04d8f4c9 100644
--- a/PermissionController/res/values-fa/strings.xml
+++ b/PermissionController/res/values-fa/strings.xml
@@ -320,7 +320,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>
@@ -601,7 +601,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>
diff --git a/PermissionController/res/values-gu/strings.xml b/PermissionController/res/values-gu/strings.xml
index 0ea95ae75..b97892c54 100644
--- a/PermissionController/res/values-gu/strings.xml
+++ b/PermissionController/res/values-gu/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_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>
@@ -579,7 +579,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>
diff --git a/PermissionController/res/values-hr/strings.xml b/PermissionController/res/values-hr/strings.xml
index 7275d1681..54e0cf989 100644
--- a/PermissionController/res/values-hr/strings.xml
+++ b/PermissionController/res/values-hr/strings.xml
@@ -251,7 +251,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>
diff --git a/PermissionController/res/values-it/strings.xml b/PermissionController/res/values-it/strings.xml
index df7a62b8f..d2b42e28b 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>
diff --git a/PermissionController/res/values-iw/strings.xml b/PermissionController/res/values-iw/strings.xml
index 9779e6c66..946b0c08b 100644
--- a/PermissionController/res/values-iw/strings.xml
+++ b/PermissionController/res/values-iw/strings.xml
@@ -207,9 +207,9 @@
<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_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 +266,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 +287,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 +302,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 +358,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>
diff --git a/PermissionController/res/values-kk/strings.xml b/PermissionController/res/values-kk/strings.xml
index 0413f0b54..a5680c1e5 100644
--- a/PermissionController/res/values-kk/strings.xml
+++ b/PermissionController/res/values-kk/strings.xml
@@ -251,7 +251,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>
diff --git a/PermissionController/res/values-ko/strings.xml b/PermissionController/res/values-ko/strings.xml
index dac0845fb..35269a437 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>
diff --git a/PermissionController/res/values-lv/strings.xml b/PermissionController/res/values-lv/strings.xml
index f6f852bfd..131aeff27 100644
--- a/PermissionController/res/values-lv/strings.xml
+++ b/PermissionController/res/values-lv/strings.xml
@@ -456,7 +456,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>
diff --git a/PermissionController/res/values-ne/strings.xml b/PermissionController/res/values-ne/strings.xml
index 9a7a4739a..9fecf53b3 100644
--- a/PermissionController/res/values-ne/strings.xml
+++ b/PermissionController/res/values-ne/strings.xml
@@ -644,7 +644,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>
diff --git a/PermissionController/res/values-pl/strings.xml b/PermissionController/res/values-pl/strings.xml
index ae98edba8..491216726 100644
--- a/PermissionController/res/values-pl/strings.xml
+++ b/PermissionController/res/values-pl/strings.xml
@@ -250,13 +250,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>
diff --git a/PermissionController/res/values-ro/strings.xml b/PermissionController/res/values-ro/strings.xml
index 6544022cf..d75d6c6de 100644
--- a/PermissionController/res/values-ro/strings.xml
+++ b/PermissionController/res/values-ro/strings.xml
@@ -456,7 +456,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>
diff --git a/PermissionController/res/values-sk/strings.xml b/PermissionController/res/values-sk/strings.xml
index d743a2237..4442bbc85 100644
--- a/PermissionController/res/values-sk/strings.xml
+++ b/PermissionController/res/values-sk/strings.xml
@@ -206,7 +206,7 @@
<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>
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 d0317471e..168646ac1 100644
--- a/PermissionController/res/values-sl/strings.xml
+++ b/PermissionController/res/values-sl/strings.xml
@@ -206,7 +206,7 @@
<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>
diff --git a/PermissionController/res/values-sq/strings.xml b/PermissionController/res/values-sq/strings.xml
index 9979f58ad..793b1beb1 100644
--- a/PermissionController/res/values-sq/strings.xml
+++ b/PermissionController/res/values-sq/strings.xml
@@ -445,7 +445,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>
diff --git a/PermissionController/res/values-uk/strings.xml b/PermissionController/res/values-uk/strings.xml
index 7f94a193c..133c1f5e7 100644
--- a/PermissionController/res/values-uk/strings.xml
+++ b/PermissionController/res/values-uk/strings.xml
@@ -206,7 +206,7 @@
<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>
diff --git a/PermissionController/res/values-vi/strings.xml b/PermissionController/res/values-vi/strings.xml
index fe798bab0..ecb94368c 100644
--- a/PermissionController/res/values-vi/strings.xml
+++ b/PermissionController/res/values-vi/strings.xml
@@ -206,7 +206,7 @@
<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>
diff --git a/PermissionController/res/values-watch/donottranslate.xml b/PermissionController/res/values-watch/donottranslate.xml
index 43830a93c..03038ddd1 100644
--- a/PermissionController/res/values-watch/donottranslate.xml
+++ b/PermissionController/res/values-watch/donottranslate.xml
@@ -71,7 +71,7 @@
<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">20sp</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>
diff --git a/PermissionController/res/values-zh-rCN/strings.xml b/PermissionController/res/values-zh-rCN/strings.xml
index ee6f9e3bf..1b76de832 100644
--- a/PermissionController/res/values-zh-rCN/strings.xml
+++ b/PermissionController/res/values-zh-rCN/strings.xml
@@ -203,13 +203,13 @@
<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="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>
diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml
index 2402ed2bf..204fb74f5 100644
--- a/PermissionController/res/xml/roles.xml
+++ b/PermissionController/res/xml/roles.xml
@@ -724,10 +724,6 @@
featureFlag="android.app.appfunctions.flags.Flags.enableAppFunctionManager" />
<permission name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED"
featureFlag="android.app.appfunctions.flags.Flags.enableAppFunctionManager" />
- <permission name="android.permission.COPY_ACCOUNTS"
- featureFlag="android.app.admin.flags.Flags.splitCreateManagedProfileEnabled" />
- <permission name="android.permission.REMOVE_ACCOUNTS"
- featureFlag="android.app.admin.flags.Flags.splitCreateManagedProfileEnabled" />
</permissions>
</role>
@@ -1495,10 +1491,8 @@
<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.COPY_ACCOUNTS"
- featureFlag="android.app.admin.flags.Flags.splitCreateManagedProfileEnabled" />
- <permission name="android.permission.REMOVE_ACCOUNTS"
- featureFlag="android.app.admin.flags.Flags.splitCreateManagedProfileEnabled" />
+ <permission name="android.permission.MANAGE_DEFAULT_APPLICATIONS" minSdkVersion="36"
+ featureFlag="com.android.permission.flags.Flags.crossUserRoleEnabled" />
</permissions>
</role>
@@ -1891,7 +1885,7 @@
shortLabel="@string/role_for_testing_profile_group_exclusivity_short_label"
showNone="true"
uiBehavior="ReservedForTestingProfileGroupExclusivityRoleUiBehavior"
- visible="true"/>
+ visible="false"/>
<!---
~ A role for the vendor package that provides privacy-preserving intelligent processor for
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..1dc0aea48 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
@@ -230,7 +230,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/ReservedForTestingProfileGroupExclusivityRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java
index f02b4d90c..5299886f6 100644
--- a/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java
@@ -49,7 +49,7 @@ public class ReservedForTestingProfileGroupExclusivityRoleBehavior implements Ro
}
@Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
if (RoleFlags.isProfileGroupExclusivityAvailable()) {
Context userContext = UserUtils.getUserContext(context, user);
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/model/AppOp.java b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java
index 56c4944a0..99145c747 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
@@ -26,6 +26,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
+import com.android.role.controller.util.RoleFlags;
import com.android.role.controller.util.PackageUtils;
import java.util.Objects;
@@ -137,8 +138,8 @@ public class AppOp {
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 && RoleFlags.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..889f5263d 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
@@ -26,6 +26,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
+import com.android.role.controller.util.RoleFlags;
import com.android.role.controller.util.UserUtils;
import java.util.Objects;
@@ -97,8 +98,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 && RoleFlags.isAtLeastB())) {
return true;
}
if (Build.VERSION.SDK_INT >= mOptionalMinSdkVersion) {
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 1d49b3c1a..5109e505b 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
@@ -502,8 +502,8 @@ public class Role {
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 && RoleFlags.isAtLeastB()))
&& Build.VERSION.SDK_INT <= mMaxSdkVersion;
}
@@ -1098,11 +1098,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();
}
/**
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 86ca8e2ce..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;
/**
@@ -129,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 4b05554e3..9c3746b79 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
@@ -413,6 +413,9 @@ 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;
diff --git a/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt b/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt
index e6cf094e3..952274d4a 100644
--- a/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt
+++ b/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt
@@ -18,7 +18,6 @@ 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
@@ -55,6 +54,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
@@ -77,6 +77,7 @@ 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" }
@@ -84,9 +85,9 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
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) ==
+ SettingType.fromIdentifier(this, settingIdentifier, isEcmInApp, reason) ==
SettingType.BLOCKED_DUE_TO_PHONE_STATE &&
!Flags.unknownCallPackageInstallBlockingEnabled()
) {
@@ -127,8 +128,10 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
context: Context,
settingIdentifier: String,
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 ->
@@ -189,10 +192,10 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() {
context: Context,
settingIdentifier: String,
isEcmInApp: Boolean,
+ restrictionReason: String?,
): SettingType {
return when {
- settingIdentifier == AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES ->
- BLOCKED_DUE_TO_PHONE_STATE
+ restrictionReason == REASON_PHONE_STATE -> BLOCKED_DUE_TO_PHONE_STATE
!isEcmInApp -> OTHER
PermissionMapping.isRuntimePlatformPermission(settingIdentifier) &&
PermissionMapping.getGroupOfPlatformPermission(settingIdentifier) != null ->
diff --git a/PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt b/PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt
index 8e58d48d9..4c698cda3 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.elements.material3.DialogButtonContent
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionConfirmationDialog
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
import com.android.permissioncontroller.permission.ui.wear.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/service/v33/SafetyCenterQsTileService.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt
index a69b78a06..5ba19f4c0 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt
@@ -70,7 +70,6 @@ class SafetyCenterQsTileService : TileService() {
qsTile.label = getString(R.string.safety_privacy_qs_tile_title)
qsTile.subtitle = getString(R.string.safety_privacy_qs_tile_subtitle)
qsTile.contentDescription = TextUtils.concat(qsTile.label, ", ", qsTile.subtitle)
- qsTile.state = Tile.STATE_ACTIVE
qsTile.updateTile()
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
index c1479caf2..a7114f30b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
@@ -262,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())) {
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..5100b08fd 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java
@@ -271,6 +271,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 +284,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 510d19706..ff3c2cbc1 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt
@@ -18,49 +18,39 @@ 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 androidx.wear.compose.material3.Dialog
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionScaffold
import com.android.permissioncontroller.permission.ui.wear.model.LocationProviderInterceptDialogArgs
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
@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 ->
+fun LocationProviderDialogScreen(
+ showDialog: Boolean,
+ onDismissRequest: () -> Unit,
+ args: LocationProviderInterceptDialogArgs?,
+) {
+ Dialog(show = showDialog, onDismissRequest = onDismissRequest) {
+ args?.run {
WearPermissionScaffold(
- materialUIVersion = WearPermissionMaterialUIVersion.MATERIAL2_5,
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/WearAppPermissionGroupsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt
index ba37205a6..5919fad0d 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.elements.material3.DialogButtonContent
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionConfirmationDialog
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType
import com.android.permissioncontroller.permission.ui.wear.model.RevokeDialogArgs
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
+import com.android.permissioncontroller.permission.ui.wear.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..8815e7905 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.elements.material2.ListFooter
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.ToggleChip
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.toggleChipDisabledColors
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionConfirmationDialog
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.defaultAlertConfirmIcon
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.defaultAlertDismissIcon
import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionConfirmDialogViewModel
import com.android.permissioncontroller.permission.ui.wear.model.ConfirmDialogArgs
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
+import com.android.permissioncontroller.permission.ui.wear.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/WearEnhancedConfirmationScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt
index 1c31ec96f..ab19a9665 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.elements.material2.Chip
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionConfirmationDialog
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder
import com.android.permissioncontroller.permission.ui.wear.model.WearEnhancedConfirmationViewModel
import com.android.permissioncontroller.permission.ui.wear.model.WearEnhancedConfirmationViewModel.ScreenState
+import com.android.permissioncontroller.permission.ui.wear.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 1498b91b6..8287c5e94 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt
@@ -18,10 +18,14 @@ 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.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.stringResource
+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
@@ -38,13 +42,13 @@ import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.N
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.ScrollableScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType
import com.android.permissioncontroller.permission.ui.wear.model.WearGrantPermissionsViewModel
import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3
+import kotlinx.coroutines.delay
@Composable
fun WearGrantPermissionsScreen(
@@ -58,13 +62,7 @@ fun WearGrantPermissionsScreen(
val locationVisibilities = viewModel.locationVisibilitiesLiveData.observeAsState(emptyList())
val preciseLocationChecked = viewModel.preciseLocationCheckedLiveData.observeAsState(false)
val buttonVisibilities = viewModel.buttonVisibilitiesLiveData.observeAsState(emptyList())
- val materialUIVersion =
- if (ResourceHelper.material3Enabled) {
- MATERIAL3
- } else {
- MATERIAL2_5
- }
-
+ val materialUIVersion = ResourceHelper.materialUIVersionInApp
ScrollableScreen(
materialUIVersion = materialUIVersion,
showTimeText = false,
@@ -83,7 +81,7 @@ fun WearGrantPermissionsScreen(
checked = preciseLocationChecked.value,
onCheckedChanged = onLocationSwitchChanged,
label = stringResource(R.string.app_permission_location_accuracy),
- toggleControl = ToggleChipToggleControl.Switch,
+ toggleControl = WearPermissionToggleControlType.Switch,
modifier = Modifier.fillMaxWidth(),
labelMaxLines = Integer.MAX_VALUE,
materialUIVersion = materialUIVersion,
@@ -119,12 +117,29 @@ fun WearGrantPermissionsScreen(
fun setContent(
composeView: ComposeView,
viewModel: WearGrantPermissionsViewModel,
+ onCancelled: () -> Unit,
onButtonClicked: (Int) -> Unit,
onLocationSwitchChanged: (Boolean) -> Unit,
) {
composeView.setContent {
- WearGrantPermissionsScreen(viewModel, onButtonClicked, onLocationSwitchChanged)
+ if (ResourceHelper.materialUIVersionInApp == MATERIAL3) {
+ AsDialog(onCancelled) {
+ WearGrantPermissionsScreen(viewModel, onButtonClicked, onLocationSwitchChanged)
+ }
+ } else {
+ WearGrantPermissionsScreen(viewModel, onButtonClicked, onLocationSwitchChanged)
+ }
+ }
+}
+
+@Composable
+private fun AsDialog(onDismissRequest: () -> Unit, content: @Composable () -> Unit) {
+ val showDialog = remember { mutableStateOf(false) }
+ LaunchedEffect(Unit) {
+ delay(300)
+ showDialog.value = true
}
+ Dialog(show = showDialog.value, onDismissRequest = onDismissRequest, content = content)
}
@Composable
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..d826e501e 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.permission.ui.wear.elements.material3.WearPermissionButton
+import com.android.permissioncontroller.permission.ui.wear.elements.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/WearManageStandardPermissionScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt
index 9aacd65d3..3e347e19c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt
@@ -29,8 +29,9 @@ 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.ui.wear.elements.material3.WearPermissionButton
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder
import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupIcon
import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
import com.android.permissioncontroller.permission.utils.StringUtils
@@ -42,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)
@@ -56,7 +57,7 @@ fun WearManageStandardPermissionScreen(
numAutoRevoked.value,
onPermGroupClick,
onCustomPermissionsClick,
- onAutoRevokedClick
+ onAutoRevokedClick,
)
if (isLoading && permissionGroups.value.isNotEmpty()) {
@@ -92,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) }
@@ -107,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/WearPermissionAppsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt
index 8e779cb8c..2fa6aa7f3 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.permission.ui.wear.elements.material3.WearPermissionButton
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder
+import com.android.permissioncontroller.permission.ui.wear.elements.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/WearPermissionUsageDetailsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt
index 1259c1ab5..63a6cd5a5 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt
@@ -37,15 +37,15 @@ import com.android.permissioncontroller.permission.ui.model.v31.BasePermissionUs
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.ui.wear.elements.material2.Chip
import com.android.permissioncontroller.permission.utils.KotlinUtils
@RequiresApi(Build.VERSION_CODES.S)
@Composable
fun WearPermissionUsageDetailsScreen(
permissionGroup: String,
- viewModel: BasePermissionUsageDetailsViewModel
+ viewModel: BasePermissionUsageDetailsViewModel,
) {
val context = LocalContext.current
val uiData = viewModel.getPermissionUsagesDetailsInfoUiLiveData().observeAsState(null)
@@ -56,7 +56,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 +80,7 @@ fun WearPermissionUsageDetailsScreen(
uiInfo.accessStartTime,
uiInfo.accessEndTime,
uiInfo.showingAttribution,
- uiInfo.attributionTags
+ uiInfo.attributionTags,
)
context.startActivityAsUser(intent, uiInfo.userHandle)
}
@@ -108,7 +108,7 @@ fun WearPermissionUsageDetailsScreen(
onShowSystemClick,
appPermissionAccessUiInfoList,
onChipClick,
- onManagePermissionClick
+ onManagePermissionClick,
)
if (isLoading && uiData.value != null) {
@@ -126,7 +126,7 @@ 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()) {
@@ -142,7 +142,7 @@ internal fun WearPermissionUsageDetailsContent(
.format(uiInfo.accessEndTime),
secondaryLabelMaxLines = Int.MAX_VALUE,
icon = uiInfo.badgedPackageIcon,
- onClick = { onChipClick(uiInfo) }
+ onClick = { onChipClick(uiInfo) },
)
}
}
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..20e0dd69b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt
@@ -32,17 +32,14 @@ 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.ui.wear.elements.material2.Chip
import com.android.permissioncontroller.permission.utils.Utils
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)
@@ -97,7 +94,7 @@ fun WearPermissionUsageScreen(
hasSystemApps,
showSystem.value,
onShowSystemClick,
- permissionGroupPreferences
+ permissionGroupPreferences,
)
if (isLoading && isDataLoaded) {
@@ -111,11 +108,11 @@ 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 = {}) }
@@ -129,7 +126,7 @@ internal fun WearPermissionUsageContent(
secondaryLabelMaxLines = Int.MAX_VALUE,
icon = preference.icon,
enabled = preference.isEnabled,
- onClick = { preference.performClick() }
+ onClick = { preference.performClick() },
)
}
}
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..9170b7d20 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt
@@ -25,9 +25,9 @@ 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.elements.material2.Chip
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.Icon
import com.android.permissioncontroller.permission.ui.wear.model.WearUnusedAppsViewModel
@Composable
@@ -43,7 +43,7 @@ fun WearUnusedAppsScreen(viewModel: WearUnusedAppsViewModel) {
showTimeText = true,
title = getScreenTitle(),
isLoading = loading.value,
- subtitle = getSubTitle(!infoMsgCategoryVisibility.value)
+ subtitle = getSubTitle(!infoMsgCategoryVisibility.value),
) {
for (period in allPeriods) {
if (!unusedAppChips.value.containsKey(period)) {
@@ -62,7 +62,7 @@ fun WearUnusedAppsScreen(viewModel: WearUnusedAppsViewModel) {
secondaryLabel = unusedAppChip.summary,
icon = unusedAppChip.icon,
iconContentDescription = unusedAppChip.contentDescription,
- onClick = unusedAppChip.onClick
+ onClick = unusedAppChip.onClick,
)
}
}
@@ -108,5 +108,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/elements/ScrollableScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
index 6ce7df125..bfa46ae55 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
@@ -19,56 +19,20 @@ 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
-import androidx.compose.foundation.layout.PaddingValues
-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.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.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.material3.WearPermissionScaffold
-import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rotaryWithScroll
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
/**
* Screen that contains a list of items defined using the [content] parameter, adds the time text
@@ -78,7 +42,7 @@ import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionT
*/
@Composable
fun ScrollableScreen(
- materialUIVersion: WearPermissionMaterialUIVersion = MATERIAL2_5,
+ materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
showTimeText: Boolean = true,
title: String? = null,
subtitle: CharSequence? = null,
@@ -86,7 +50,7 @@ fun ScrollableScreen(
isLoading: Boolean = false,
titleTestTag: String? = null,
subtitleTestTag: String? = null,
- content: ScalingLazyListScope.() -> Unit,
+ content: ListScopeWrapper.() -> Unit,
) {
var dismissed by remember { mutableStateOf(false) }
val activity = LocalContext.current.findActivity()
@@ -135,207 +99,6 @@ fun ScrollableScreen(
}
}
-@Composable
-internal fun Wear2Scaffold(
- showTimeText: Boolean,
- title: String?,
- subtitle: CharSequence?,
- image: Any?,
- isLoading: Boolean,
- content: ScalingLazyListScope.() -> Unit,
- titleTestTag: String? = null,
- subtitleTestTag: String? = null,
-) {
- val itemsSpacedBy = 4.dp
- val screenWidth = LocalConfiguration.current.screenWidthDp
- val screenHeight = LocalConfiguration.current.screenHeightDp
- val scrollContentHorizontalPadding = (screenWidth * 0.052).dp
- val titleHorizontalPadding = (screenWidth * 0.0884).dp
- val subtitleHorizontalPadding = (screenWidth * 0.0416).dp
- val scrollContentTopPadding = (screenHeight * 0.1456).dp - itemsSpacedBy
- val scrollContentBottomPadding = (screenHeight * 0.3636).dp
- val titleBottomPadding =
- if (subtitle == null) {
- 8.dp
- } else {
- 4.dp
- }
- val subtitleBottomPadding = 8.dp
- val timeTextTopPadding =
- if (showTimeText) {
- 1.dp
- } else {
- 0.dp
- }
- val titlePaddingValues =
- PaddingValues(
- start = titleHorizontalPadding,
- top = 4.dp,
- bottom = titleBottomPadding,
- end = titleHorizontalPadding,
- )
- val subTitlePaddingValues =
- PaddingValues(
- start = subtitleHorizontalPadding,
- top = 4.dp,
- bottom = subtitleBottomPadding,
- end = subtitleHorizontalPadding,
- )
- val initialCenterIndex = 0
- val centerHeightDp = Dp(LocalConfiguration.current.screenHeightDp / 2.0f)
- // We are adding TimeText's padding to create a smooth scrolling
- val initialCenterItemScrollOffset = scrollContentTopPadding + timeTextTopPadding
- val scrollAwayOffset = centerHeightDp - initialCenterItemScrollOffset
- val focusRequester = remember { FocusRequester() }
- val listState = remember { ScalingLazyListState(initialCenterItemIndex = initialCenterIndex) }
- LaunchedEffect(title) {
- listState.animateScrollToItem(index = 0) // Scroll to the top when triggerValue changes
- }
- 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,
- ),
- timeText = {
- if (showTimeText && !isLoading) {
- TimeText(
- modifier =
- Modifier.scrollAway(listState, initialCenterIndex, scrollAwayOffset)
- .padding(top = timeTextTopPadding)
- )
- }
- },
- vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) },
- positionIndicator =
- if (!isLoading) {
- { 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,
- // Set autoCentering to null to avoid adding extra padding based on the
- // content.
- autoCentering = null,
- contentPadding =
- PaddingValues(
- start = scrollContentHorizontalPadding,
- end = scrollContentHorizontalPadding,
- top = scrollContentTopPadding,
- bottom = scrollContentBottomPadding,
- ),
- ) {
- staticItem()
- image?.let {
- val imageModifier = Modifier.size(24.dp)
- when (image) {
- is Int ->
- item {
- Image(
- painter = painterResource(id = image),
- contentDescription = null,
- contentScale = ContentScale.Crop,
- modifier = imageModifier,
- colorFilter = ColorFilter.tint(iconColor),
- )
- }
- is Drawable ->
- item {
- Image(
- painter = rememberDrawablePainter(image),
- contentDescription = null,
- contentScale = ContentScale.Crop,
- modifier = imageModifier,
- colorFilter = ColorFilter.tint(iconColor),
- )
- }
- else -> {}
- }
- }
- if (title != null) {
- item {
- var modifier: Modifier = Modifier
- if (titleTestTag != null) {
- modifier = modifier.testTag(titleTestTag)
- }
- ListHeader(modifier = Modifier.padding(titlePaddingValues)) {
- Text(
- text = title,
- textAlign = TextAlign.Center,
- modifier = modifier,
- )
- }
- }
- }
- if (subtitle != null) {
- item {
- var modifier: Modifier =
- Modifier.align(Alignment.Center).padding(subTitlePaddingValues)
- if (subtitleTestTag != null) {
- modifier = modifier.testTag(subtitleTestTag)
- }
- AnnotatedText(
- text = subtitle,
- style =
- MaterialTheme.typography.body2.copy(
- color = MaterialTheme.colors.onSurfaceVariant
- ),
- modifier = modifier,
- shouldCapitalize = true,
- )
- }
- }
-
- content()
- }
- RequestFocusOnResume(focusRequester = focusRequester)
- }
- }
- }
- }
-}
-
-private fun ScalingLazyListScope.staticItem() {
- /*
- This empty item helps to ensure accurate scroll offset calculation. If auto centering is enabled
- initial item's(first item for us) center matches the center of the screen. Scroll offset is 0 at
- that point.
-
- if auto centering is not enabled, initial item will start at the top of the screen with the
- scroll offset equal to ScreenHeight/2 - scrollContentTopPadding - firstItemHeight/2.
-
- We need to this offset value to properly move time text.That is the scroll-away offset of the
- Time Text is equal to the scroll offset of the list at initial position.
-
- It is easier to calculate if we know the values of ScreenHeight, ScrollContentTopPadding and
- FirstItem's height. ScreenHeight and ScrollContentPadding are constants but height of the
- FirstItem depends on the content. Instead of measuring the height, we can simplify the
- calculation with an empty item with 0dp height.
- */
- item {}
-}
-
-@Composable
-private fun RequestFocusOnResume(focusRequester: FocusRequester) {
- val lifecycleOwner = LocalLifecycleOwner.current
- LaunchedEffect(Unit) {
- lifecycleOwner.repeatOnLifecycle(state = Lifecycle.State.RESUMED) {
- focusRequester.requestFocus()
- }
- }
-}
-
internal fun dismiss(activity: Activity) {
if (activity is FragmentActivity) {
if (!activity.supportFragmentManager.popBackStackImmediate()) {
@@ -364,3 +127,7 @@ internal fun Context.findActivity(): Activity {
}
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)
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt
deleted file mode 100644
index b6f6db4d3..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt
+++ /dev/null
@@ -1,58 +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.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.role
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.semantics.stateDescription
-import com.android.permissioncontroller.R
-
-enum class ToggleChipToggleControl {
- Switch,
- Radio,
- Checkbox,
-}
-
-@Composable
-fun Modifier.toggleControlSemantics(
- toggleControl: ToggleChipToggleControl,
- checked: Boolean,
-): Modifier {
- 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
- }
- )
-
- return semantics {
- role = semanticsRole
- stateDescription = stateDescriptionSemantics
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/AlertDialog.kt
index c07d2ba9e..a49d8822e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/AlertDialog.kt
@@ -14,20 +14,17 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.permission.ui.wear.elements.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 +32,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.permission.ui.wear.elements.material2.layout.ScalingLazyColumnDefaults
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumnState
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.rememberColumnState
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder
/**
* This component is an alternative to [AlertContent], providing the following:
@@ -54,95 +51,44 @@ import com.android.permissioncontroller.permission.ui.wear.elements.layout.remem
*/
@Composable
fun AlertDialog(
+ title: String? = null,
message: String,
- iconRes: Int? = null,
- okButtonIcon: Any = Icons.Default.Check,
- cancelButtonIcon: Any = Icons.Default.Close,
- onCancelButtonClick: () -> Unit,
- onOKButtonClick: () -> Unit,
+ positiveButtonContent: DialogButtonContent?,
+ negativeButtonContent: DialogButtonContent?,
showDialog: Boolean,
- scalingLazyListState: ScalingLazyListState,
modifier: Modifier = Modifier,
- title: String? = null,
- okButtonContentDescription: String = stringResource(android.R.string.ok),
- cancelButtonContentDescription: String = stringResource(android.R.string.cancel)
+ iconRes: WearPermissionIconBuilder? = null,
+ scalingLazyListState: ScalingLazyListState,
) {
val focusManager = LocalFocusManager.current
Dialog(
showDialog = showDialog,
onDismissRequest = {
focusManager.clearFocus()
- onCancelButtonClick()
+ negativeButtonContent?.onClick?.invoke()
},
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,
- showDialog: Boolean,
- scalingLazyListState: ScalingLazyListState,
- modifier: Modifier = Modifier,
- title: String? = null,
- buttonContentDescription: String = stringResource(android.R.string.ok)
-) {
- Dialog(
- showDialog = showDialog,
- onDismissRequest = {},
- 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 +101,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 +131,7 @@ fun AlertContent(
maxWidth =
(maxScreenWidthPx *
(1f - totalPaddingPercentage * 2f / 100f))
- .toInt(),
+ .toInt()
),
)
.lineCount
@@ -200,21 +146,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/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Chip.kt
index 40f097c67..15542ec20 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Chip.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.permission.ui.wear.elements.material2
import android.graphics.drawable.Drawable
import androidx.annotation.StringRes
@@ -46,6 +46,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.permission.ui.wear.elements.rememberDrawablePainter
/**
* This component is an alternative to [Chip], providing the following:
@@ -67,7 +68,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 +88,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 +121,7 @@ fun Chip(
largeIcon = largeIcon,
textColor = textColor,
colors = colors,
- enabled = enabled
+ enabled = enabled,
)
}
@@ -143,7 +144,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 +158,7 @@ fun Chip(
textColor = textColor,
iconColor = iconColor,
colors = colors,
- enabled = enabled
+ enabled = enabled,
)
}
@@ -180,7 +181,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
@@ -196,8 +197,8 @@ fun Chip(
style =
MaterialTheme.typography.button.copy(
fontWeight = FontWeight.W600,
- hyphens = Hyphens.Auto
- )
+ hyphens = Hyphens.Auto,
+ ),
)
}
@@ -209,7 +210,7 @@ fun Chip(
color = secondaryTextColor,
overflow = TextOverflow.Ellipsis,
maxLines = secondaryLabelMaxLines ?: 1,
- style = MaterialTheme.typography.caption2
+ style = MaterialTheme.typography.caption2,
)
}
}
@@ -221,7 +222,7 @@ fun Chip(
start = 10.dp,
top = verticalPadding,
end = ChipDefaults.ChipHorizontalPadding,
- bottom = verticalPadding
+ bottom = verticalPadding,
)
} else {
ChipDefaults.ContentPadding
@@ -236,7 +237,7 @@ fun Chip(
colors = colors,
enabled = enabled,
contentPadding = contentPadding,
- shape = RoundedCornerShape(26.dp)
+ shape = RoundedCornerShape(26.dp),
)
}
@@ -258,6 +259,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/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Icon.kt
index 1a304b37e..3cfac7eef 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Icon.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.permission.ui.wear.elements.material2
import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
@@ -29,6 +29,7 @@ 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.permission.ui.wear.elements.rememberDrawablePainter
/**
* This component is an alternative to [Icon], providing the following:
@@ -40,7 +41,7 @@ public 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
@@ -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,
)
}
@@ -62,7 +63,7 @@ public 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
@@ -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,7 +115,7 @@ fun Icon(
painter = rememberDrawablePainter(icon),
contentDescription = contentDescription,
modifier = iconModifier,
- tint = tint
+ tint = tint,
)
}
else -> throw IllegalArgumentException("Type not supported.")
@@ -123,5 +124,5 @@ fun Icon(
public enum class IconRtlMode {
Default,
- Mirrored
+ Mirrored,
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ListFooter.kt
index 5ed912ec6..4f6d47faf 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ListFooter.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.permission.ui.wear.elements.material2
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
@@ -52,7 +52,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 +62,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/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ListHeader.kt
index 0a2a3937c..2d3eb0d52 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ListHeader.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.permission.ui.wear.elements.material2
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -60,7 +60,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,14 +69,14 @@ fun ListHeader(
mergeDescendants = true
) {
heading()
- }
+ },
) {
CompositionLocalProvider(
LocalContentColor provides contentColor,
LocalTextStyle provides
MaterialTheme.typography.title3.copy(
fontWeight = FontWeight.W600,
- hyphens = Hyphens.Auto
+ hyphens = Hyphens.Auto,
),
) {
content()
@@ -111,7 +111,7 @@ fun ListSubheader(
.fillMaxWidth()
.wrapContentSize(align = Alignment.CenterStart)
.background(backgroundColor)
- .semantics(mergeDescendants = true) { heading() }
+ .semantics(mergeDescendants = true) { heading() },
) {
CompositionLocalProvider(
LocalContentColor provides contentColor,
@@ -120,7 +120,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/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ResponsiveDialog.kt
index e1e869f71..4cb3ab615 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ResponsiveDialog.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/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.permission.ui.wear.elements.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.permission.ui.wear.elements.material2.layout.ScalingLazyColumn
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumnDefaults.responsive
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumnState
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.rememberColumnState
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.defaultAlertConfirmIcon
+import com.android.permissioncontroller.permission.ui.wear.elements.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/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ToggleChip.kt
index 2e89586c9..bfb5d114d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ToggleChip.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.wear.elements
+package com.android.permissioncontroller.permission.ui.wear.elements.material2
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.BoxScope
@@ -29,6 +29,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.wear.compose.material.ChipDefaults
@@ -39,6 +44,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.permission.ui.wear.elements.material3.WearPermissionToggleControlType
/**
* This component is an alternative to [ToggleChip], providing the following:
@@ -52,7 +59,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,
@@ -92,15 +99,16 @@ 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
@@ -127,7 +135,7 @@ fun ToggleChip(
checked = checked,
onCheckedChange = { newChecked ->
// Radio buttons cannot be toggled off by tapping on it again.
- if (toggleControl != ToggleChipToggleControl.Radio || newChecked) {
+ if (toggleControl != WearPermissionToggleControlType.Radio || newChecked) {
onCheckedChanged.invoke(newChecked)
}
},
@@ -219,3 +227,29 @@ fun toggleChipBackgroundColors(): ToggleChipColors {
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/material2/Wear2Scaffold.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Wear2Scaffold.kt
new file mode 100644
index 000000000..866b7f012
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Wear2Scaffold.kt
@@ -0,0 +1,258 @@
+/*
+ * 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.permission.ui.wear.elements.material2
+
+import android.graphics.drawable.Drawable
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+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.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+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.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.lifecycle.Lifecycle
+import androidx.lifecycle.compose.LocalLifecycleOwner
+import androidx.lifecycle.repeatOnLifecycle
+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.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.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.AnnotatedText
+import com.android.permissioncontroller.permission.ui.wear.elements.rememberDrawablePainter
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme
+
+/**
+ * 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 Wear2Scaffold(
+ showTimeText: Boolean,
+ title: String?,
+ subtitle: CharSequence?,
+ image: Any?,
+ isLoading: Boolean,
+ content: ScalingLazyListScope.() -> Unit,
+ titleTestTag: String? = null,
+ subtitleTestTag: String? = null,
+) {
+ val itemsSpacedBy = 4.dp
+ val screenWidth = LocalConfiguration.current.screenWidthDp
+ val screenHeight = LocalConfiguration.current.screenHeightDp
+ val scrollContentHorizontalPadding = (screenWidth * 0.052).dp
+ val titleHorizontalPadding = (screenWidth * 0.0884).dp
+ val subtitleHorizontalPadding = (screenWidth * 0.0416).dp
+ val scrollContentTopPadding = (screenHeight * 0.1456).dp - itemsSpacedBy
+ val scrollContentBottomPadding = (screenHeight * 0.3636).dp
+ val titleBottomPadding =
+ if (subtitle == null) {
+ 8.dp
+ } else {
+ 4.dp
+ }
+ val subtitleBottomPadding = 8.dp
+ val timeTextTopPadding =
+ if (showTimeText) {
+ 1.dp
+ } else {
+ 0.dp
+ }
+ val titlePaddingValues =
+ PaddingValues(
+ start = titleHorizontalPadding,
+ top = 4.dp,
+ bottom = titleBottomPadding,
+ end = titleHorizontalPadding,
+ )
+ val subTitlePaddingValues =
+ PaddingValues(
+ start = subtitleHorizontalPadding,
+ top = 4.dp,
+ bottom = subtitleBottomPadding,
+ end = subtitleHorizontalPadding,
+ )
+ val initialCenterIndex = 0
+ val centerHeightDp = Dp(LocalConfiguration.current.screenHeightDp / 2.0f)
+ // We are adding TimeText's padding to create a smooth scrolling
+ val initialCenterItemScrollOffset = scrollContentTopPadding + timeTextTopPadding
+ val scrollAwayOffset = centerHeightDp - initialCenterItemScrollOffset
+ val focusRequester = remember { FocusRequester() }
+ val listState = remember { ScalingLazyListState(initialCenterItemIndex = initialCenterIndex) }
+ LaunchedEffect(title) {
+ listState.animateScrollToItem(index = 0) // Scroll to the top when triggerValue changes
+ }
+ WearPermissionTheme {
+ Scaffold(
+ modifier = Modifier.focusRequester(focusRequester),
+ timeText = {
+ if (showTimeText && !isLoading) {
+ TimeText(
+ modifier =
+ Modifier.scrollAway(listState, initialCenterIndex, scrollAwayOffset)
+ .padding(top = timeTextTopPadding)
+ )
+ }
+ },
+ vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) },
+ positionIndicator =
+ if (!isLoading) {
+ { 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,
+ // Set autoCentering to null to avoid adding extra padding based on the
+ // content.
+ autoCentering = null,
+ contentPadding =
+ PaddingValues(
+ start = scrollContentHorizontalPadding,
+ end = scrollContentHorizontalPadding,
+ top = scrollContentTopPadding,
+ bottom = scrollContentBottomPadding,
+ ),
+ ) {
+ staticItem()
+ image?.let {
+ val imageModifier = Modifier.size(24.dp)
+ when (image) {
+ is Int ->
+ item {
+ Image(
+ painter = painterResource(id = image),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = imageModifier,
+ colorFilter = ColorFilter.tint(iconColor),
+ )
+ }
+ is Drawable ->
+ item {
+ Image(
+ painter = rememberDrawablePainter(image),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = imageModifier,
+ colorFilter = ColorFilter.tint(iconColor),
+ )
+ }
+ else -> {}
+ }
+ }
+ if (title != null) {
+ item {
+ var modifier: Modifier = Modifier
+ if (titleTestTag != null) {
+ modifier = modifier.testTag(titleTestTag)
+ }
+ ListHeader(modifier = Modifier.padding(titlePaddingValues)) {
+ Text(
+ text = title,
+ textAlign = TextAlign.Center,
+ modifier = modifier,
+ )
+ }
+ }
+ }
+ if (subtitle != null) {
+ item {
+ var modifier: Modifier =
+ Modifier.align(Alignment.Center).padding(subTitlePaddingValues)
+ if (subtitleTestTag != null) {
+ modifier = modifier.testTag(subtitleTestTag)
+ }
+ AnnotatedText(
+ text = subtitle,
+ style =
+ MaterialTheme.typography.body2.copy(
+ color = MaterialTheme.colors.onSurfaceVariant
+ ),
+ modifier = modifier,
+ shouldCapitalize = true,
+ )
+ }
+ }
+
+ content()
+ }
+ RequestFocusOnResume(focusRequester = focusRequester)
+ }
+ }
+ }
+ }
+}
+
+private fun ScalingLazyListScope.staticItem() {
+ /*
+ This empty item helps to ensure accurate scroll offset calculation. If auto centering is enabled
+ initial item's(first item for us) center matches the center of the screen. Scroll offset is 0 at
+ that point.
+
+ if auto centering is not enabled, initial item will start at the top of the screen with the
+ scroll offset equal to ScreenHeight/2 - scrollContentTopPadding - firstItemHeight/2.
+
+ We need to this offset value to properly move time text.That is the scroll-away offset of the
+ Time Text is equal to the scroll offset of the list at initial position.
+
+ It is easier to calculate if we know the values of ScreenHeight, ScrollContentTopPadding and
+ FirstItem's height. ScreenHeight and ScrollContentPadding are constants but height of the
+ FirstItem depends on the content. Instead of measuring the height, we can simplify the
+ calculation with an empty item with 0dp height.
+ */
+ item {}
+}
+
+@Composable
+private fun RequestFocusOnResume(focusRequester: FocusRequester) {
+ val lifecycleOwner = LocalLifecycleOwner.current
+ LaunchedEffect(Unit) {
+ lifecycleOwner.repeatOnLifecycle(state = Lifecycle.State.RESUMED) {
+ focusRequester.requestFocus()
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnDefaults.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/layout/ScalingLazyColumnDefaults.kt
index 550f1dc24..c06fdaf14 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnDefaults.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/layout/ScalingLazyColumnDefaults.kt
@@ -16,7 +16,7 @@
@file:Suppress("ObjectLiteralToLambda")
-package com.android.permissioncontroller.permission.ui.wear.elements.layout
+package com.android.permissioncontroller.permission.ui.wear.elements.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.permission.ui.wear.elements.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/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/layout/ScalingLazyColumnState.kt
index 0603647b1..0e669f6ff 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnState.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/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.permission.ui.wear.elements.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.permission.ui.wear.elements.material2.layout.ScalingLazyColumnDefaults.responsiveScalingParams
+import com.android.permissioncontroller.permission.ui.wear.elements.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/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt
index 79a8963d8..9a89e0809 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt
@@ -33,7 +33,8 @@ 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.permission.ui.wear.elements.Chip
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.Chip
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
/**
@@ -45,8 +46,7 @@ import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionM
fun WearPermissionButton(
label: String,
modifier: Modifier = Modifier,
- materialUIVersion: WearPermissionMaterialUIVersion =
- WearPermissionMaterialUIVersion.MATERIAL2_5,
+ materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
iconBuilder: WearPermissionIconBuilder? = null,
labelMaxLines: Int? = null,
secondaryLabel: String? = null,
@@ -63,7 +63,7 @@ fun WearPermissionButton(
modifier = modifier,
secondaryLabel = secondaryLabel,
secondaryLabelMaxLines = secondaryLabelMaxLines,
- icon = { iconBuilder?.build() },
+ icon = iconBuilder?.let { { iconBuilder.build() } },
largeIcon = false,
colors = style.material2ChipColors(),
enabled = enabled,
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt
index 504c69bb0..36d3f9f33 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt
@@ -21,8 +21,8 @@ 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.permission.ui.wear.elements.chipDefaultColors
-import com.android.permissioncontroller.permission.ui.wear.elements.chipDisabledColors
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.chipDefaultColors
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.chipDisabledColors
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle.DisabledLike
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle.Primary
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle.Secondary
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionConfirmationDialog.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionConfirmationDialog.kt
new file mode 100644
index 000000000..0e1bf1fbe
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/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.permission.ui.wear.elements.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.permission.ui.wear.elements.material2.AlertDialog
+import com.android.permissioncontroller.permission.ui.wear.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/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt
index b7521d073..52674b50d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt
@@ -17,12 +17,16 @@ package com.android.permissioncontroller.permission.ui.wear.elements.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.permission.ui.wear.elements.rememberDrawablePainter
@@ -66,7 +70,7 @@ class WearPermissionIconBuilder private constructor() {
}
fun modifier(modifier: Modifier): WearPermissionIconBuilder {
- this.modifier then modifier
+ this.modifier = modifier then this.modifier
return this
}
@@ -99,3 +103,11 @@ class WearPermissionIconBuilder private constructor() {
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/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt
index cd18b5b09..35efe5db1 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt
@@ -21,7 +21,7 @@ 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.permission.ui.wear.elements.ListFooter
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.ListFooter
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
/** This component is creates a transparent styled button to use as a list footer. */
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListSubHeader.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListSubHeader.kt
new file mode 100644
index 000000000..ddcd93326
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/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.permission.ui.wear.elements.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.permission.ui.wear.elements.material2.ListSubheader
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
+import com.android.permissioncontroller.permission.ui.wear.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/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt
index 9a926f5a3..35bdf583e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt
@@ -17,6 +17,7 @@ package com.android.permissioncontroller.permission.ui.wear.elements.material3
import android.graphics.drawable.Drawable
import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.PaddingValues
@@ -33,41 +34,61 @@ 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.ScrollInfoProvider
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
+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.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.permission.ui.wear.elements.AnnotatedText
-import com.android.permissioncontroller.permission.ui.wear.elements.Wear2Scaffold
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumn
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState
-import com.android.permissioncontroller.permission.ui.wear.elements.layout.rememberResponsiveColumnState
+import com.android.permissioncontroller.permission.ui.wear.elements.ListScopeWrapper
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.Wear2Scaffold
import com.android.permissioncontroller.permission.ui.wear.elements.rememberDrawablePainter
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
import com.android.permissioncontroller.permission.ui.wear.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() } }
+ }
+}
+
+private class ScalingScopeConverter(private val scope: ScalingLazyListScope) : ListScopeWrapper {
+ override fun item(key: Any?, contentType: Any?, content: @Composable () -> Unit) {
+ scope.item { 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
internal fun WearPermissionScaffold(
- materialUIVersion: WearPermissionMaterialUIVersion = MATERIAL2_5,
+ materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
showTimeText: Boolean,
title: String?,
subtitle: CharSequence?,
image: Any?,
isLoading: Boolean,
- content: ScalingLazyListScope.() -> Unit,
+ content: ListScopeWrapper.() -> Unit,
titleTestTag: String? = null,
subtitleTestTag: String? = null,
) {
@@ -79,7 +100,7 @@ internal fun WearPermissionScaffold(
subtitle,
image,
isLoading,
- content,
+ { content.invoke(ScalingScopeConverter(this)) },
titleTestTag,
subtitleTestTag,
)
@@ -90,7 +111,7 @@ internal fun WearPermissionScaffold(
subtitle,
image,
isLoading,
- content,
+ { content.invoke(TransformingScopeConverter(this)) },
titleTestTag,
subtitleTestTag,
)
@@ -104,7 +125,7 @@ private fun WearPermissionScaffoldInternal(
subtitle: CharSequence?,
image: Any?,
isLoading: Boolean,
- content: ScalingLazyListScope.() -> Unit,
+ content: TransformingLazyColumnScope.() -> Unit,
titleTestTag: String? = null,
subtitleTestTag: String? = null,
) {
@@ -114,14 +135,12 @@ private fun WearPermissionScaffoldInternal(
WearPermissionScaffoldPaddingDefaults(
screenWidth = screenWidth,
screenHeight = screenHeight,
- titleNeedsLargePadding = subtitle == null,
)
- val columnState =
- rememberResponsiveColumnState(contentPadding = { paddingDefaults.scrollContentPadding })
+ val columnState = rememberTransformingLazyColumnState()
WearPermissionTheme(version = WearPermissionMaterialUIVersion.MATERIAL3) {
AppScaffold(timeText = wearPermissionTimeText(showTimeText && !isLoading)) {
ScreenScaffold(
- scrollInfoProvider = ScrollInfoProvider(columnState.state),
+ scrollInfoProvider = ScrollInfoProvider(columnState),
scrollIndicator = wearPermissionScrollIndicator(!isLoading, columnState),
) {
Box(modifier = Modifier.fillMaxSize()) {
@@ -129,11 +148,13 @@ private fun WearPermissionScaffoldInternal(
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
} else {
ScrollingView(
+ contentPadding = paddingDefaults.scrollContentPadding,
columnState = columnState,
icon = painterFromImage(image),
title = title,
titleTestTag = titleTestTag,
- titlePaddingValues = paddingDefaults.titlePaddingValues,
+ titlePaddingValues =
+ paddingDefaults.titlePaddingValues(subtitle == null),
subtitle = subtitle,
subtitleTestTag = subtitleTestTag,
subTitlePaddingValues = paddingDefaults.subTitlePaddingValues,
@@ -146,45 +167,10 @@ private fun WearPermissionScaffoldInternal(
}
}
-private class WearPermissionScaffoldPaddingDefaults(
- screenWidth: Int,
- screenHeight: Int,
- titleNeedsLargePadding: Boolean,
-) {
- private val firstSpacerItemHeight = 0.dp
- private val scrollContentHorizontalPadding = (screenWidth * 0.052).dp
- private val titleHorizontalPadding = (screenWidth * 0.0884).dp
- private val subtitleHorizontalPadding = (screenWidth * 0.0416).dp
- private val scrollContentTopPadding = (screenHeight * 0.1456).dp - firstSpacerItemHeight
- private val scrollContentBottomPadding = (screenHeight * 0.3636).dp
- private val defaultItemPadding = 4.dp
- private val largeItemPadding = 8.dp
- val titlePaddingValues =
- PaddingValues(
- start = titleHorizontalPadding,
- top = defaultItemPadding,
- bottom = if (titleNeedsLargePadding) largeItemPadding else defaultItemPadding,
- end = titleHorizontalPadding,
- )
- val subTitlePaddingValues =
- PaddingValues(
- start = subtitleHorizontalPadding,
- top = defaultItemPadding,
- bottom = largeItemPadding,
- end = subtitleHorizontalPadding,
- )
- val scrollContentPadding =
- PaddingValues(
- start = scrollContentHorizontalPadding,
- end = scrollContentHorizontalPadding,
- top = scrollContentTopPadding,
- bottom = scrollContentBottomPadding,
- )
-}
-
@Composable
private fun BoxScope.ScrollingView(
- columnState: ScalingLazyColumnState,
+ contentPadding: PaddingValues,
+ columnState: TransformingLazyColumnState,
icon: Painter?,
title: String?,
titleTestTag: String?,
@@ -192,16 +178,26 @@ private fun BoxScope.ScrollingView(
subtitleTestTag: String?,
titlePaddingValues: PaddingValues,
subTitlePaddingValues: PaddingValues,
- content: ScalingLazyListScope.() -> Unit,
+ content: TransformingLazyColumnScope.() -> Unit,
) {
- ScalingLazyColumn(columnState = columnState) {
- iconItem(icon, Modifier.size(24.dp))
- titleItem(text = title, testTag = titleTestTag, contentPaddingValues = titlePaddingValues)
- subtitleItem(
- text = subtitle,
- testTag = subtitleTestTag,
- modifier = Modifier.align(Alignment.Center).padding(subTitlePaddingValues),
- )
+ TransformingLazyColumn(
+ contentPadding = contentPadding,
+ state = columnState,
+ modifier = Modifier.background(MaterialTheme.colorScheme.background),
+ ) {
+ with(TransformingScopeConverter(this)) {
+ iconItem(icon, Modifier.size(IconButtonDefaults.LargeIconSize))
+ titleItem(
+ text = title,
+ testTag = titleTestTag,
+ contentPaddingValues = titlePaddingValues,
+ )
+ subtitleItem(
+ text = subtitle,
+ testTag = subtitleTestTag,
+ modifier = Modifier.align(Alignment.Center).padding(subTitlePaddingValues),
+ )
+ }
content()
}
}
@@ -216,15 +212,10 @@ private fun wearPermissionTimeText(showTime: Boolean): @Composable () -> Unit {
private fun wearPermissionScrollIndicator(
showIndicator: Boolean,
- columnState: ScalingLazyColumnState,
+ columnState: TransformingLazyColumnState,
): @Composable (BoxScope.() -> Unit)? {
return if (showIndicator) {
- {
- ScrollIndicator(
- modifier = Modifier.align(Alignment.CenterEnd),
- state = columnState.state,
- )
- }
+ { ScrollIndicator(modifier = Modifier.align(Alignment.CenterEnd), state = columnState) }
} else {
null
}
@@ -246,7 +237,7 @@ private fun Modifier.optionalTestTag(tag: String?): Modifier {
return this then testTag(tag)
}
-private fun ScalingLazyListScope.iconItem(painter: Painter?, modifier: Modifier = Modifier) =
+private fun ListScopeWrapper.iconItem(painter: Painter?, modifier: Modifier = Modifier) =
painter?.let {
item {
val iconColor = WearPermissionButtonStyle.Secondary.material3ButtonColors().iconColor
@@ -260,14 +251,14 @@ private fun ScalingLazyListScope.iconItem(painter: Painter?, modifier: Modifier
}
}
-private fun ScalingLazyListScope.titleItem(
+private fun ListScopeWrapper.titleItem(
text: String?,
testTag: String?,
contentPaddingValues: PaddingValues,
modifier: Modifier = Modifier,
) =
text?.let {
- item {
+ item(contentType = "header") {
ListHeader(
modifier = modifier.requiredHeightIn(1.dp), // We do not want default min height
contentPadding = contentPaddingValues,
@@ -276,12 +267,13 @@ private fun ScalingLazyListScope.titleItem(
text = it,
textAlign = TextAlign.Center,
modifier = Modifier.optionalTestTag(testTag),
+ style = MaterialTheme.typography.titleLarge.copy(hyphens = Hyphens.Auto),
)
}
}
}
-private fun ScalingLazyListScope.subtitleItem(
+private fun ListScopeWrapper.subtitleItem(
text: CharSequence?,
testTag: String?,
modifier: Modifier = Modifier,
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffoldPaddingDefaults.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffoldPaddingDefaults.kt
new file mode 100644
index 000000000..14eaec4bf
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffoldPaddingDefaults.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.permission.ui.wear.elements.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 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,
+ )
+
+ 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/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt
index 9841ca521..d37d17a84 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt
@@ -20,16 +20,26 @@ 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.permission.ui.wear.elements.ToggleChip
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
-import com.android.permissioncontroller.permission.ui.wear.elements.toggleControlSemantics
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.ToggleChip
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
import com.android.permissioncontroller.permission.ui.wear.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.
@@ -39,14 +49,13 @@ import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionM
*/
@Composable
fun WearPermissionToggleControl(
- toggleControl: ToggleChipToggleControl,
+ toggleControl: WearPermissionToggleControlType,
label: String,
checked: Boolean,
onCheckedChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier,
labelMaxLines: Int? = null,
- materialUIVersion: WearPermissionMaterialUIVersion =
- WearPermissionMaterialUIVersion.MATERIAL2_5,
+ materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings,
iconBuilder: WearPermissionIconBuilder? = null,
secondaryLabel: String? = null,
secondaryLabelMaxLines: Int? = null,
@@ -87,7 +96,7 @@ fun WearPermissionToggleControl(
@Composable
private fun WearPermissionToggleControlInternal(
label: String,
- toggleControl: ToggleChipToggleControl,
+ toggleControl: WearPermissionToggleControlType,
checked: Boolean,
onCheckedChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier,
@@ -118,15 +127,19 @@ private fun WearPermissionToggleControlInternal(
}
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()
- // .heightIn(min = 58.dp) // TODO(b/370783358): This should be a overlaid value
- .toggleControlSemantics(toggleControl, checked)
+ modifier.fillMaxWidth().semantics { stateDescription = toggleControlStateDescription }
when (toggleControl) {
- ToggleChipToggleControl.Radio ->
+ WearPermissionToggleControlType.Radio ->
RadioButton(
selected = checked,
onSelect = {
@@ -144,7 +157,7 @@ private fun WearPermissionToggleControlInternal(
colors = style.radioButtonColorScheme(),
)
- ToggleChipToggleControl.Checkbox ->
+ WearPermissionToggleControlType.Checkbox ->
CheckboxButton(
checked = checked,
onCheckedChange = onCheckedChanged,
@@ -156,7 +169,7 @@ private fun WearPermissionToggleControlInternal(
colors = style.checkboxColorScheme(),
)
- ToggleChipToggleControl.Switch ->
+ WearPermissionToggleControlType.Switch ->
SwitchButton(
checked = checked,
onCheckedChange = onCheckedChanged,
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt
index b5746f019..048a06861 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt
@@ -25,8 +25,8 @@ 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.permission.ui.wear.elements.toggleChipBackgroundColors
-import com.android.permissioncontroller.permission.ui.wear.elements.toggleChipDisabledColors
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.toggleChipBackgroundColors
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.toggleChipDisabledColors
/**
* Defines toggle control styles, It helps in setting the right colors scheme to a toggle control.
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/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/ResourceHelper.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt
index c7ed0958c..2a40a625f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt
@@ -27,11 +27,35 @@ internal object ResourceHelper {
private const val MATERIAL3_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled"
- val material3Enabled: Boolean
+ /* 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 {
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
index 8823bee07..736d543a3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt
@@ -53,9 +53,6 @@ fun WearPermissionTheme(
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
WearPermissionLegacyTheme(content)
} else {
- // Whether we are ready to use material3 for any screen.
- val useBridgedTheme = ResourceHelper.material3Enabled
-
// Material3 UI controls are still being used in the screen that the theme is applied
if (version == MATERIAL3) {
val material3Theme = WearOverlayableMaterial3Theme(LocalContext.current)
@@ -70,7 +67,7 @@ fun WearPermissionTheme(
// But some in-app screens(like permission grant screen) are migrated to material3.
// To avoid having two set of overlay resources, we will use material3 overlay resources to
// support material2_5 UI controls as well.
- else if (version == MATERIAL2_5 && useBridgedTheme) {
+ else if (version == MATERIAL2_5 && ResourceHelper.materialUIVersionInApp == MATERIAL3) {
val material3Theme = WearOverlayableMaterial3Theme(LocalContext.current)
val bridgedLegacyTheme = WearMaterialBridgedLegacyTheme.createFrom(material3Theme)
MaterialTheme(
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/PermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
index a3446f802..7f714e083 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
@@ -181,13 +181,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) {
@@ -343,7 +348,7 @@ object PermissionMapping {
val appSupportsPickerPrompt =
group.permissions[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED]?.isImplicit ==
- false
+ false
return if (appSupportsPickerPrompt) {
PARTIAL_MEDIA_PERMISSIONS
@@ -352,12 +357,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 3d3b47272..aae5cb82c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -547,8 +547,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);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING
index 93ad3d31b..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,6 +47,9 @@
"exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
+ },
+ {
+ "name": "CtsRoleMultiUserTestCases"
}
],
"postsubmit": [
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java
index 234554193..60139f0c8 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java
@@ -710,7 +710,7 @@ public class RequestRoleFragment extends DialogFragment {
checked = mCheckedUserPackage == null;
icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle);
title = context.getString(R.string.default_app_none);
- subtitle = mHolderUserPackage != null ? context.getString(
+ subtitle = mHolderUserPackage == null ? context.getString(
R.string.request_role_current_default) : null;
} else {
applicationInfo = qualifyingApplication.first;
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..c322b2bef 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt
@@ -29,10 +29,10 @@ 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.permission.ui.wear.elements.material2.Chip
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.chipDefaultColors
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.chipDisabledColors
import com.android.permissioncontroller.role.ui.RoleItem
@Composable
@@ -65,7 +65,7 @@ fun WearDefaultAppListScreen(
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..0c39ca8c4 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt
@@ -25,14 +25,16 @@ 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.permission.ui.wear.elements.material2.ListFooter
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.ToggleChip
+import com.android.permissioncontroller.permission.ui.wear.elements.material2.toggleChipDisabledColors
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionConfirmationDialog
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType
+import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
+import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
import com.android.permissioncontroller.role.ui.wear.model.ConfirmDialogArgs
@Composable
@@ -41,11 +43,13 @@ fun WearDefaultAppScreen(helper: WearDefaultAppHelper) {
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()) {
@@ -57,7 +61,7 @@ fun WearDefaultAppScreen(helper: WearDefaultAppHelper) {
private fun WearDefaultAppContent(
isLoading: Boolean,
qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>,
- helper: WearDefaultAppHelper
+ helper: WearDefaultAppHelper,
) {
ScrollableScreen(title = helper.getTitle(), isLoading = isLoading) {
helper.getNonePreference(qualifyingApplications)?.let {
@@ -67,8 +71,8 @@ private fun WearDefaultAppContent(
icon = it.icon,
checked = it.checked,
onCheckedChanged = it.onDefaultCheckChanged,
- toggleControl = ToggleChipToggleControl.Radio,
- labelMaxLine = Integer.MAX_VALUE
+ toggleControl = WearPermissionToggleControlType.Radio,
+ labelMaxLine = Integer.MAX_VALUE,
)
}
}
@@ -86,9 +90,9 @@ private fun WearDefaultAppContent(
secondaryLabel = pref.summary?.toString(),
checked = pref.checked,
onCheckedChanged = pref.getOnCheckChanged(),
- toggleControl = ToggleChipToggleControl.Radio,
+ toggleControl = WearPermissionToggleControlType.Radio,
labelMaxLine = Integer.MAX_VALUE,
- secondaryLabelMaxLine = Integer.MAX_VALUE
+ secondaryLabelMaxLine = Integer.MAX_VALUE,
)
}
}
@@ -98,14 +102,18 @@ private fun WearDefaultAppContent(
}
@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/WearRequestRoleScreen.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt
index fcc0d56f9..b4758c88b 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt
@@ -32,17 +32,15 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
-import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionListFooter
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl
import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlStyle
+import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType
import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper
import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5
-import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3
import com.android.permissioncontroller.role.UserPackage
import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData
@@ -80,14 +78,8 @@ fun WearRequestRoleScreen(
helper.initializeSelectedPackage()
}
}
- val materialUIVersion =
- if (ResourceHelper.material3Enabled) {
- MATERIAL3
- } else {
- MATERIAL2_5
- }
WearRequestRoleContent(
- materialUIVersion,
+ ResourceHelper.materialUIVersionInApp,
isLoading,
helper,
roleLiveData.value,
@@ -137,7 +129,7 @@ internal fun WearRequestRoleContent(
onCheckedChanged = { checked ->
onCheckedChanged(checked, pref.userPackage, pref.isHolder)
},
- toggleControl = ToggleChipToggleControl.Radio,
+ toggleControl = WearPermissionToggleControlType.Radio,
labelMaxLines = Integer.MAX_VALUE,
)
}
@@ -162,7 +154,7 @@ internal fun WearRequestRoleContent(
onCheckedChanged = { checked ->
onCheckedChanged(checked, pref.userPackage, pref.isHolder)
},
- toggleControl = ToggleChipToggleControl.Radio,
+ toggleControl = WearPermissionToggleControlType.Radio,
)
}
pref.subTitle?.let { subTitle ->
@@ -183,7 +175,7 @@ internal fun WearRequestRoleContent(
enabled = enabled,
onCheckedChanged = { checked -> run { onDontAskAgainCheckedChanged(checked) } },
label = stringResource(R.string.request_role_dont_ask_again),
- toggleControl = ToggleChipToggleControl.Checkbox,
+ toggleControl = WearPermissionToggleControlType.Checkbox,
style = WearPermissionToggleControlStyle.Transparent,
modifier =
Modifier.testTag("com.android.permissioncontroller:id/dont_ask_again"),
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
index abf159955..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;
@@ -42,9 +41,6 @@ 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}. */
@@ -54,25 +50,16 @@ public class SafetyStatusPreference extends Preference
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) {
@@ -93,9 +80,7 @@ public class SafetyStatusPreference extends Preference
updateStatusIcon(statusCardView);
- updateStatusText(statusCardView.getTitleView(), statusCardView.getSummaryView());
-
- mFirstBind = false;
+ statusCardView.showText(mStatus);
}
private void configureButtons(Context context, StatusCardView statusCardView) {
@@ -125,14 +110,6 @@ public class SafetyStatusPreference extends Preference
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();
@@ -143,33 +120,6 @@ public class SafetyStatusPreference extends Preference
/* 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/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 4cd9e0e6f..49e4e9474 100644
--- a/PermissionController/tests/inprocess/Android.bp
+++ b/PermissionController/tests/inprocess/Android.bp
@@ -53,6 +53,7 @@ android_test {
// 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/util/ArrayUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt
index 708d4222f..c7b9ad823 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt
@@ -16,11 +16,15 @@
package com.android.permissioncontroller.permission.util
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.android.permissioncontroller.permission.utils.ArrayUtils
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/util/CollectionUtilsTest.kt
index 3d4bd28ff..627d19474 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt
@@ -16,11 +16,15 @@
package com.android.permissioncontroller.permission.util
+import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.android.permissioncontroller.permission.utils.CollectionUtils
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/util/KotlinUtilsTest.kt
index 37aa8d988..34c351683 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt
@@ -30,11 +30,13 @@ 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 +50,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 +68,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/util/PermissionMappingTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt
index aa7d7da60..29b4e1c4e 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt
@@ -19,14 +19,31 @@ package com.android.permissioncontroller.permission.util
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.android.permissioncontroller.permission.utils.PermissionMapping
+import com.android.permissioncontroller.permission.utils.Utils;
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(
@@ -76,4 +93,81 @@ class PermissionMappingTest {
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/util/UtilsTest.kt
index 11bcca356..1cfe6a5d3 100644
--- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt
+++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt
@@ -16,6 +16,8 @@
package com.android.permissioncontroller.permission.util
+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,6 +40,14 @@ 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
@@ -46,12 +56,20 @@ 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 +114,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 +314,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/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/NotificationListenerCheckInternalTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/NotificationListenerCheckInternalTest.kt
index bc00d3bc8..4bb021b3d 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/NotificationListenerCheckInternalTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/NotificationListenerCheckInternalTest.kt
@@ -30,7 +30,6 @@ import android.safetycenter.SafetyCenterManager
import android.safetycenter.SafetyEvent
import android.safetycenter.SafetySourceData
import android.safetycenter.SafetySourceIssue
-import androidx.core.util.Preconditions
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
@@ -464,9 +463,7 @@ class NotificationListenerCheckInternalTest {
}
val safetySourceIssue =
- Preconditions.checkNotNull(
- notificationListenerCheck.createSafetySourceIssue(testComponent, 0)
- )
+ checkNotNull(notificationListenerCheck.createSafetySourceIssue(testComponent, 0))
val expectedId = "notification_listener_${testComponent.flattenToString()}"
val expectedTitle =
diff --git a/PermissionController/tests/permissionui/Android.bp b/PermissionController/tests/permissionui/Android.bp
index 5f177f40c..e0e8fed10 100644
--- a/PermissionController/tests/permissionui/Android.bp
+++ b/PermissionController/tests/permissionui/Android.bp
@@ -47,6 +47,7 @@ android_test {
"androidx.test.ext.truth",
"androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
+ "android.permission.flags-aconfig-java-export",
"com.android.permission.flags-aconfig-java-export",
"compatibility-device-util-axt",
"flag-junit",
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 b38f5f40a..08143f77f 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,11 +17,18 @@
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
@@ -32,13 +39,18 @@ 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 =
@@ -95,14 +107,38 @@ 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 bodySensorsEitherDisplayedInMainPageOrInAdditional() {
+ 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() {
+ 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/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml b/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml
index de033ac44..cb6323fff 100644
--- a/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml
+++ b/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml
@@ -30,6 +30,36 @@
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"
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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-af-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-am-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ar-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-as-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-az-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-b+sr+Latn-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-be-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-bg-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-bn-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-bs-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ca-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-cs-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-da-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</resources>
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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-de-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-el-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-en-rAU-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-en-rCA-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-en-rGB-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-en-rIN-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-es-rUS-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-es-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-et-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-eu-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-fa-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-fi-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-fr-rCA-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-fr-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-gl-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-gu-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-hi-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-hr-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-hu-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-hy-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-in-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-is-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-it-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-iw-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</resources>
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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ja-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ka-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-kk-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-km-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-kn-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ko-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ky-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-lo-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-lt-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-lv-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</resources>
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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-mk-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</resources>
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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ml-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-mn-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-mr-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ms-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-my-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-nb-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ne-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-nl-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-or-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-pa-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-pl-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-pt-rBR-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-pt-rPT-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-pt-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ro-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ru-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-si-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sk-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</resources>
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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sl-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sq-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sr-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sv-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-sw-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ta-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-te-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-th-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-tl-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-tr-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-uk-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-ur-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-uz-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-vi-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-zh-rCN-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-zh-rHK-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-zh-rTW-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</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..07f3c054c
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-zu-v36/strings.xml
@@ -0,0 +1,41 @@
+<?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">
+ <!-- no translation found for face_unlock_title (3991635517593572926) -->
+ <skip />
+ <!-- no translation found for face_unlock_title_for_work (1451170625947022012) -->
+ <skip />
+ <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string>
+ <!-- no translation found for face_unlock_search_terms (2708195853333028283) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title (5579868242026550596) -->
+ <skip />
+ <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) -->
+ <skip />
+ <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string>
+ <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title (1613730442896319515) -->
+ <skip />
+ <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) -->
+ <skip />
+ <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string>
+ <!-- no translation found for wear_unlock_search_terms (3769797118448924263) -->
+ <skip />
+</resources>
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/flags.aconfig b/flags/flags.aconfig
index 4cb084988..45afa5ff4 100644
--- a/flags/flags.aconfig
+++ b/flags/flags.aconfig
@@ -152,4 +152,13 @@ flag {
description: "This flag is used to enable Expressive Design for Settings pages inside PermissionController"
bug: "375480184"
is_fixed_read_only: true
-} \ No newline at end of file
+}
+
+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 212996b24..c5d971435 100644
--- a/framework-s/api/system-current.txt
+++ b/framework-s/api/system-current.txt
@@ -64,7 +64,7 @@ package android.app.role {
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 public static final String ROLE_SYSTEM_DEPENDENCY_INSTALLER = "android.app.role.SYSTEM_DEPENDENCY_INSTALLER";
+ 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 db05a0af6..4248a429c 100644
--- a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java
+++ b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java
@@ -33,6 +33,7 @@ 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;
@@ -202,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,
@@ -349,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/RoleManager.java b/framework-s/java/android/app/role/RoleManager.java
index 70bcfbf36..9f28b7f19 100644
--- a/framework-s/java/android/app/role/RoleManager.java
+++ b/framework-s/java/android/app/role/RoleManager.java
@@ -272,7 +272,7 @@ public final class RoleManager {
*
* @hide
*/
- @SuppressLint("UnflaggedApi")
+ @FlaggedApi("android.content.pm.sdk_dependency_installer")
@SystemApi
public static final String ROLE_SYSTEM_DEPENDENCY_INSTALLER =
"android.app.role.SYSTEM_DEPENDENCY_INSTALLER";
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/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java
index 65fde6daf..46b5eedbc 100644
--- a/service/java/com/android/ecm/EnhancedConfirmationService.java
+++ b/service/java/com/android/ecm/EnhancedConfirmationService.java
@@ -16,6 +16,9 @@
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.annotation.FlaggedApi;
import android.annotation.IntDef;
@@ -89,7 +92,7 @@ public class EnhancedConfirmationService extends SystemService {
private static final int CALL_TYPE_UNTRUSTED = 0;
private static final int CALL_TYPE_TRUSTED = 1;
- private static final int CALL_TYPE_EMERGENCY = 2;
+ private static final int CALL_TYPE_EMERGENCY = 1 << 1;
@IntDef(flag = true, value = {
CALL_TYPE_UNTRUSTED,
CALL_TYPE_TRUSTED,
@@ -237,7 +240,7 @@ 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
@@ -245,30 +248,31 @@ public class EnhancedConfirmationService extends SystemService {
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
- PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES);
UNTRUSTED_CALL_RESTRICTED_SETTINGS.add(
AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES);
+ UNTRUSTED_CALL_RESTRICTED_SETTINGS.add(
+ AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
}
}
@@ -287,10 +291,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");
@@ -299,12 +309,16 @@ public class EnhancedConfirmationService extends SystemService {
try {
if (!isSettingEcmProtected(settingIdentifier)) {
- return false;
+ return null;
+ }
+ if (isSettingEcmGuardedForPackage(settingIdentifier, packageName, userId)) {
+ return REASON_PACKAGE_RESTRICTED;
}
- if (isSettingProtectedGlobally(settingIdentifier)) {
- return true;
+ String globalProtectionReason = getGlobalProtectionReason(settingIdentifier);
+ if (globalProtectionReason != null) {
+ return globalProtectionReason;
}
- return isPackageEcmGuarded(packageName, userId);
+ return null;
} catch (NameNotFoundException e) {
throw new IllegalArgumentException(e);
}
@@ -436,6 +450,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));
@@ -506,19 +528,23 @@ 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 boolean isSettingProtectedGlobally(@NonNull String settingIdentifier) {
- if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) {
- return isUntrustedCallOngoing();
+ private String getGlobalProtectionReason(@NonNull String settingIdentifier) {
+ if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)
+ && isUntrustedCallOngoing()) {
+ return REASON_PHONE_STATE;
}
- return false;
+ return null;
}
@Nullable
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/tests/apex/AndroidTest.xml b/tests/apex/AndroidTest.xml
index b1af0f53e..c3807eb9d 100644
--- a/tests/apex/AndroidTest.xml
+++ b/tests/apex/AndroidTest.xml
@@ -20,6 +20,7 @@
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
<option name="test-tag" value="PermissionApexTests" />
+ <option name="hidden-api-checks" value="false" />
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
<!-- Install test -->
diff --git a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
index 687234582..44eef2144 100644
--- a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
+++ b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt
@@ -93,7 +93,7 @@ class DeviceAwarePermissionGrantTest {
val displayConfigBuilder =
VirtualDeviceRule.createDefaultVirtualDisplayConfigBuilder(
DISPLAY_WIDTH,
- DISPLAY_HEIGHT
+ DISPLAY_HEIGHT,
)
.setFlags(
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC or
@@ -114,7 +114,7 @@ class DeviceAwarePermissionGrantTest {
@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() {
@@ -124,13 +124,13 @@ class DeviceAwarePermissionGrantTest {
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() {
@@ -140,13 +140,13 @@ class DeviceAwarePermissionGrantTest {
true,
deviceDisplayName,
expectPermissionGrantedOnDefaultDevice = false,
- expectPermissionGrantedOnRemoteDevice = 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
@@ -160,8 +160,9 @@ class DeviceAwarePermissionGrantTest {
@RequiresFlagsEnabled(
Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED,
Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED,
- Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES
+ Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES,
)
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
@Test
fun onRemoteDevice_requestPermissionForHostDevice_shouldGrantPermission() {
// Create a virtual device with default policy, so that camera permission request will
@@ -176,16 +177,18 @@ class DeviceAwarePermissionGrantTest {
virtualDisplay.display.displayId,
virtualDevice.deviceId,
true,
- Settings.Global.getString(defaultDeviceContext.contentResolver,
- Settings.Global.DEVICE_NAME),
+ Settings.Global.getString(
+ defaultDeviceContext.contentResolver,
+ Settings.Global.DEVICE_NAME,
+ ),
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 onRemoteDevice_requestPermissionForRemoteDevice_shouldGrantPermission() {
@@ -195,7 +198,7 @@ class DeviceAwarePermissionGrantTest {
true,
deviceDisplayName,
expectPermissionGrantedOnDefaultDevice = false,
- expectPermissionGrantedOnRemoteDevice = true
+ expectPermissionGrantedOnRemoteDevice = true,
)
}
@@ -205,7 +208,7 @@ 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)
@@ -240,13 +243,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) }
diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
index bceec18ae..a2f85c6a1 100644
--- a/tests/cts/permissionpolicy/res/raw/android_manifest.xml
+++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
@@ -8632,22 +8632,6 @@
android:protectionLevel="signature|role"
android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/>
- <!--
- This permission allows the system to receive PACKAGE_CHANGED broadcasts when the component
- state of a non-exported component has been changed.
- <p>Not for use by third-party applications. </p>
- <p>Protection level: internal
- @hide
- -->
- <permission
- android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
- android:protectionLevel="internal"
- android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
-
- <uses-permission
- android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
- android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
-
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
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/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..b2da92d22 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt
@@ -121,7 +121,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.
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
index 92599b617..bd22cb5bc 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt
@@ -148,6 +148,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 +318,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
reinstall: Boolean,
grantRuntimePermissions: Boolean,
expectSuccess: Boolean,
- installSource: String?
+ installSource: String?,
) {
installPackage(
apkPath,
@@ -333,7 +336,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 +466,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
- apkName: String
+ apkName: String,
) {
installPackageViaSession(
apkName,
@@ -506,7 +509,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceAndMetadataWithoutTopLevelVersion(
- apkName: String
+ apkName: String,
) {
installPackageViaSession(
apkName,
@@ -515,7 +518,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceAndMetadataWithInvalidTopLevelVersion(
- apkName: String
+ apkName: String,
) {
installPackageViaSession(
apkName,
@@ -524,7 +527,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceAndMetadataWithoutSafetyLabelVersion(
- apkName: String
+ apkName: String,
) {
installPackageViaSession(
apkName,
@@ -533,7 +536,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
}
protected fun installPackageWithInstallSourceAndMetadataWithInvalidSafetyLabelVersion(
- apkName: String
+ apkName: String,
) {
installPackageViaSession(
apkName,
@@ -552,7 +555,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 +580,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected fun assertPermissionRationaleDialogIsVisible(
expected: Boolean,
- showSettingsSection: Boolean = true
+ showSettingsSection: Boolean = true,
) {
assertPermissionRationaleActivityTitleIsVisible(expected)
assertPermissionRationaleActivityDataSharingSourceSectionVisible(expected)
@@ -623,7 +626,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected inline fun startAppActivityAndAssertResultCode(
expectedResultCode: Int,
- block: () -> Unit
+ block: () -> Unit,
) {
val future =
startActivityForFuture(
@@ -641,7 +644,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected inline fun requestAppPermissionsForNoResult(
vararg permissions: String?,
- crossinline block: () -> Unit
+ crossinline block: () -> Unit,
) {
// Request the permissions
doAndWaitForWindowTransition {
@@ -665,7 +668,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 +703,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 +769,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(),
@@ -801,8 +804,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 +979,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()
@@ -1032,7 +1069,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected fun revokeAppPermissionsByUi(
vararg permissions: String,
- isLegacyApp: Boolean = false
+ isLegacyApp: Boolean = false,
) {
setAppPermissionState(
*permissions,
@@ -1095,7 +1132,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() {
protected fun navigateToIndividualPermissionSetting(
permission: String,
- manuallyNavigate: Boolean = false
+ manuallyNavigate: Boolean = false,
) {
val useLegacyNavigation = isWatch || isAutomotive || manuallyNavigate
if (useLegacyNavigation) {
@@ -1368,7 +1405,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
}
@@ -1379,7 +1417,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
}
}
@@ -1387,7 +1426,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
}
@@ -1420,7 +1460,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/EnhancedConfirmationInCallTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt
index 16a27c9a8..9a4908c79 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt
@@ -24,6 +24,7 @@ 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
@@ -56,8 +57,11 @@ import org.junit.Test
// @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
@@ -149,20 +153,32 @@ class EnhancedConfirmationInCallTest {
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(): Boolean {
+ private fun isSettingRestricted(settingsIdentifier: String): Boolean {
return callWithShellPermissionIdentity {
- ecm.isRestricted(context.packageName, AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES)
+ 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(isSettingRestricted())
+ Assert.assertTrue(areSettingsRestricted())
voipService.endCallAndWaitForInactive()
- Assert.assertFalse(isSettingRestricted())
+ Assert.assertFalse(areSettingsRestricted())
}
@Test
@@ -170,9 +186,9 @@ class EnhancedConfirmationInCallTest {
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(isSettingRestricted())
+ Assert.assertFalse(areSettingsRestricted())
voipService.endCallAndWaitForInactive()
- Assert.assertFalse(isSettingRestricted())
+ Assert.assertFalse(areSettingsRestricted())
}
@Test
@@ -180,9 +196,9 @@ class EnhancedConfirmationInCallTest {
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(isSettingRestricted())
+ Assert.assertFalse(areSettingsRestricted())
voipService.endCallAndWaitForInactive()
- Assert.assertFalse(isSettingRestricted())
+ Assert.assertFalse(areSettingsRestricted())
}
@Test
@@ -192,10 +208,26 @@ class EnhancedConfirmationInCallTest {
voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone)
addContact(tempContactDisplay, tempContactPhone)
// State should not be recomputed just because the contact is newly added
- Assert.assertTrue(isSettingRestricted())
+ Assert.assertTrue(areSettingsRestricted())
voipService.endCallAndWaitForInactive()
voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone)
// A new call should recognize our contact, and mark the call as trusted
- Assert.assertFalse(isSettingRestricted())
+ 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 8e91a00ce..9ec09dab7 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt
@@ -78,20 +78,14 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
@Test
fun installedAppStartsWithModeDefault() {
installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST)
- eventually {
- runWithShellPermissionIdentity {
- assertEquals(
- getAppEcmState(context, appOpsManager, APP_PACKAGE_NAME),
- AppOpsManager.MODE_DEFAULT
- )
- }
- }
+ waitForModeDefault()
}
@RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
@Test
fun givenStoreAppThenIsNotRestrictedFromProtectedSetting() {
installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST)
+ waitForModeDefault()
runWithShellPermissionIdentity {
eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
}
@@ -101,6 +95,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
@Test
fun givenLocalAppThenIsRestrictedFromProtectedSetting() {
installPackageWithInstallSourceAndMetadataFromLocalFile(APP_APK_NAME_LATEST)
+ waitForModeDefault()
runWithShellPermissionIdentity {
eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
}
@@ -110,6 +105,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
@Test
fun givenDownloadedThenAppIsRestrictedFromProtectedSetting() {
installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST)
+ waitForModeDefault()
runWithShellPermissionIdentity {
eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
}
@@ -119,6 +115,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
@Test
fun givenExplicitlyRestrictedAppThenIsRestrictedFromProtectedSetting() {
installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST)
+ waitForModeDefault()
eventually {
runWithShellPermissionIdentity {
assertEquals(
@@ -138,6 +135,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
@Test
fun givenRestrictedAppThenIsNotRestrictedFromNonProtectedSetting() {
installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST)
+ waitForModeDefault()
runWithShellPermissionIdentity {
eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, NON_PROTECTED_SETTING)) }
}
@@ -147,6 +145,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
@Test
fun givenRestrictedAppThenClearRestrictionNotAllowedByDefault() {
installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST)
+ waitForModeDefault()
runWithShellPermissionIdentity {
eventually { assertFalse(ecm.isClearRestrictionAllowed(APP_PACKAGE_NAME)) }
}
@@ -156,6 +155,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
@Test
fun givenRestrictedAppWhenClearRestrictionThenNotRestrictedFromProtectedSetting() {
installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST)
+ waitForModeDefault()
runWithShellPermissionIdentity {
eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) }
ecm.setClearRestrictionAllowed(APP_PACKAGE_NAME)
@@ -169,6 +169,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
@Test
fun createRestrictedSettingDialogIntentReturnsIntent() {
installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST)
+ waitForModeDefault()
val intent = ecm.createRestrictedSettingDialogIntent(APP_PACKAGE_NAME, PROTECTED_SETTING)
@@ -181,6 +182,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
APP_APK_NAME_LATEST
)
+ waitForModeDefault()
val permissionAndExpectedGrantResults =
arrayOf(
GROUP_2_PERMISSION_1_RESTRICTED to false,
@@ -207,6 +209,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
APP_APK_NAME_LATEST
)
+ waitForModeDefault()
requestAppPermissionsAndAssertResult(
GROUP_3_PERMISSION_1_UNRESTRICTED to false,
@@ -236,6 +239,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
APP_APK_NAME_LATEST
)
+ waitForModeDefault()
requestAppPermissionsAndAssertResult(
GROUP_3_PERMISSION_1_UNRESTRICTED to true,
@@ -254,6 +258,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms(
APP_APK_NAME_LATEST
)
+ waitForModeDefault()
requestAppPermissionsAndAssertResult(
GROUP_4_PERMISSION_1_UNRESTRICTED to true,
@@ -287,6 +292,18 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() {
)
}
+ private fun waitForModeDefault() {
+ eventually {
+ runWithShellPermissionIdentity {
+ assertEquals(
+ "Timed out waiting for package mode to change to MODE_DEFAULT",
+ getAppEcmState(context, appOpsManager, APP_PACKAGE_NAME),
+ AppOpsManager.MODE_DEFAULT
+ )
+ }
+ }
+ }
+
companion object {
private const val GROUP_2_PERMISSION_1_RESTRICTED = Manifest.permission.SEND_SMS
private const val GROUP_2_PERMISSION_2_RESTRICTED = Manifest.permission.READ_SMS
@@ -294,7 +311,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/PermissionSplitTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
index e71ac32a5..c2b5447dd 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt
@@ -16,7 +16,11 @@
package android.permissionui.cts
+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
@@ -60,34 +64,112 @@ class PermissionSplitTest : BaseUsePermissionTest() {
testLocationPermissionSplit(false)
}
+ // 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.VANILLA_ICE_CREAM,
+ maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
)
@Test
- fun testBodySensorSplitOnTToV() {
+ fun testBodySensorSplitOnTToU() {
installPackage(APP_APK_PATH_31)
- 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 testBodySensorSplitPostV_replaceBodySensorFlagDisabled() {
+ installPackage(APP_APK_PATH_31)
+ 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.VANILLA_ICE_CREAM,
+ maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
)
@Test
- fun testBodySensorSplit32OnTToV() {
+ fun testBodySensorSplit32OnTToU() {
+ installPackage(APP_APK_PATH_32)
+ 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)
- testBodySensorPermissionSplit(true)
+ 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.VANILLA_ICE_CREAM,
+ maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
)
@Test
- fun testBodySensorNonSplitonTToV() {
+ fun testBodySensorNonSplitOnTToU() {
installPackage(APP_APK_PATH_LATEST)
- testBodySensorPermissionSplit(false)
+ testBodySensorPermissionSplitToBodySensorsBackground(false)
+ }
+
+ // 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 testBodySensorNonSplitPostV_replaceBodySensorFlagDisabled() {
+ installPackage(APP_APK_PATH_LATEST)
+ testBodySensorPermissionSplitToBodySensorsBackground(false)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
+ @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
+ @Test
+ fun testBodySensorSplitOnBaklava_splitToReadHeartRate() {
+ 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) {
@@ -112,7 +194,7 @@ class PermissionSplitTest : BaseUsePermissionTest() {
assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, expectSplit)
}
- private fun testBodySensorPermissionSplit(expectSplit: Boolean) {
+ private fun testBodySensorPermissionSplitToBodySensorsBackground(expectSplit: Boolean) {
assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, false)
assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, false)
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/role/Android.bp b/tests/cts/role/Android.bp
index 9f1e6cff6..ea9af5d8e 100644
--- a/tests/cts/role/Android.bp
+++ b/tests/cts/role/Android.bp
@@ -37,7 +37,9 @@ android_test {
"bedstead-multiuser",
"flag-junit",
"platform-test-annotations",
+ "platform-test-rules",
"truth",
+ "uiautomator-helpers",
],
test_suites: [
@@ -48,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 f26bc0eb5..bf1e32676 100644
--- a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
@@ -18,6 +18,7 @@ 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;
@@ -26,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;
@@ -286,6 +288,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));
@@ -713,6 +719,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));
@@ -923,6 +933,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 {
@@ -1402,16 +1425,6 @@ public class RoleManagerTest {
});
}
- @RequiresFlagsDisabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
- @Test
- public void cannotGetDefaultHoldersForTestFlagDisabled() throws Exception {
- runWithShellPermissionIdentity(() -> {
- assertThrows(IllegalStateException.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
@@ -1441,18 +1454,6 @@ public class RoleManagerTest {
});
}
- @RequiresFlagsDisabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
- @Test
- public void cannotSetDefaultHoldersForTestFlagDisabled() throws Exception {
- List<String> testRoleHolders = List.of("a", "b", "c");
- runWithShellPermissionIdentity(() -> {
- assertThrows(IllegalStateException.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
@@ -1528,16 +1529,6 @@ public class RoleManagerTest {
});
}
- @RequiresFlagsDisabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
- @Test
- public void cannotGetIsRoleVisibleForTestFlagDisabled() throws Exception {
- runWithShellPermissionIdentity(() -> {
- assertThrows(IllegalStateException.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
@@ -1566,16 +1557,6 @@ public class RoleManagerTest {
});
}
- @RequiresFlagsDisabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED)
- @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava")
- @Test
- public void cannotSetRoleVisibleForTestFlagDisabled() throws Exception {
- runWithShellPermissionIdentity(() -> {
- assertThrows(IllegalStateException.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
diff --git a/tests/cts/rolemultiuser/Android.bp b/tests/cts/rolemultiuser/Android.bp
index 7a49bc4e5..51eff83b9 100644
--- a/tests/cts/rolemultiuser/Android.bp
+++ b/tests/cts/rolemultiuser/Android.bp
@@ -24,6 +24,7 @@ android_test {
srcs: [
"src/**/*.kt",
+ ":CtsRoleTestUtils",
],
static_libs: [
diff --git a/tests/cts/rolemultiuser/TEST_MAPPING b/tests/cts/rolemultiuser/TEST_MAPPING
index 323e3094c..b45469e14 100644
--- a/tests/cts/rolemultiuser/TEST_MAPPING
+++ b/tests/cts/rolemultiuser/TEST_MAPPING
@@ -1,12 +1,17 @@
{
- "postsubmit": [
+ "presubmit": [
{
"name": "CtsRoleMultiUserTestCases"
}
],
- "mainline-postsubmit": [
+ "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
index ee00c2c39..e8aaddf4c 100644
--- a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
+++ b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt
@@ -16,6 +16,7 @@
package android.app.rolemultiuser.cts
import android.app.Activity
+import android.app.role.cts.RoleManagerUtil
import android.app.role.RoleManager
import android.content.ComponentName
import android.content.Context
@@ -70,6 +71,7 @@ import java.util.function.Consumer
import org.junit.After
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
@@ -88,6 +90,7 @@ class RoleManagerMultiUserTest {
@Before
@Throws(java.lang.Exception::class)
fun setUp() {
+ assumeTrue(RoleManagerUtil.isCddCompliantScreenSize());
installAppForAllUsers()
}
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 ee0326bd3..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
@@ -481,7 +482,10 @@ class SafetyCenterActivityTest {
context.launchSafetyCenterActivity {
if (SafetyCenterFlags.showSubpages) {
- waitDisplayed(By.text("OK")) { it.click() } // Open subpage
+ clickOpenSubpage(
+ context,
+ safetyCenterTestConfigs.singleSourceConfig.safetySourcesGroups.first(),
+ )
}
waitDisplayed(By.text("OK")) { it.click() }
waitButtonDisplayed("Exit test activity") { it.click() }
@@ -495,7 +499,11 @@ class SafetyCenterActivityTest {
context.launchSafetyCenterActivity {
if (SafetyCenterFlags.showSubpages) {
- waitDisplayed(By.text("OK")) { it.click() } // Open subpage
+ clickOpenSubpage(
+ context,
+ safetyCenterTestConfigs.implicitIntentSingleSourceConfig.safetySourcesGroups
+ .first(),
+ )
}
waitDisplayed(By.text("OK")) { it.click() }
waitButtonDisplayed("Exit test activity") { it.click() }
@@ -509,7 +517,10 @@ class SafetyCenterActivityTest {
context.launchSafetyCenterActivity {
if (SafetyCenterFlags.showSubpages) {
- waitDisplayed(By.text("OK")) { it.click() } // Open subpage
+ clickOpenSubpage(
+ context,
+ safetyCenterTestConfigs.singleSourceConfig.safetySourcesGroups.first(),
+ )
}
waitDisplayed(By.text("Ok title")) { it.click() }
waitButtonDisplayed("Exit test activity") { it.click() }
@@ -527,7 +538,10 @@ class SafetyCenterActivityTest {
context.launchSafetyCenterActivity {
if (SafetyCenterFlags.showSubpages) {
- waitDisplayed(By.text("OK")) { it.click() } // Open subpage
+ clickOpenSubpage(
+ context,
+ safetyCenterTestConfigs.singleSourceConfig.safetySourcesGroups.first(),
+ )
}
waitDisplayed(By.desc("Information")) { it.click() }
waitButtonDisplayed("Exit test activity") { it.click() }
@@ -1482,7 +1496,11 @@ class SafetyCenterActivityTest {
context.launchSafetyCenterActivity {
if (SafetyCenterFlags.showSubpages) {
- waitDisplayed(By.text("OK")) { it.click() } // Open subpage
+ clickOpenSubpage(
+ context,
+ safetyCenterTestConfigs.implicitIntentSingleSourceConfig.safetySourcesGroups
+ .first(),
+ )
}
waitDisplayed(By.text("OK")) { it.click() }
waitDisplayed(By.text("is_from_settings_homepage false"))
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/UiTestHelper.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
index c7d195528..3dfefeecf 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
@@ -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>