summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--PREUPLOAD.cfg4
-rw-r--r--PermissionController/Android.bp16
-rw-r--r--PermissionController/AndroidManifest.xml44
-rw-r--r--PermissionController/TEST_MAPPING68
-rw-r--r--PermissionController/iconloaderlib/build.gradle2
-rw-r--r--PermissionController/proguard.flags9
-rw-r--r--PermissionController/res/drawable-v34/ic_business.xml10
-rw-r--r--PermissionController/res/drawable-v34/ic_collections_bookmark.xml10
-rw-r--r--PermissionController/res/drawable-v34/ic_gear.xml13
-rw-r--r--PermissionController/res/drawable-v34/ic_info.xml10
-rw-r--r--PermissionController/res/drawable/coarse_off_dark.gifbin170063 -> 0 bytes
-rw-r--r--PermissionController/res/drawable/coarse_off_light.gifbin127528 -> 0 bytes
-rw-r--r--PermissionController/res/drawable/coarse_on_dark.gifbin196366 -> 0 bytes
-rw-r--r--PermissionController/res/drawable/coarse_on_light.gifbin149193 -> 0 bytes
-rw-r--r--PermissionController/res/drawable/fine_off_dark.gifbin178865 -> 0 bytes
-rw-r--r--PermissionController/res/drawable/fine_off_light.gifbin110934 -> 0 bytes
-rw-r--r--PermissionController/res/drawable/fine_on_dark.gifbin160541 -> 0 bytes
-rw-r--r--PermissionController/res/drawable/fine_on_light.gifbin117548 -> 0 bytes
-rw-r--r--PermissionController/res/drawable/grant_dialog_permission_rationale_background.xml22
-rw-r--r--PermissionController/res/drawable/ic_more_info_arrow.xml23
-rw-r--r--PermissionController/res/drawable/ic_shield_exclamation_outline.xml23
-rw-r--r--PermissionController/res/layout-television/preference_permissions_category.xml4
-rw-r--r--PermissionController/res/layout-television/preference_permissions_no_apps.xml2
-rw-r--r--PermissionController/res/layout-v33/preference_issue_card.xml6
-rw-r--r--PermissionController/res/layout-v34/permission_rationale.xml147
-rw-r--r--PermissionController/res/layout/app_permission.xml43
-rw-r--r--PermissionController/res/layout/footer_with_link_preference.xml56
-rw-r--r--PermissionController/res/layout/grant_permissions.xml44
-rw-r--r--PermissionController/res/layout/grant_permissions_material3.xml50
-rw-r--r--PermissionController/res/navigation/nav_graph.xml5
-rw-r--r--PermissionController/res/raw-night/coarse_loc_off.json1
-rw-r--r--PermissionController/res/raw-night/coarse_loc_on.json1
-rw-r--r--PermissionController/res/raw-night/fine_loc_off.json1
-rw-r--r--PermissionController/res/raw-night/fine_loc_on.json1
-rw-r--r--PermissionController/res/raw/coarse_loc_off.json1
-rw-r--r--PermissionController/res/raw/coarse_loc_on.json1
-rw-r--r--PermissionController/res/raw/fine_loc_off.json1
-rw-r--r--PermissionController/res/raw/fine_loc_on.json1
-rw-r--r--PermissionController/res/values-af/strings.xml44
-rw-r--r--PermissionController/res/values-am/strings.xml44
-rw-r--r--PermissionController/res/values-ar/strings.xml44
-rw-r--r--PermissionController/res/values-as/strings.xml44
-rw-r--r--PermissionController/res/values-az/strings.xml44
-rw-r--r--PermissionController/res/values-b+sr+Latn/strings.xml44
-rw-r--r--PermissionController/res/values-be/strings.xml44
-rw-r--r--PermissionController/res/values-bg/strings.xml44
-rw-r--r--PermissionController/res/values-bn/strings.xml44
-rw-r--r--PermissionController/res/values-bs/strings.xml44
-rw-r--r--PermissionController/res/values-ca/strings.xml44
-rw-r--r--PermissionController/res/values-cs/strings.xml48
-rw-r--r--PermissionController/res/values-da/strings.xml44
-rw-r--r--PermissionController/res/values-de/strings.xml44
-rw-r--r--PermissionController/res/values-el/strings.xml44
-rw-r--r--PermissionController/res/values-en-rAU/strings.xml44
-rw-r--r--PermissionController/res/values-en-rCA/strings.xml40
-rw-r--r--PermissionController/res/values-en-rGB/strings.xml44
-rw-r--r--PermissionController/res/values-en-rIN/strings.xml44
-rw-r--r--PermissionController/res/values-en-rXC/strings.xml40
-rw-r--r--PermissionController/res/values-es-rUS/strings.xml44
-rw-r--r--PermissionController/res/values-es/strings.xml44
-rw-r--r--PermissionController/res/values-et/strings.xml44
-rw-r--r--PermissionController/res/values-eu/strings.xml44
-rw-r--r--PermissionController/res/values-fa/strings.xml44
-rw-r--r--PermissionController/res/values-fi/strings.xml46
-rw-r--r--PermissionController/res/values-fr-rCA/strings.xml44
-rw-r--r--PermissionController/res/values-fr/strings.xml44
-rw-r--r--PermissionController/res/values-gl/strings.xml44
-rw-r--r--PermissionController/res/values-gu/strings.xml44
-rw-r--r--PermissionController/res/values-hi/strings.xml44
-rw-r--r--PermissionController/res/values-hr/strings.xml44
-rw-r--r--PermissionController/res/values-hu/strings.xml44
-rw-r--r--PermissionController/res/values-hy/strings.xml44
-rw-r--r--PermissionController/res/values-in/strings.xml44
-rw-r--r--PermissionController/res/values-is/strings.xml44
-rw-r--r--PermissionController/res/values-it/strings.xml44
-rw-r--r--PermissionController/res/values-iw/strings.xml44
-rw-r--r--PermissionController/res/values-ja/strings.xml44
-rw-r--r--PermissionController/res/values-ka/strings.xml44
-rw-r--r--PermissionController/res/values-kk/strings.xml44
-rw-r--r--PermissionController/res/values-km/strings.xml44
-rw-r--r--PermissionController/res/values-kn/strings.xml44
-rw-r--r--PermissionController/res/values-ko/strings.xml44
-rw-r--r--PermissionController/res/values-ky/strings.xml46
-rw-r--r--PermissionController/res/values-lo/strings.xml44
-rw-r--r--PermissionController/res/values-lt/strings.xml44
-rw-r--r--PermissionController/res/values-lv/strings.xml44
-rw-r--r--PermissionController/res/values-mk/strings.xml44
-rw-r--r--PermissionController/res/values-ml/strings.xml44
-rw-r--r--PermissionController/res/values-mn/strings.xml44
-rw-r--r--PermissionController/res/values-mr/strings.xml44
-rw-r--r--PermissionController/res/values-ms/strings.xml44
-rw-r--r--PermissionController/res/values-my/strings.xml44
-rw-r--r--PermissionController/res/values-nb/strings.xml44
-rw-r--r--PermissionController/res/values-ne/strings.xml46
-rw-r--r--PermissionController/res/values-night/themes.xml2
-rw-r--r--PermissionController/res/values-nl/strings.xml44
-rw-r--r--PermissionController/res/values-or/strings.xml44
-rw-r--r--PermissionController/res/values-pa/strings.xml44
-rw-r--r--PermissionController/res/values-pl/strings.xml44
-rw-r--r--PermissionController/res/values-pt-rBR/strings.xml44
-rw-r--r--PermissionController/res/values-pt-rPT/strings.xml44
-rw-r--r--PermissionController/res/values-pt/strings.xml44
-rw-r--r--PermissionController/res/values-ro/strings.xml44
-rw-r--r--PermissionController/res/values-ru/strings.xml44
-rw-r--r--PermissionController/res/values-si/strings.xml44
-rw-r--r--PermissionController/res/values-sk/strings.xml44
-rw-r--r--PermissionController/res/values-sl/strings.xml44
-rw-r--r--PermissionController/res/values-sq/strings.xml44
-rw-r--r--PermissionController/res/values-sr/strings.xml44
-rw-r--r--PermissionController/res/values-sv/strings.xml46
-rw-r--r--PermissionController/res/values-sw/strings.xml44
-rw-r--r--PermissionController/res/values-ta/strings.xml44
-rw-r--r--PermissionController/res/values-te/strings.xml44
-rw-r--r--PermissionController/res/values-th/strings.xml44
-rw-r--r--PermissionController/res/values-tl/strings.xml44
-rw-r--r--PermissionController/res/values-tr/strings.xml44
-rw-r--r--PermissionController/res/values-uk/strings.xml44
-rw-r--r--PermissionController/res/values-ur/strings.xml44
-rw-r--r--PermissionController/res/values-uz/strings.xml44
-rw-r--r--PermissionController/res/values-v31/styles.xml8
-rw-r--r--PermissionController/res/values-v33/styles.xml162
-rw-r--r--PermissionController/res/values-v34/bools.xml21
-rw-r--r--PermissionController/res/values-v34/strings.xml191
-rw-r--r--PermissionController/res/values-vi/strings.xml46
-rw-r--r--PermissionController/res/values-w764dp-v33/styles.xml40
-rw-r--r--PermissionController/res/values-zh-rCN/strings.xml44
-rw-r--r--PermissionController/res/values-zh-rHK/strings.xml44
-rw-r--r--PermissionController/res/values-zh-rTW/strings.xml44
-rw-r--r--PermissionController/res/values-zu/strings.xml44
-rw-r--r--PermissionController/res/values/bools.xml1
-rw-r--r--PermissionController/res/values/overlayable.xml33
-rw-r--r--PermissionController/res/values/strings.xml111
-rw-r--r--PermissionController/res/values/styles.xml252
-rw-r--r--PermissionController/res/values/themes.xml10
-rw-r--r--PermissionController/res/xml-v34/app_data_sharing_updates.xml26
-rw-r--r--PermissionController/res/xml-v34/safety_center_subpage.xml30
-rw-r--r--PermissionController/res/xml/roles.xml131
-rw-r--r--PermissionController/res/xml/settings_button_with_divider_preference_widget.xml44
-rw-r--r--PermissionController/role-controller/Android.bp37
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java)35
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/AutomotiveRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/AutomotiveRoleBehavior.java)5
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/BrowserRoleBehavior.java)24
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceAppStreamingRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/CompanionDeviceAppStreamingRoleBehavior.java)6
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceComputerRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/CompanionDeviceComputerRoleBehavior.java)6
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceGlassesRoleBehavior.java41
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceWatchRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/CompanionDeviceWatchRoleBehavior.java)6
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/DevicePolicyManagementRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/DevicePolicyManagementRoleBehavior.java)5
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/DialerRoleBehavior.java)40
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/DocumentManagerRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/DocumentManagerRoleBehavior.java)5
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/EmergencyRoleBehavior.java)20
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/HomeRoleBehavior.java)88
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/NotesRoleBehavior.java58
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/SmsRoleBehavior.java)37
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/SystemShellRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/SystemShellRoleBehavior.java)5
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/SystemUiRoleBehavior.java)5
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/SystemWearHealthServiceRoleBehavior.java36
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/behavior/TelevisionRoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/TelevisionRoleBehavior.java)5
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/AppOp.java)8
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/AppOpPermissions.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/AppOpPermissions.java)6
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/IntentFilterData.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/IntentFilterData.java)2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Permission.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/Permission.java)8
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/PermissionSet.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/PermissionSet.java)2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/Permissions.java)64
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/PreferredActivity.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/PreferredActivity.java)2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/RequiredActivity.java)14
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/RequiredBroadcastReceiver.java)13
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/RequiredComponent.java)50
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/RequiredContentProvider.java)13
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredMetaData.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/RequiredMetaData.java)2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/RequiredService.java)14
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Role.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/Role.java)190
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/RoleBehavior.java)57
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/RoleParser.java)47
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/model/Roles.java (renamed from PermissionController/src/com/android/permissioncontroller/role/model/Roles.java)2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/ArrayUtils.java61
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/CollectionUtils.java89
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/NotificationUtils.java (renamed from PermissionController/src/com/android/permissioncontroller/role/utils/NotificationUtils.java)2
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/PackageUtils.java91
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/RoleManagerCompat.java102
-rw-r--r--PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java88
-rw-r--r--PermissionController/src/android/support/wearable/view/CircledImageView.java602
-rw-r--r--PermissionController/src/android/support/wearable/view/Gusterpolator.java84
-rw-r--r--PermissionController/src/android/support/wearable/view/ProgressDrawable.java176
-rw-r--r--PermissionController/src/android/support/wearable/view/SimpleAnimatorListener.java67
-rw-r--r--PermissionController/src/android/support/wearable/view/WearableListView.java1388
-rw-r--r--PermissionController/src/com/android/permissioncontroller/Constants.java31
-rw-r--r--PermissionController/src/com/android/permissioncontroller/DeviceUtils.java4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/PermissionController.proto2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/PermissionControllerApplication.java13
-rw-r--r--PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt25
-rw-r--r--PermissionController/src/com/android/permissioncontroller/hibernation/OWNERS1
-rw-r--r--PermissionController/src/com/android/permissioncontroller/incident/ReportDetails.java11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING54
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/LightInstallSourceInfoLiveData.kt98
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/LightPackageInfoLiveData.kt8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/PackageBroadcastReceiver.kt4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesUiInfoLiveData.kt52
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelInfoLiveData.kt156
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt13
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt151
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightPackageOpsLiveData.kt117
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/v34/AppDataSharingUpdatesLiveData.kt89
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightInstallSourceInfo.kt30
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt75
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermission.kt17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/SafetyLabelInfo.kt34
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt256
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt94
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/v31/AppPermissionUsage.java73
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/v31/PermissionUsages.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/v34/AppDataSharingUpdate.kt108
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt1
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt211
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/service/v34/package-info.java (renamed from service/java/com/android/access/AccessCheckingService.kt)12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java116
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java26
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/ReviewOngoingUsageActivity.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/UnusedAppsFragment.kt5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionsFragment.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt29
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt211
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt142
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java70
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt147
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java15
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionControlPreference.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewPermissionsFragment.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/DashboardUtils.kt103
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionDetailsFragment.java428
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionHistoryPreference.java25
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionUsageV2Fragment.java386
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatePreference.kt59
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFragment.kt184
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesWrapperFragment.kt39
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/FooterWithLinkPreference.kt86
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt204
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/package-info.java18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt187
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt379
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewOngoingUsageViewModel.kt6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewPermissionsViewModel.kt6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageControlPreferenceUtils.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt798
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModelNew.kt632
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModel.kt278
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModelNew.kt358
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v33/SafetyCenterQsViewModel.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/AppDataSharingUpdatesViewModel.kt170
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt236
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/package-info.java18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/television/GrantPermissionsViewHandlerImpl.java5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java481
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleViewHandler.kt97
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/v34/package-info.java (renamed from service/java/com/android/access/package-info.java)10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt337
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt44
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt44
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java43
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java40
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt5
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/SafetyCenterReceiver.kt26
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/privacysources/v34/AppDataSharingUpdatesPrivacySource.kt121
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/Role.md12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING17
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/model/EncryptionUnawareConfirmationMixin.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/model/RoleParserInitializer.java (renamed from tests/cts/safetycenter/src/android/safetycenter/cts/testing/WaitForBroadcastIdle.kt)25
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/model/VisibilityMixin.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/service/RoleControllerServiceImpl.java12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/service/RoleSearchIndexablesProvider.java15
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppActivity.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java13
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java11
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java4
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleViewModel.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleItem.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleListLiveData.java7
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/RoleLiveData.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/TwoTargetPreference.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppFragment.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/AssistantRoleUiBehavior.java63
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/BrowserRoleUiBehavior.java37
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/DialerRoleUiBehavior.java68
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/EmergencyRoleUiBehavior.java47
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java121
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java133
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/behavior/SmsRoleUiBehavior.java47
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessActivity.java8
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java12
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/utils/PackageUtils.java35
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/utils/RoleManagerCompat.java42
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java151
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java39
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/TEST_MAPPING10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java135
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterJobServiceFlags.java61
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PendingIntentSender.kt22
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java198
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt111
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsFragment.java24
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt126
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterUiFlags.kt35
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupPreference.kt36
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyHomepageEntryPreference.kt63
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java127
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetySubpageEntryPreference.kt109
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt75
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterViewModel.kt16
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt80
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetylabel/AppsSafetyLabelHistory.kt92
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetylabel/AppsSafetyLabelHistoryPersistence.kt410
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetylabel/SafetyLabelChangedBroadcastReceiver.kt63
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING27
-rw-r--r--PermissionController/tests/inprocess/Android.bp7
-rw-r--r--PermissionController/tests/mocking/Android.bp8
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationControllerTest.kt10
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/ReviewPermissionsViewModelTest.kt14
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt7
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/AppDataSharingUpdatesPrivacySourceTest.kt226
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/SafetyCenterReceiverTest.kt7
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt14
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/OWNERS3
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/StatusUiDataTest.kt267
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/AppsSafetyLabelHistoryPersistenceTest.kt310
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/SafetyLabelChangesJobServiceTest.kt193
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/TEST_MAPPING12
-rw-r--r--PermissionController/tests/outofprocess/Android.bp1
-rw-r--r--PermissionController/tests/permissionui/Android.bp11
-rw-r--r--PermissionController/tests/permissionui/AndroidTest.xml1
-rw-r--r--SafetyCenter/Config/Android.bp1
-rw-r--r--SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java80
-rw-r--r--SafetyCenter/Config/tests/Android.bp5
-rw-r--r--SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt54
-rw-r--r--SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt114
-rw-r--r--SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_hidden_with_only_dynamic.xml15
-rw-r--r--SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_hidden_with_only_static.xml14
-rw-r--r--SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_stateful_with_only_issue_only.xml13
-rw-r--r--SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_stateless_with_only_issue_only.xml13
-rw-r--r--SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_deduplication_groups.xml32
-rw-r--r--SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_notifications.xml16
-rw-r--r--SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_package_certs.xml32
-rw-r--r--SafetyCenter/Config/tests/res/raw-v34/config_valid.xml193
-rw-r--r--SafetyCenter/Config/tests/res/values/strings.xml3
-rw-r--r--SafetyCenter/ConfigLintChecker/Android.bp9
-rw-r--r--SafetyCenter/ConfigLintChecker/java/android/annotation/SuppressLint.java39
-rw-r--r--SafetyCenter/ConfigLintChecker/java/android/os/Build.java8
-rw-r--r--SafetyCenter/ConfigLintChecker/java/android/os/Parcel.java4
-rw-r--r--SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ConfigSchemaDetector.kt61
-rw-r--r--SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/FileSdk.kt82
-rw-r--r--SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ParserExceptionDetector.kt44
-rw-r--r--SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java28
-rw-r--r--SafetyCenter/ConfigLintChecker/java/com/android/modules/utils/build/SdkLevel.java45
-rw-r--r--SafetyCenter/ConfigLintChecker/tests/java/android/safetycenter/lint/test/ConfigSchemaDetectorTest.kt121
-rw-r--r--SafetyCenter/ConfigLintChecker/tests/java/android/safetycenter/lint/test/ParserExceptionDetectorTest.kt141
-rw-r--r--SafetyCenter/Persistence/tests/java/com/android/safetycenter/persistence/PersistedSafetyCenterIssueTest.kt2
-rw-r--r--SafetyCenter/Resources/res/raw-v34/safety_center_config.xml124
-rw-r--r--SafetyCenter/Resources/res/values-fa/strings.xml2
-rw-r--r--SafetyCenter/Resources/res/values-v34/strings.xml24
-rw-r--r--SafetyCenter/Resources/shared_res/values-af/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-am/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ar/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-as/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-az/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-be/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-bg/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-bn/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-bs/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ca/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-cs/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-da/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-de/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-el/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-es/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-et/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-eu/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-fa/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-fi/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-fr/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-gl/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-gu/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-hi/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-hr/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-hu/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-hy/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-in/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-is/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-it/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-iw/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ja/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ka/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-kk/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-km/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-kn/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ko/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ky/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-lo/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-lt/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-lv/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-mk/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ml/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-mn/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-mr/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ms/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-my/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-nb/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ne/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-nl/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-or/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-pa/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-pl/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ro/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ru/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-si/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-sk/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-sl/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-sq/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-sr/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-sv/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-sw/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ta/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-te/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-th/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-tl/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-tr/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-uk/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-ur/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-uz/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-vi/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values-zu/strings.xml10
-rw-r--r--SafetyCenter/Resources/shared_res/values/strings.xml22
-rw-r--r--SafetyCenter/TEST_MAPPING10
-rw-r--r--SafetyLabel/Android.bp49
-rw-r--r--SafetyLabel/java/com/android/permission/safetylabel/DataCategory.java100
-rw-r--r--SafetyLabel/java/com/android/permission/safetylabel/DataCategoryConstants.java101
-rw-r--r--SafetyLabel/java/com/android/permission/safetylabel/DataLabel.java83
-rw-r--r--SafetyLabel/java/com/android/permission/safetylabel/DataLabelConstants.java42
-rw-r--r--SafetyLabel/java/com/android/permission/safetylabel/DataPurposeConstants.java79
-rw-r--r--SafetyLabel/java/com/android/permission/safetylabel/DataType.java154
-rw-r--r--SafetyLabel/java/com/android/permission/safetylabel/DataTypeConstants.java421
-rw-r--r--SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java60
-rw-r--r--SafetyLabel/tests/Android.bp38
-rw-r--r--SafetyLabel/tests/AndroidManifest.xml30
-rw-r--r--SafetyLabel/tests/AndroidTest.xml40
-rw-r--r--SafetyLabel/tests/java/com/android/permission/safetylabel/DataCategoryTest.kt261
-rw-r--r--SafetyLabel/tests/java/com/android/permission/safetylabel/DataLabelTest.kt126
-rw-r--r--SafetyLabel/tests/java/com/android/permission/safetylabel/DataTypeTest.kt280
-rw-r--r--SafetyLabel/tests/java/com/android/permission/safetylabel/SafetyLabelTest.kt70
-rw-r--r--SafetyLabel/tests/java/com/android/permission/safetylabel/SafetyLabelTestPersistableBundles.kt261
-rw-r--r--framework-s/Android.bp19
-rw-r--r--framework-s/api/current.txt1
-rw-r--r--framework-s/api/system-current.txt125
-rw-r--r--framework-s/java/android/app/role/RoleManager.java17
-rw-r--r--framework-s/java/android/app/role/TEST_MAPPING19
-rw-r--r--framework-s/java/android/safetycenter/ISafetyCenterManager.aidl10
-rw-r--r--framework-s/java/android/safetycenter/SafetyCenterData.java234
-rw-r--r--framework-s/java/android/safetycenter/SafetyCenterIssue.java339
-rw-r--r--framework-s/java/android/safetycenter/SafetyCenterManager.java64
-rw-r--r--framework-s/java/android/safetycenter/SafetyEvent.java18
-rw-r--r--framework-s/java/android/safetycenter/SafetySourceData.java92
-rw-r--r--framework-s/java/android/safetycenter/SafetySourceIssue.java899
-rw-r--r--framework-s/java/android/safetycenter/SafetySourceStatus.java18
-rw-r--r--framework-s/java/android/safetycenter/TEST_MAPPING10
-rw-r--r--framework-s/java/android/safetycenter/config/BuilderUtils.java40
-rw-r--r--framework-s/java/android/safetycenter/config/SafetyCenterConfig.java17
-rw-r--r--framework-s/java/android/safetycenter/config/SafetySource.java304
-rw-r--r--framework-s/java/android/safetycenter/config/SafetySourcesGroup.java169
-rw-r--r--framework-s/java/android/safetycenter/config/safety_center_config-v34.xsd217
-rw-r--r--framework-s/java/android/safetylabel/SafetyLabelConstants.java63
-rw-r--r--framework/Android.bp9
-rw-r--r--ktfmt_includes.txt1
-rw-r--r--service/Android.bp28
-rw-r--r--service/java/com/android/permission/compat/UserHandleCompat.java11
-rw-r--r--service/java/com/android/role/RoleService.java10
-rw-r--r--service/java/com/android/role/RoleShellCommand.java15
-rw-r--r--service/java/com/android/role/TEST_MAPPING21
-rw-r--r--service/java/com/android/safetycenter/PendingIntentFactory.java27
-rw-r--r--service/java/com/android/safetycenter/RefreshReasons.java12
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java89
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterConfigReader.java71
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterDataFactory.java260
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterFlags.java29
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterIssueCache.java26
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterIssueDeduplicator.java204
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterIssueExtended.java144
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterListeners.java56
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterNotificationFactory.java48
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterNotificationReceiver.java83
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterNotificationSender.java25
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterPullAtomCallback.java16
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterRepository.java78
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterService.java71
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java11
-rw-r--r--service/java/com/android/safetycenter/TEST_MAPPING10
-rw-r--r--tests/cts/safetycenter/Android.bp2
-rw-r--r--tests/cts/safetycenter/AndroidManifest.xml1
-rw-r--r--tests/cts/safetycenter/AndroidTest.xml1
-rw-r--r--tests/cts/safetycenter/TEST_MAPPING10
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt492
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt6
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt11
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt491
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt1270
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterMultiUsersTest.kt95
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterNotificationTest.kt317
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryGroupTest.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryTest.kt8
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt5
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt24
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt162
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt1330
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt26
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt54
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt342
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt432
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/Coroutines.kt18
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/NotificationCharacteristics.kt24
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterApisWithShellPermissions.kt11
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsConfigs.kt231
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsData.kt108
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsHelper.kt2
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterEnabledChangedReceiver.kt4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterFlags.kt80
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceCtsData.kt108
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceReceiver.kt23
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/UiTestHelper.kt21
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt78
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterSubpagesTest.kt602
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/EqualsHashCodeToStringTester.kt139
568 files changed, 29082 insertions, 7447 deletions
diff --git a/Android.bp b/Android.bp
index 74e980580..f9eee9ddb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -111,6 +111,7 @@ bootclasspath_fragment {
package_prefixes: [
"android.app.role",
"android.safetycenter",
+ "android.safetylabel",
"com.android.permission",
],
},
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index ee31c505f..925bbf9d7 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -4,5 +4,5 @@ commit_msg_changeid_field = true
[Hook Scripts]
checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
-ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
-strings_lint_hook = ${REPO_ROOT}/frameworks/base/tools/stringslint/stringslint_sha.sh ${PREUPLOAD_COMMIT}
+ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/packages/modules/Permission/ktfmt_includes.txt ${PREUPLOAD_FILES}
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES}
diff --git a/PermissionController/Android.bp b/PermissionController/Android.bp
index 5a4d2930e..d0563133f 100644
--- a/PermissionController/Android.bp
+++ b/PermissionController/Android.bp
@@ -45,7 +45,10 @@ genrule {
java_library {
name: "permissioncontroller-statsd",
sdk_version: "system_current",
-
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.permission",
+ ],
srcs: [
":statslog-permissioncontroller-java-gen",
],
@@ -136,10 +139,14 @@ android_app {
"SettingsLibUtils",
"modules-utils-build_system",
"safety-center-resources-lib",
+ "lottie",
+ "safety-label",
+ "role-controller",
],
proto: {
type: "lite",
+ include_dirs: ["packages/modules/Permission/PermissionController/src/com/android/permissioncontroller"],
},
lint: {
@@ -150,7 +157,10 @@ android_app {
proguard_flags_files: ["proguard.flags"],
},
- plugins: ["java_api_finder"],
-
kotlincflags: ["-Xjvm-default=enable"],
+
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.permission",
+ ],
}
diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml
index 83d598888..56e64aa49 100644
--- a/PermissionController/AndroidManifest.xml
+++ b/PermissionController/AndroidManifest.xml
@@ -64,6 +64,8 @@
<uses-permission android:name="android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK" />
<uses-permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS"/>
<uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" />
+ <uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" />
+ <uses-permission android:name="android.permission.GET_APP_METADATA" />
<application android:name="com.android.permissioncontroller.PermissionControllerApplication"
android:label="@string/app_name"
@@ -190,6 +192,20 @@
</intent-filter>
</receiver>
+ <service
+ android:name="com.android.permissioncontroller.permission.service.v34.SafetyLabelChangesJobService"
+ android:enabled="@bool/is_at_least_u"
+ android:permission="android.permission.BIND_JOB_SERVICE"
+ android:exported="false" />
+
+ <receiver
+ android:name="com.android.permissioncontroller.permission.service.v34.SafetyLabelChangesJobService$Receiver"
+ android:enabled="@bool/is_at_least_u"
+ android:exported="false">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ </intent-filter>
+ </receiver>
<receiver
android:name="com.android.permissioncontroller.privacysources.AccessibilityOnBootReceiver"
@@ -224,6 +240,17 @@
</intent-filter>
</receiver>
+ <receiver
+ android:name="com.android.permissioncontroller.safetylabel.SafetyLabelChangedBroadcastReceiver"
+ android:enabled="@bool/is_at_least_u"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.PACKAGE_REMOVED" />
+ <action android:name="android.intent.action.PACKAGE_ADDED" />
+ <data android:scheme="package" />
+ </intent-filter>
+ </receiver>
+
<service
android:name="com.android.permissioncontroller.privacysources.AccessibilityJobService"
android:enabled="@bool/is_at_least_t"
@@ -248,6 +275,17 @@
</intent-filter>
</activity>
+ <activity android:name="com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity"
+ android:configChanges="keyboardHidden|screenSize"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:theme="@style/GrantPermissions.FilterTouches"
+ android:visibleToInstantApps="true"
+ android:inheritShowWhenLocked="true"
+ android:hardwareAccelerated="false"
+ android:canDisplayOnRemoteDevices="false">
+ </activity>
+
<activity android:name="com.android.permissioncontroller.permission.ui.ManagePermissionsActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/app_permissions"
@@ -262,6 +300,7 @@
<action android:name="android.intent.action.REVIEW_PERMISSION_USAGE" />
<action android:name="android.intent.action.REVIEW_PERMISSION_HISTORY" />
<action android:name="android.intent.action.MANAGE_UNUSED_APPS" />
+ <action android:name="android.intent.action.REVIEW_APP_DATA_SHARING_UPDATES" />
<action android:name="android.permission.action.REVIEW_PERMISSION_DECISIONS"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
@@ -312,7 +351,7 @@
<activity android:name="com.android.permissioncontroller.permission.ui.OverlayWarningDialog"
android:excludeFromRecents="true"
android:exported="false"
- android:theme="@style/Theme.AppCompat.DayNight.Dialog.Alert" />
+ android:theme="@style/Theme.DeviceDefault.Dialog.NoActionBar.DayNight" />
<activity android:name="com.android.permissioncontroller.permission.ui.LocationProviderInterceptDialog"
android:excludeFromRecents="true"
@@ -523,9 +562,10 @@
</receiver>
<activity android:name="com.android.permissioncontroller.incident.ConfirmationActivity"
- android:theme="@style/Theme.DeviceDefault.Dialog.Alert.DayNight"
+ android:theme="@style/Theme.DeviceDefault.Dialog.NoActionBar.DayNight"
android:exported="false"
android:excludeFromRecents="true"
+ android:finishOnCloseSystemDialogs="true"
android:noHistory="true" />
<receiver android:name="com.android.permissioncontroller.incident.ApprovalReceiver"
diff --git a/PermissionController/TEST_MAPPING b/PermissionController/TEST_MAPPING
index 0911e0d91..0ae3818fd 100644
--- a/PermissionController/TEST_MAPPING
+++ b/PermissionController/TEST_MAPPING
@@ -11,6 +11,74 @@
}
],
"file_patterns": ["res/xml/roles\\.xml"]
+ },
+ {
+ "name": "PermissionUiTestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsPermission3TestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsRoleTestCases[com.google.android.permission.apex]",
+ "options": [
+ // TODO(b/238677748): These two tests currently fails on R base image
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList"
+ },
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ],
+ "file_patterns": ["res/xml/roles\\.xml"]
+ },
+ {
+ "name": "PermissionUiTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ },
+ // TODO(b/238773220): These tests currently fails on R base image
+ {
+ "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageCustomPermissionsFragmentTest#groupSummaryGetsUpdatedWhenPermissionGetsGranted"
+ },
+ {
+ "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageCustomPermissionsFragmentTest#groupSummaryGetsUpdatedWhenPermissionGetsRevoked"
+ },
+ {
+ "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageStandardPermissionsFragmentTest#additionalPermissionSummaryGetUpdateWhenAppGetsInstalled"
+ },
+ {
+ "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageStandardPermissionsFragmentTest#additionalPermissionSummaryGetUpdateWhenDefinerGetsUninstalled"
+ },
+ {
+ "exclude-filter": "com.android.permissioncontroller.permissionui.ui.handheld.ManageStandardPermissionsFragmentTest#additionalPermissionSummaryGetUpdateWhenUserGetsUninstalled"
+ }
+ ]
+ },
+ {
+ "name": "CtsPermission3TestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
}
],
"imports": [
diff --git a/PermissionController/iconloaderlib/build.gradle b/PermissionController/iconloaderlib/build.gradle
index 84102758e..35ca8dee2 100644
--- a/PermissionController/iconloaderlib/build.gradle
+++ b/PermissionController/iconloaderlib/build.gradle
@@ -34,5 +34,5 @@ android {
}
dependencies {
- implementation "androidx.core:core:${ANDROID_X_VERSION}"
+ implementation "androidx.core:core"
}
diff --git a/PermissionController/proguard.flags b/PermissionController/proguard.flags
index 1f0b03269..13590aa39 100644
--- a/PermissionController/proguard.flags
+++ b/PermissionController/proguard.flags
@@ -8,7 +8,12 @@
-dontwarn androidx.core.**
# Keep classes that implements RoleBehavior, which are used by reflection.
--keep class * implements com.android.permissioncontroller.role.model.RoleBehavior {
+-keep class * implements com.android.role.controller.model.RoleBehavior {
+ *;
+}
+
+# Keep classes that implements RoleUiBehavior, which are used by reflection.
+-keep class * implements com.android.permissioncontroller.role.ui.behavior.RoleUiBehavior {
*;
}
@@ -25,4 +30,4 @@
*** get*();
*** set*(***);
*** has*();
-} \ No newline at end of file
+}
diff --git a/PermissionController/res/drawable-v34/ic_business.xml b/PermissionController/res/drawable-v34/ic_business.xml
new file mode 100644
index 000000000..23ee37d0e
--- /dev/null
+++ b/PermissionController/res/drawable-v34/ic_business.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,7L12,3L2,3v18h20L22,7L12,7zM6,19L4,19v-2h2v2zM6,15L4,15v-2h2v2zM6,11L4,11L4,9h2v2zM6,7L4,7L4,5h2v2zM10,19L8,19v-2h2v2zM10,15L8,15v-2h2v2zM10,11L8,11L8,9h2v2zM10,7L8,7L8,5h2v2zM20,19h-8v-2h2v-2h-2v-2h2v-2h-2L12,9h8v10zM18,11h-2v2h2v-2zM18,15h-2v2h2v-2z"/>
+</vector>
diff --git a/PermissionController/res/drawable-v34/ic_collections_bookmark.xml b/PermissionController/res/drawable-v34/ic_collections_bookmark.xml
new file mode 100644
index 000000000..29f9e569c
--- /dev/null
+++ b/PermissionController/res/drawable-v34/ic_collections_bookmark.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M4,6L2,6v14c0,1.1 0.9,2 2,2h14v-2L4,20L4,6zM20,2L8,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,16L8,16L8,4h5v7l2.5,-1.88L18,11L18,4h2v12z"/>
+</vector>
diff --git a/PermissionController/res/drawable-v34/ic_gear.xml b/PermissionController/res/drawable-v34/ic_gear.xml
new file mode 100644
index 000000000..958284d4c
--- /dev/null
+++ b/PermissionController/res/drawable-v34/ic_gear.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M13.85,22.25h-3.7c-0.74,0 -1.36,-0.54 -1.45,-1.27l-0.27,-1.89c-0.27,-0.14 -0.53,-0.29 -0.79,-0.46l-1.8,0.72c-0.7,0.26 -1.47,-0.03 -1.81,-0.65L2.2,15.53c-0.35,-0.66 -0.2,-1.44 0.36,-1.88l1.53,-1.19c-0.01,-0.15 -0.02,-0.3 -0.02,-0.46 0,-0.15 0.01,-0.31 0.02,-0.46l-1.52,-1.19c-0.59,-0.45 -0.74,-1.26 -0.37,-1.88l1.85,-3.19c0.34,-0.62 1.11,-0.9 1.79,-0.63l1.81,0.73c0.26,-0.17 0.52,-0.32 0.78,-0.46l0.27,-1.91c0.09,-0.7 0.71,-1.25 1.44,-1.25h3.7c0.74,0 1.36,0.54 1.45,1.27l0.27,1.89c0.27,0.14 0.53,0.29 0.79,0.46l1.8,-0.72c0.71,-0.26 1.48,0.03 1.82,0.65l1.84,3.18c0.36,0.66 0.2,1.44 -0.36,1.88l-1.52,1.19c0.01,0.15 0.02,0.3 0.02,0.46s-0.01,0.31 -0.02,0.46l1.52,1.19c0.56,0.45 0.72,1.23 0.37,1.86l-1.86,3.22c-0.34,0.62 -1.11,0.9 -1.8,0.63l-1.8,-0.72c-0.26,0.17 -0.52,0.32 -0.78,0.46l-0.27,1.91c-0.1,0.68 -0.72,1.22 -1.46,1.22zM10.62,20.25h2.76l0.37,-2.55 0.53,-0.22c0.44,-0.18 0.88,-0.44 1.34,-0.78l0.45,-0.34 2.38,0.96 1.38,-2.4 -2.03,-1.58 0.07,-0.56c0.03,-0.26 0.06,-0.51 0.06,-0.78s-0.03,-0.53 -0.06,-0.78l-0.07,-0.56 2.03,-1.58 -1.39,-2.4 -2.39,0.96 -0.45,-0.35c-0.42,-0.32 -0.87,-0.58 -1.33,-0.77l-0.52,-0.22 -0.37,-2.55h-2.76l-0.37,2.55 -0.53,0.21c-0.44,0.19 -0.88,0.44 -1.34,0.79l-0.45,0.33 -2.38,-0.95 -1.39,2.39 2.03,1.58 -0.07,0.56c-0.03,0.26 -0.06,0.53 -0.06,0.79s0.02,0.53 0.06,0.78l0.07,0.56 -2.03,1.58 1.38,2.4 2.39,-0.96 0.45,0.35c0.43,0.33 0.86,0.58 1.33,0.77l0.53,0.22 0.38,2.55z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,12m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0"/>
+</vector>
diff --git a/PermissionController/res/drawable-v34/ic_info.xml b/PermissionController/res/drawable-v34/ic_info.xml
new file mode 100644
index 000000000..35f7f5f61
--- /dev/null
+++ b/PermissionController/res/drawable-v34/ic_info.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M11,7h2v2h-2zM11,11h2v6h-2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
+</vector>
diff --git a/PermissionController/res/drawable/coarse_off_dark.gif b/PermissionController/res/drawable/coarse_off_dark.gif
deleted file mode 100644
index 09a7da18d..000000000
--- a/PermissionController/res/drawable/coarse_off_dark.gif
+++ /dev/null
Binary files differ
diff --git a/PermissionController/res/drawable/coarse_off_light.gif b/PermissionController/res/drawable/coarse_off_light.gif
deleted file mode 100644
index a5419cd91..000000000
--- a/PermissionController/res/drawable/coarse_off_light.gif
+++ /dev/null
Binary files differ
diff --git a/PermissionController/res/drawable/coarse_on_dark.gif b/PermissionController/res/drawable/coarse_on_dark.gif
deleted file mode 100644
index a2ea07bd0..000000000
--- a/PermissionController/res/drawable/coarse_on_dark.gif
+++ /dev/null
Binary files differ
diff --git a/PermissionController/res/drawable/coarse_on_light.gif b/PermissionController/res/drawable/coarse_on_light.gif
deleted file mode 100644
index 491edb612..000000000
--- a/PermissionController/res/drawable/coarse_on_light.gif
+++ /dev/null
Binary files differ
diff --git a/PermissionController/res/drawable/fine_off_dark.gif b/PermissionController/res/drawable/fine_off_dark.gif
deleted file mode 100644
index 560e38a34..000000000
--- a/PermissionController/res/drawable/fine_off_dark.gif
+++ /dev/null
Binary files differ
diff --git a/PermissionController/res/drawable/fine_off_light.gif b/PermissionController/res/drawable/fine_off_light.gif
deleted file mode 100644
index 5661b9270..000000000
--- a/PermissionController/res/drawable/fine_off_light.gif
+++ /dev/null
Binary files differ
diff --git a/PermissionController/res/drawable/fine_on_dark.gif b/PermissionController/res/drawable/fine_on_dark.gif
deleted file mode 100644
index aadf7821b..000000000
--- a/PermissionController/res/drawable/fine_on_dark.gif
+++ /dev/null
Binary files differ
diff --git a/PermissionController/res/drawable/fine_on_light.gif b/PermissionController/res/drawable/fine_on_light.gif
deleted file mode 100644
index a592c6305..000000000
--- a/PermissionController/res/drawable/fine_on_light.gif
+++ /dev/null
Binary files differ
diff --git a/PermissionController/res/drawable/grant_dialog_permission_rationale_background.xml b/PermissionController/res/drawable/grant_dialog_permission_rationale_background.xml
new file mode 100644
index 000000000..2c2f588cd
--- /dev/null
+++ b/PermissionController/res/drawable/grant_dialog_permission_rationale_background.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="16dp"/>
+ <stroke android:width="1dp" android:color="?android:attr/textColorSecondaryInverse" />
+</shape> \ No newline at end of file
diff --git a/PermissionController/res/drawable/ic_more_info_arrow.xml b/PermissionController/res/drawable/ic_more_info_arrow.xml
new file mode 100644
index 000000000..73eb5ccfc
--- /dev/null
+++ b/PermissionController/res/drawable/ic_more_info_arrow.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="48dp"
+ android:height="48dp" android:viewportWidth="48" android:viewportHeight="48"
+ android:autoMirrored="true" android:tint="?attr/colorControlNormal">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M18.75,36 L16.6,33.85 26.5,23.95 16.6,14.05 18.75,11.9 30.8,23.95Z"/>
+</vector> \ No newline at end of file
diff --git a/PermissionController/res/drawable/ic_shield_exclamation_outline.xml b/PermissionController/res/drawable/ic_shield_exclamation_outline.xml
new file mode 100644
index 000000000..5785babf9
--- /dev/null
+++ b/PermissionController/res/drawable/ic_shield_exclamation_outline.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="48dp"
+ android:height="48dp" android:viewportWidth="48" android:viewportHeight="48"
+ android:tint="?attr/colorControlNormal">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M24,31.3Q24.7,31.3 25.2,30.8Q25.7,30.3 25.7,29.6Q25.7,28.9 25.2,28.4Q24.7,27.9 24,27.9Q23.3,27.9 22.8,28.4Q22.3,28.9 22.3,29.6Q22.3,30.3 22.8,30.8Q23.3,31.3 24,31.3ZM22.5,24.6H25.5V14.25H22.5ZM24,43.95Q17,42.2 12.5,35.825Q8,29.45 8,21.85V9.95L24,3.95L40,9.95V21.85Q40,29.45 35.5,35.825Q31,42.2 24,43.95ZM24,40.85Q29.75,38.95 33.375,33.675Q37,28.4 37,21.85V12.05L24,7.15L11,12.05V21.85Q11,28.4 14.625,33.675Q18.25,38.95 24,40.85ZM24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Q24,24 24,24Z"/>
+</vector>
diff --git a/PermissionController/res/layout-television/preference_permissions_category.xml b/PermissionController/res/layout-television/preference_permissions_category.xml
index 59412065d..ff5cc6147 100644
--- a/PermissionController/res/layout-television/preference_permissions_category.xml
+++ b/PermissionController/res/layout-television/preference_permissions_category.xml
@@ -25,7 +25,7 @@
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
- android:id="@+android:id/title"
+ android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/settings_category_text_color"
@@ -33,7 +33,7 @@
android:textStyle="bold"/>
<TextView
- android:id="@+android:id/summary"
+ android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
diff --git a/PermissionController/res/layout-television/preference_permissions_no_apps.xml b/PermissionController/res/layout-television/preference_permissions_no_apps.xml
index dbcd3ec25..0511ecf57 100644
--- a/PermissionController/res/layout-television/preference_permissions_no_apps.xml
+++ b/PermissionController/res/layout-television/preference_permissions_no_apps.xml
@@ -17,7 +17,7 @@
<!-- Layout used for PreferenceCategory in a PreferenceActivity. -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+android:id/title"
+ android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
diff --git a/PermissionController/res/layout-v33/preference_issue_card.xml b/PermissionController/res/layout-v33/preference_issue_card.xml
index cbe8e498e..07ce06810 100644
--- a/PermissionController/res/layout-v33/preference_issue_card.xml
+++ b/PermissionController/res/layout-v33/preference_issue_card.xml
@@ -28,6 +28,12 @@
style="@style/SafetyCenterIssueDismiss" />
<TextView
+ android:id="@+id/issue_card_attribution_title"
+ android:text="@string/summary_placeholder"
+ android:importantForAccessibility="no"
+ style="@style/SafetyCenterIssueAttributionTitle" />
+
+ <TextView
android:id="@+id/issue_card_title"
android:text="@string/summary_placeholder"
android:importantForAccessibility="no"
diff --git a/PermissionController/res/layout-v34/permission_rationale.xml b/PermissionController/res/layout-v34/permission_rationale.xml
new file mode 100644
index 000000000..c3b782d75
--- /dev/null
+++ b/PermissionController/res/layout-v34/permission_rationale.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<!--
+ ~ A lot of content in this file is identical to grant_permissions.xml
+ ~ Be sure to update both files when making changes.
+ -->
+
+<!-- In (hopefully very rare) case dialog is too high: allow scrolling -->
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="@style/PermissionRationaleScrollView">
+
+ <LinearLayout
+ android:id="@+id/grant_singleton"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ style="@style/PermissionRationaleSingleton">
+
+ <!-- The dialog -->
+ <LinearLayout
+ android:id="@+id/grant_dialog"
+ android:theme="@style/Theme.PermissionGrantDialog"
+ android:importantForAccessibility="no"
+ style="@style/PermissionRationaleDialog">
+
+ <LinearLayout
+ android:id="@+id/content_container"
+ style="@style/PermissionRationaleContent">
+
+ <LinearLayout
+ style="@style/PermissionRationaleTitleContainer">
+
+ <ImageView
+ android:id="@+id/permission_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_shield_exclamation_outline"
+ style="@style/PermissionRationaleTitleIcon" />
+
+ <TextView
+ android:id="@+id/permission_rationale_title"
+ android:text="@string/permission_rationale_title"
+ style="@style/PermissionRationaleTitleMessage" />
+
+ </LinearLayout>
+
+ <LinearLayout style="@style/PermissionRationaleSectionOuterContainer">
+ <ImageView
+ android:id="@+id/purpose_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_info"
+ style="@style/PermissionRationaleSectionIcon" />
+ <LinearLayout style="@style/PermissionRationaleSectionInnerContainer">
+ <TextView
+ android:id="@+id/purpose_title"
+ android:text="@string/permission_rationale_purpose_title"
+ style="@style/PermissionRationaleSectionTitle" />
+ <TextView
+ android:id="@+id/purpose_message"
+ android:text="@string/permission_rationale_purpose_message"
+ style="@style/PermissionRationaleSectionMessage" />
+ </LinearLayout>
+ </LinearLayout>
+ <LinearLayout style="@style/PermissionRationaleSectionOuterContainer">
+ <ImageView
+ android:id="@+id/third_party_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_business"
+ style="@style/PermissionRationaleSectionIcon" />
+ <LinearLayout style="@style/PermissionRationaleSectionInnerContainer">
+ <TextView
+ android:id="@+id/third_party_title"
+ android:text="@string/permission_rationale_thirdparty_title"
+ style="@style/PermissionRationaleSectionTitle" />
+ <TextView
+ android:id="@+id/third_party_message"
+ android:text="@string/permission_rationale_thirdparty_message"
+ style="@style/PermissionRationaleSectionMessage" />
+ </LinearLayout>
+ </LinearLayout>
+ <LinearLayout style="@style/PermissionRationaleSectionOuterContainer">
+ <ImageView
+ android:id="@+id/settings_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_gear"
+ style="@style/PermissionRationaleSectionIcon" />
+ <LinearLayout style="@style/PermissionRationaleSectionInnerContainer">
+ <TextView
+ android:id="@+id/settings_title"
+ android:text="@string/permission_rationale_permission_settings_title"
+ style="@style/PermissionRationaleSectionTitle" />
+ <TextView
+ android:id="@+id/settings_message"
+ android:text="@string/permission_rationale_permission_settings_message"
+ style="@style/PermissionRationaleSectionMessage" />
+ </LinearLayout>
+ </LinearLayout>
+ <LinearLayout style="@style/PermissionRationaleSectionOuterContainer">
+ <ImageView
+ android:id="@+id/learn_more_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_collections_bookmark"
+ style="@style/PermissionRationaleSectionIcon" />
+ <LinearLayout style="@style/PermissionRationaleSectionInnerContainer">
+ <TextView
+ android:id="@+id/learn_more_message"
+ android:text="@string/permission_rationale_permission_learn_more_title"
+ style="@style/PermissionRationaleSectionTitle" />
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout style="@style/PermissionRationaleButtonContainer">
+ <!-- TODO(b/260269197): update back button style -->
+ <Button
+ android:id="@+id/back_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="36dp"
+ android:layout_marginTop="6dp"
+ android:layout_marginBottom="6dp"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:text="@string/back" />
+ </LinearLayout>
+
+ </LinearLayout>
+ </LinearLayout>
+ </LinearLayout>
+</ScrollView> \ No newline at end of file
diff --git a/PermissionController/res/layout/app_permission.xml b/PermissionController/res/layout/app_permission.xml
index 697150b6f..8341102a3 100644
--- a/PermissionController/res/layout/app_permission.xml
+++ b/PermissionController/res/layout/app_permission.xml
@@ -38,6 +38,44 @@
<LinearLayout
style="@style/AppPermissionSelection">
+ <LinearLayout
+ android:id="@+id/app_permission_rationale_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/AppPermissionRationaleContainer">
+ <TextView
+ android:id="@+id/app_permission_rationale_message"
+ style="@style/AppPermissionMessage" />
+
+ <LinearLayout
+ android:id="@+id/app_permission_rationale_content"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/AppPermissionRationaleContent" >
+
+ <ImageView
+ android:id="@+id/app_permission_rationale_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_shield_exclamation_outline"
+ style="@style/AppPermissionRationaleIcon" />
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/AppPermissionRationaleTextContent">
+ <TextView
+ android:duplicateParentState="true"
+ android:id="@+id/app_permission_rationale_title"
+ style="@style/AppPermissionRationaleTitle" />
+ <TextView
+ android:duplicateParentState="true"
+ android:id="@+id/app_permission_rationale_subtitle"
+ style="@style/AppPermissionRationaleSubtitle" />
+ </LinearLayout>
+
+ </LinearLayout>
+ </LinearLayout>
+
<TextView
android:id="@+id/permission_message"
style="@style/AppPermissionMessage" />
@@ -64,6 +102,11 @@
style="@style/AppPermissionRadioButton" />
<RadioButton
+ android:id="@+id/select_photos_radio_button"
+ android:text="@string/app_permission_button_select_photos"
+ style="@style/AppPermissionRadioButton" />
+
+ <RadioButton
android:id="@+id/ask_one_time_radio_button"
android:text="@string/app_permission_button_ask"
style="@style/AppPermissionRadioButton" />
diff --git a/PermissionController/res/layout/footer_with_link_preference.xml b/PermissionController/res/layout/footer_with_link_preference.xml
new file mode 100644
index 000000000..2c921122d
--- /dev/null
+++ b/PermissionController/res/layout/footer_with_link_preference.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<!-- TODO(b/261666772): Extract to styles where necessary and update to final spec. -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="20dp"
+ android:layout_marginTop="24dp"
+ android:orientation="vertical"
+ android:clipToPadding="false">
+
+ <FrameLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="20dp">
+ <androidx.preference.internal.PreferenceImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:maxWidth="24dp"
+ app:maxHeight="24dp" />
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/footer_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp"
+ android:textColor="?android:attr/textColorSecondary" />
+
+ <TextView
+ android:id="@+id/footer_link"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="16dp" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/PermissionController/res/layout/grant_permissions.xml b/PermissionController/res/layout/grant_permissions.xml
index cda679d05..ca0706171 100644
--- a/PermissionController/res/layout/grant_permissions.xml
+++ b/PermissionController/res/layout/grant_permissions.xml
@@ -62,6 +62,30 @@
</LinearLayout>
+ <!-- permission rationale -->
+ <LinearLayout
+ android:id="@+id/permission_rationale_container"
+ style="@style/PermissionGrantPermissionRationaleContent">
+
+ <ImageView
+ android:id="@+id/permission_rationale_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_shield_exclamation_outline"
+ style="@style/PermissionGrantPermissionRationaleIcon" />
+
+ <TextView
+ android:id="@+id/permission_rationale_message"
+ style="@style/PermissionGrantPermissionRationaleMessage" />
+
+ <ImageView
+ android:id="@+id/permission_rationale_more_info_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_more_info_arrow"
+ style="@style/PermissionGrantPermissionRationaleMoreInfoIcon" />
+
+ </LinearLayout>
+
+ <!-- location (precise/approximate) animations -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -117,11 +141,31 @@
style="@style/PermissionGrantButtonAllowOneTime" />
<com.android.permissioncontroller.permission.ui.widget.SecureButton
+ android:id="@+id/permission_allow_all_photos_button"
+ android:text="@string/grant_dialog_button_allow_all_photos"
+ style="@style/PermissionGrantButtonAllowAllPhotos" />
+
+ <com.android.permissioncontroller.permission.ui.widget.SecureButton
+ android:id="@+id/permission_allow_more_selected_photos_button"
+ android:text="@string/grant_dialog_button_allow_more_selected_photos"
+ style="@style/PermissionGrantButtonAllowMorePhotos" />
+
+ <com.android.permissioncontroller.permission.ui.widget.SecureButton
+ android:id="@+id/permission_allow_selected_photos_button"
+ android:text="@string/grant_dialog_button_allow_selected_photos"
+ style="@style/PermissionGrantButtonAllowSelectedPhotos" />
+
+ <com.android.permissioncontroller.permission.ui.widget.SecureButton
android:id="@+id/permission_deny_button"
android:text="@string/grant_dialog_button_deny"
style="@style/PermissionGrantButtonDeny" />
<com.android.permissioncontroller.permission.ui.widget.SecureButton
+ android:id="@+id/permission_dont_allow_more_selected_photos_button"
+ android:text="@string/grant_dialog_button_dont_allow_more_selected_photos"
+ style="@style/PermissionGrantButtonDeny" />
+
+ <com.android.permissioncontroller.permission.ui.widget.SecureButton
android:id="@+id/permission_deny_and_dont_ask_again_button"
android:text="@string/grant_dialog_button_deny"
style="@style/PermissionGrantButtonDeny" />
diff --git a/PermissionController/res/layout/grant_permissions_material3.xml b/PermissionController/res/layout/grant_permissions_material3.xml
index 317a33b55..9c3ab6eef 100644
--- a/PermissionController/res/layout/grant_permissions_material3.xml
+++ b/PermissionController/res/layout/grant_permissions_material3.xml
@@ -63,6 +63,30 @@
</LinearLayout>
+ <!-- permission rationale -->
+ <LinearLayout
+ android:id="@+id/permission_rationale_container"
+ style="@style/PermissionGrantPermissionRationaleContent">
+
+ <ImageView
+ android:id="@+id/permission_rationale_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_shield_exclamation_outline"
+ style="@style/PermissionGrantPermissionRationaleIcon" />
+
+ <TextView
+ android:id="@+id/permission_rationale_message"
+ style="@style/PermissionGrantPermissionRationaleMessage" />
+
+ <ImageView
+ android:id="@+id/permission_rationale_more_info_icon"
+ android:importantForAccessibility="no"
+ android:src="@drawable/ic_more_info_arrow"
+ style="@style/PermissionGrantPermissionRationaleMoreInfoIcon" />
+
+ </LinearLayout>
+
+ <!-- location (precise/approximate) animations -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -70,7 +94,7 @@
<RadioGroup
android:id="@+id/permission_location_accuracy_radio_group"
- style="@style/PermissionLocationAccuracyRadioGroup">
+ style="@style/PermissionLocationAccuracyRadioGroupMaterial3">
<RadioButton
android:id="@+id/permission_location_accuracy_radio_fine"
@@ -86,12 +110,12 @@
<ImageView
android:id="@+id/permission_location_accuracy_fine_only"
android:contentDescription="@string/precise_image_description"
- style="@style/PermissionLocationAccuracyFineImageView" />
+ style="@style/PermissionLocationAccuracyFineImageViewMaterial3" />
<ImageView
android:id="@+id/permission_location_accuracy_coarse_only"
android:contentDescription="@string/approximate_image_description"
- style="@style/PermissionLocationAccuracyCoarseImageView" />
+ style="@style/PermissionLocationAccuracyCoarseImageViewMaterial3" />
</LinearLayout>
@@ -118,11 +142,31 @@
style="@style/PermissionGrantButtonAllowOneTimeMaterial3" />
<com.android.permissioncontroller.permission.ui.widget.SecureButton
+ android:id="@+id/permission_allow_all_photos_button"
+ android:text="@string/grant_dialog_button_allow_all_photos"
+ style="@style/PermissionGrantButtonAllowAllPhotosMaterial3" />
+
+ <com.android.permissioncontroller.permission.ui.widget.SecureButton
+ android:id="@+id/permission_allow_more_selected_photos_button"
+ android:text="@string/grant_dialog_button_allow_more_selected_photos"
+ style="@style/PermissionGrantButtonAllowMorePhotosMaterial3" />
+
+ <com.android.permissioncontroller.permission.ui.widget.SecureButton
+ android:id="@+id/permission_allow_selected_photos_button"
+ android:text="@string/grant_dialog_button_allow_selected_photos"
+ style="@style/PermissionGrantButtonAllowSelectedPhotosMaterial3" />
+
+ <com.android.permissioncontroller.permission.ui.widget.SecureButton
android:id="@+id/permission_deny_button"
android:text="@string/grant_dialog_button_deny"
style="@style/PermissionGrantButtonDenyMaterial3" />
<com.android.permissioncontroller.permission.ui.widget.SecureButton
+ android:id="@+id/permission_dont_allow_more_selected_photos_button"
+ android:text="@string/grant_dialog_button_dont_allow_more_selected_photos"
+ style="@style/PermissionGrantButtonDontAllowMorePhotosMaterial3" />
+
+ <com.android.permissioncontroller.permission.ui.widget.SecureButton
android:id="@+id/permission_deny_and_dont_ask_again_button"
android:text="@string/grant_dialog_button_deny"
style="@style/PermissionGrantButtonDenyMaterial3" />
diff --git a/PermissionController/res/navigation/nav_graph.xml b/PermissionController/res/navigation/nav_graph.xml
index cd3ba5980..874e31a87 100644
--- a/PermissionController/res/navigation/nav_graph.xml
+++ b/PermissionController/res/navigation/nav_graph.xml
@@ -186,4 +186,9 @@
app:popExitAnim="@anim/activity_close_exit"
app:popEnterAnim="@anim/activity_open_enter"/>
</fragment>
+
+ <fragment
+ android:id="@+id/app_data_sharing_updates"
+ android:name="com.android.permissioncontroller.permission.ui.handheld.v34.AppDataSharingUpdatesWrapperFragment"
+ android:label="AppDataSharingUpdates"/>
</navigation> \ No newline at end of file
diff --git a/PermissionController/res/raw-night/coarse_loc_off.json b/PermissionController/res/raw-night/coarse_loc_off.json
new file mode 100644
index 000000000..2779a0c64
--- /dev/null
+++ b/PermissionController/res/raw-night/coarse_loc_off.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":91,"w":300,"h":300,"nm":"Location_Accuracy_Approximate_OFF_DT","ddd":0,"assets":[{"id":"comp_0","nm":"Approximate_OFF_DT","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.5,49,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":15,"s":[62,62,100]},{"t":60,"s":[90,90,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Highway_Signs","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.19],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.19],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.584,67.277],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.48,-0.218],[-0.001,3.653],[5.479,-0.218],[5.999,-3.653],[-6,-3.653]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.583,71.205],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.037],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.037],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.129],[0,-1.046],[-2.496,0.129],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.584,65.956],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.19],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.19],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,70.159],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.479,-0.218],[0.001,3.653],[5.48,-0.218],[5.999,-3.653],[-5.999,-3.653]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,74.086],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.037],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.037],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.129],[0,-1.046],[-2.496,0.129],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,68.837],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":4,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.191],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.191],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,222.357],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 13","np":4,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.479,-0.217],[-0.001,3.653],[5.48,-0.217],[5.999,-3.652],[-5.999,-3.652]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,226.283],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 14","np":4,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.038],[0,0],[0.292,-1.238],[0,0]],"o":[[0,0],[-1.358,0.038],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.238]],"v":[[5.065,-1.046],[2.496,0.128],[0,-1.046],[-2.496,0.128],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,221.035],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 15","np":4,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.191],[0,0],[0.041,0.177]],"o":[[-0.042,0.177],[0,0],[-0.03,-0.191],[0,0]],"v":[[-5.894,-0.276],[-6,0.275],[6,0.275],[5.894,-0.276]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.011,186.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 16","np":4,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.192],[0,0],[-1.626,3.1],[0.158,1.028],[0,0]],"o":[[1.626,3.1],[0,0],[0.626,-1.192],[0,0],[-0.158,1.028]],"v":[[-5.479,-0.218],[-0.001,3.652],[5.48,-0.218],[5.999,-3.652],[-5.999,-3.652]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.01,190.467],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 17","np":4,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.036],[0,0],[1.358,0.038],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.038],[0,0],[-1.321,-0.036],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.128],[0,-1.046],[-2.496,0.128],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.011,185.218],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 18","np":4,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".yellow200","cl":"yellow200","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-10.273,-62.792],[10.273,-19.932],[5.308,43.982],[3.529,62.792]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[304.691,67.791],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 19","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-69.845,50.02],[-43.615,20.333],[-43.615,-21.96],[-34.261,-28.874],[-24.095,-28.874],[-16.368,-34.974],[-1.728,-31.72],[1.931,-37.821],[7.218,-34.567],[23.079,-42.293],[55.331,-42.293],[69.845,-50.02]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[373.948,242.314],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 20","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-113.972,-119.335],[-113.972,-89.146],[-110.288,-76.336],[-118.014,-57.426],[-110.288,-39.939],[-93.615,-15.54],[-81.414,-1.306],[-84.668,17.808],[-75.721,29.193],[-63.521,36.921],[-49.288,39.36],[-41.968,48.714],[-28.954,48.714],[-17.568,53.594],[-2.115,58.067],[5.205,65.794],[21.066,68.233],[51.972,97.107],[62.545,99.954],[75.559,99.954],[118.014,119.335]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[245.421,175.3],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 21","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-214.415,-10.167],[-193.269,2.034],[-180.255,2.034],[-172.122,4.88],[-161.548,-0.406],[-149.349,0.001],[-132.675,-7.32],[-126.575,-5.693],[-113.969,-10.167],[-96.075,-8.133],[-88.755,-2.643],[-80.215,-2.643],[-43.208,-2.643],[-22.062,-6.1],[-13.319,0.407],[-0.61,2.034],[17.384,-2.643],[35.278,-0.406],[50.731,2.034],[53.985,6.913],[73.912,4.88],[86.111,10.167],[114.578,10.167],[150.365,10.167],[159.311,4.88],[214.415,2.034]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[223.889,101.607],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 22","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-152.296,-34.933],[-152.296,-17.527],[-140.503,-1.668],[-129.523,3.62],[-129.523,10.94],[-120.577,15.413],[-117.729,22.327],[-88.449,34.933],[-76.251,30.867],[-68.117,23.953],[-38.43,27.207],[-25.417,28.833],[-7.93,24.767],[20.943,25.986],[31.924,29.24],[38.837,25.58],[51.443,25.58],[60.391,21.513],[86.01,18.667],[97.804,26.8],[152.297,26.393]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[304.103,105.308],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 24","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[21.757,-76.408],[21.757,-63.89],[13.214,-63.89],[-14.031,-38.677],[-19.724,-23.223],[-21.757,54.857],[-18.098,63.803],[-21.757,76.409]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,177.698],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 25","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-67.1,-42.533],[-37.413,-29.478],[-34.567,-20.938],[-28.873,-24.191],[-17.08,-23.378],[-5.693,-7.518],[12.2,-3.046],[18.3,5.495],[24.807,9.562],[38.227,11.594],[52.867,19.729],[62.525,21.355],[67.1,29.896],[67.1,42.533]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[160.753,59.919],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 26","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-139.893,-158.396],[-135.827,-143.349],[-95.566,-100.243],[-75.233,-93.736],[-51.646,-67.303],[-48.8,-54.29],[-35.38,-42.496],[-32.94,-22.57],[-24.807,-18.91],[-10.574,-14.844],[8.947,16.47],[58.56,53.477],[82.147,84.79],[102.074,96.99],[125.66,133.997],[139.893,142.536],[139.893,158.396]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[243.307,169.317],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 28","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey300","cl":"grey300","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[31.717,-1.007],[-31.717,1.007]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[287.883,119.11],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 29","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-35.365,-14.212],[-27.159,-14.715],[-22.239,-18.741],[-8.414,-18.741],[6.556,-19.629],[15.02,-18.202],[28.069,-14.212],[28.069,-5.406],[28.069,1.64],[28.069,10.949],[29.704,14.22],[35.239,16.731],[35.365,19.629]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[291.531,123.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 30","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-41.561,-23.371],[-37.444,-22.113],[-31.154,-25.384],[-26.499,-26.767],[-21.215,-28.151],[-18.196,-31.05],[-12.41,-31.528],[-8.761,-31.05],[-7.628,-28.906],[9.733,-22.113],[23.067,-5.129],[29.106,-0.14],[37.283,12.608],[40.931,21.004],[41.561,31.528]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[348.112,171.29],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 31","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[4.64,-38.155],[4.64,-8.29],[7.771,-2.101],[6.974,5.21],[2.462,11.432],[-0.966,22.989],[-7.771,38.155]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[251.527,139.762],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 32","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-19.621,11.458],[-23.788,-11.458],[23.788,-11.458]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[259.299,233.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 33","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-36.573,-3.452],[3.542,-3.452],[8.492,-1.858],[25.051,-3.452],[31.026,-2.655],[35.293,0.133],[36.573,2.352],[34.013,2.352],[32.887,3.451]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[297.973,269.1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 34","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-25.691,-18.223],[-10.328,-18.734],[-5.207,-17.88],[1.281,-18.307],[18.009,-19.077],[25.691,-12.504],[22.789,-10.797],[14.168,0.47],[12.376,5.846],[8.45,13.615],[4.011,19.077]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[307.575,287.115],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 35","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[50.742,46.448],[51.669,43.468],[50.742,37.157],[55.148,32.302],[72.276,31.185],[72.276,5.64],[70.623,-1.526],[70.623,-40.523],[69.383,-42.865],[62.906,-43.554],[56.843,-42.865],[54.224,-44.381],[49.264,-44.381],[45.13,-46.448],[-72.276,-46.448]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[226.967,241.72],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 36","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-119.509,-44.611],[-97.824,-40.96],[-89.824,-34.688],[-72.741,-36.266],[-66.754,-33.154],[40.537,-34.971],[59.633,-38.882],[68.047,-38.882],[73.387,-37.306],[74.626,-33.154],[77.919,-31.804],[77.919,-26.232],[80.508,-24.928],[89.57,-21.215],[97.823,-16.038],[105.684,-14.257],[108.827,-8.431],[113.844,5.663],[119.508,8.884],[117.654,19.078],[110.284,25.092],[107.048,37.527],[104.562,44.612]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[278.637,267.243],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 37","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-51.352,2.509],[13.052,3.155],[35.386,-3.155],[51.352,-3.155]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[301.911,213.904],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 38","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[275.481,202.819],[275.481,222.082]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 39","np":2,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.805,-24.131],[-0.446,-14.804],[1.18,18.209],[-2.805,24.131]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[269.292,219.403],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 40","np":2,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-16.325,-7.523],[-4.713,-0.839],[5.806,-0.839],[16.325,7.523]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[261.745,182.35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 41","np":2,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-52.188,-19.67],[-41.346,-24.51],[-9.628,-12.712],[5.098,-9.312],[20.956,-3.564],[26.135,-4.62],[34.719,-0.671],[41.671,4.326],[52.189,7.729],[46.039,15.452],[46.202,24.509]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[213.259,163.422],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 42","np":2,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[73.548,-6.19],[75.996,7.565],[69.525,24.299],[59.976,26.175],[53.504,28.441],[47.03,33.188],[47.03,39.488],[34.894,32.698],[26.767,21.986],[17.423,17.347],[-1.49,12.605],[-28.757,3.71],[-37.055,-16.252],[-66.354,-16.252],[-66.354,-18.782],[-69.801,-23.38],[-68.077,-26.092],[-70.654,-30.31],[-74.309,-30.31],[-75.996,-39.488]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[230.554,138.453],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 43","np":2,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-68.814,-67.689],[-33.186,-67.127],[-13.566,-64.287],[9.77,-52.647],[17.081,-46.04],[58.129,-44.971],[60.347,-29.873],[60.347,-12.806],[61.785,6.876],[59.395,9.068],[60.347,54.334],[65.44,54.334],[68.814,58.411],[63.612,63.472],[65.581,64.877],[63.612,67.689]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[243.756,235.438],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 44","np":2,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-6.636,-10.36],[-2.642,-10.95],[4.106,-13.762],[4.478,-12.242],[5.933,-6.307],[6.636,13.761]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[170.836,181.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 45","np":2,"cix":2,"bm":0,"ix":17,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[150.903,178.874],[176.77,178.874]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 46","np":2,"cix":2,"bm":0,"ix":18,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-46.11,-24.495],[-24.601,-24.495],[-18.978,-22.285],[10.012,-24.495],[26.288,-21.262],[39.924,-19.433],[46.11,-14.795],[41.028,24.495]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[194.482,197.586],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 47","np":2,"cix":2,"bm":0,"ix":19,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[163.403,74.086],[163.403,98.964]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 48","np":2,"cix":2,"bm":0,"ix":20,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.298,-13.017],[-2.298,-7.952],[0.038,-1.57],[0.038,13.017]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[138.776,74.953],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 49","np":2,"cix":2,"bm":0,"ix":21,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-25.094,-4.83],[-14.691,-5.955],[-13.004,-4.127],[-6.116,-5.041],[25.094,-5.041],[25.094,5.955]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[113.72,93.01],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 50","np":2,"cix":2,"bm":0,"ix":22,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-21.799,-26.171],[-21.799,13.635],[-20.774,20.008],[16.176,26.171],[21.799,24.503]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[143.443,79.138],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 51","np":2,"cix":2,"bm":0,"ix":23,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[88.626,74.857],[151.806,74.857]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 52","np":2,"cix":2,"bm":0,"ix":24,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[93.653,52.401],[93.653,94.366]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 53","np":2,"cix":2,"bm":0,"ix":25,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.723,-35.415],[-1.723,-14.469],[0.62,-12.953],[1.723,35.415]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[162.477,159.858],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 54","np":2,"cix":2,"bm":0,"ix":26,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-11.575,3.259],[-0.551,-3.259],[4.821,-3.259],[7.166,-1.191],[9.784,-1.191],[11.575,-3.259]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[155.932,166.01],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 55","np":2,"cix":2,"bm":0,"ix":27,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.489,-23.839],[-6.05,-13.917],[-7.841,-12.401],[-7.841,1.329],[-11.562,10.887],[-11.562,23.84],[11.562,23.84]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.855000035903,0.862999949736,0.877999997606,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[179.069,138.912],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 56","np":2,"cix":2,"bm":0,"ix":28,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey700","cl":"grey700","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-84.172,-14.163],[-87.624,-10.786],[-87.624,-7.25],[-91.245,-3.685],[-94.866,-5.154],[-98.486,-11.792],[-101.705,-10.384],[-105.729,-11.792],[-110.154,-14.163],[-116.994,-11.792],[-121.218,-5.355],[-119.247,-2.337],[-117.598,-2.337],[-116.19,1.083],[-110.959,2.286],[-107.137,3.899],[-97.49,3.899],[-97.49,10.725],[-88.428,11.544],[-86.819,19.59],[-68.714,22.205],[-62.64,26.027],[-55.235,33.068],[-45.78,31.79],[-35.723,31.79],[-22.848,31.79],[-17.818,34.678],[-3.957,38.5],[5.315,37.695],[13.563,34.678],[23.622,33.873],[46.757,36.891],[58.826,36.891],[67.678,33.068],[75.14,30.654],[87.996,32.666],[90.209,31.79],[108.313,29.649],[117.768,25.022],[130.243,25.424],[144.523,25.022],[148.34,20.596],[149.955,15.969],[148.34,10.725],[127.193,-42.264],[-25.865,-48.73],[-69.318,-44.784],[-72.134,-37.519],[-82.394,-31.305]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.376470588235,0.388235294118,0.407843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[308.06,59.651],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 57","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-53.521,-13.307],[-41.048,-20.863],[-32.801,-18.738],[-26.967,-20.863],[-15.902,-15.576],[-11.075,-15.576],[-11.075,-13.307],[4.818,-13.307],[6.83,-15.576],[17.29,-15.576],[20.308,-23.708],[24.532,-22.041],[35.194,-20.863],[45.454,-19.945],[47.264,-15.576],[52.695,-6.064],[46.459,23.708],[42.881,20.101],[34.646,17.883],[-4.626,17.883],[-13.177,20.101],[-23.946,20.101],[-30.387,15.26],[-38.835,16.264],[-49.296,17.271],[-56.009,14.456]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.376470588235,0.388235294118,0.407843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[69.974,127.35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 61","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey800","cl":"grey800","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-2.851,0.95],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[2.851,-0.95],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-209.239,-23.701],[-197.204,-12.299],[-193.403,-15.149],[-193.403,-18.95],[-181.051,-21.801],[-177.251,-23.701],[-166.482,-23.701],[-157.931,-25.918],[-154.447,-25.918],[-135.761,-25.918],[-131.327,-25.918],[-124.36,-25.918],[-118.658,-25.918],[-110.424,-23.701],[-104.406,-19.108],[-98.389,-12.299],[-88.254,0.49],[-84.77,0],[-84.77,-6.282],[-81.92,-1.531],[-83.351,0],[-84.736,0.49],[-86.024,1.81],[-86.644,2.239],[-81.92,12.262],[-74.427,29.3],[-73.52,32.545],[-69.607,41.517],[-69.607,44.142],[-68.7,45.383],[-66.791,56.121],[-66.791,60.226],[-68.748,61.371],[-68.748,62.564],[-70.322,63.995],[-70.37,65.523],[-72.327,67.146],[-73.615,67.146],[-74.14,68.195],[-75.477,68.195],[-75.477,69.389],[-76.908,70.439],[-77.481,71.059],[-77.481,72.014],[-78.293,72.872],[-76.813,73.445],[-76.67,74.877],[-75.572,75.402],[-75.668,77.788],[-74.904,77.788],[-74.904,81.367],[-74.093,81.367],[-73.854,83.611],[-72.947,84.231],[-72.947,85.52],[-71.754,86.331],[-69.655,86.331],[-64.643,86.331],[-64.023,85.472],[-61.302,85.902],[-61.064,86.331],[-57.485,86.665],[-56.005,88.287],[-56.005,89.815],[-54.287,90.912],[-53.428,90.244],[-52.998,90.912],[-51.614,91.199],[-51.614,92.249],[-49.133,92.249],[-48.751,93.299],[-46.651,93.299],[-45.935,94.253],[-45.076,94.253],[-42.833,96.688],[-40.256,96.688],[-40.256,97.498],[-38.108,97.498],[-37.678,98.501],[-35.817,98.501],[-34.815,99.169],[-34.004,99.169],[-33.622,98.453],[-31.14,98.453],[-31.427,97.642],[-31.904,96.782],[-33.431,96.735],[-33.622,91.58],[-33.097,91.58],[-32.763,92.583],[-31.713,92.44],[-31.14,92.965],[-29.279,92.869],[-29.279,91.676],[-30.138,91.342],[-30.043,89.385],[-30.902,88.669],[-30.949,87.237],[-31.904,86.808],[-30.997,86.092],[-30.854,84.66],[-30.138,84.374],[-30.091,80.556],[-31.045,79.984],[-30.759,76.691],[-29.47,78.313],[-29.279,79.268],[-27.37,80.222],[-26.559,79.888],[-25.89,78.934],[-25.604,77.884],[-24.459,76.357],[-21.882,75.402],[-17.586,74.973],[-14.771,74.734],[-14.771,75.831],[-22.072,77.74],[-23.361,78.122],[-24.268,78.6],[-25.127,79.124],[-25.509,80.174],[-27.036,81.272],[-28.181,81.749],[-29.088,83.229],[-29.088,85.997],[-28.849,87.953],[-27.752,88.287],[-26.177,89.385],[-24.173,88.097],[-23.648,88.097],[-22.502,87.143],[-22.74,86.331],[-23.027,83.372],[-22.12,83.133],[-21.548,83.897],[-21.5,84.518],[-20.88,84.66],[-20.88,85.902],[-20.02,86.188],[-19.734,85.328],[-16.918,85.568],[-16.966,84.708],[-19.114,82.895],[-19.591,82.035],[-19.495,81.272],[-16.441,81.415],[-16.107,80.509],[-13.72,80.509],[-12.862,79.076],[-11.239,79.984],[-10.809,83.849],[-9.473,83.897],[-8.996,84.566],[-6.848,84.852],[-6.085,85.949],[-3.364,86.236],[-3.078,84.756],[-3.698,81.701],[-6.037,79.554],[-6.323,78.504],[-3.603,78.456],[-2.219,78.027],[-0.406,77.359],[0.549,77.74],[2.84,77.645],[4.94,77.645],[6.133,78.027],[8.567,78.6],[9.14,79.458],[11.096,79.458],[11.43,80.413],[13.149,80.413],[13.435,80.986],[14.628,81.033],[14.628,80.413],[15.868,79.936],[15.868,78.934],[16.346,81.272],[18.064,81.32],[18.064,80.222],[17.539,80.174],[17.539,78.074],[18.446,78.552],[19.973,80.031],[21.452,80.079],[21.452,82.704],[20.976,83.42],[20.976,84.518],[19.782,84.804],[19.83,85.71],[22.026,86.188],[22.884,86.999],[24.698,87.047],[25.414,87.953],[27.275,87.953],[30.855,87.953],[31.523,86.999],[32.764,87.047],[32.286,88.335],[31.761,88.956],[32.048,90.436],[36.915,90.818],[37.583,98.787],[39.97,102.366],[42.738,103.368],[44.552,106.185],[54.431,117.018],[58.582,120.216],[67.889,127.518],[73.711,130.429],[85.165,134.294],[88.458,136.013],[91.179,136.013],[97.717,142.026],[98.338,144.89],[104.494,147.849],[107.262,150],[211.975,150],[211.975,-151.69],[-210.962,-153.764]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[223.279,171.151],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 62","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey900","cl":"grey900","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-206,148.684],[206,148.684],[206,-148.684],[-206,-148.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[223.279,172.467],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 63","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[101.086,101.086,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.66,0.66],"y":[0,0]},"t":0,"s":[294,294]},{"t":10,"s":[300,300]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.66],"y":[0]},"t":0,"s":[6]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[98.741,98.741],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[64.361,64.36,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":0,"s":[150,150,100]},{"t":45,"s":[48.034,48.034,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-34.164],[34.165,0],[0,34.165],[-34.165,0]],"o":[[0,34.165],[-34.165,0],[0,-34.164],[34.165,0]],"v":[[61.861,-0.001],[0,61.861],[-61.861,-0.001],[0,-61.861]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.829000016755,0.885999971278,0.955999995213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[100]},{"t":32,"s":[70]}],"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[64.361,64.361],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-34.164],[34.165,0],[0,34.165],[-34.165,0]],"o":[[0,34.165],[-34.165,0],[0,-34.164],[34.165,0]],"v":[[61.861,-0.001],[0,61.861],[-61.861,-0.001],[0,-61.861]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.552941203117,0.670588254929,0.921568632126,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[50]},{"t":32,"s":[20]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[64.361,64.361],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":585,"st":-15,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"CircleMatte 2","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[82.705,0],[0,-82.705],[-82.705,0],[0,82.705]],"o":[[-82.705,0],[0,82.705],[82.705,0],[0,-82.705]],"v":[[-0.125,-149.938],[-149.875,-0.188],[-0.125,149.562],[149.625,-0.188]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":-1,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[15.4,0],[0,0],[0,15.4],[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0]],"o":[[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0],[15.4,0],[0,0],[0,15.4]],"v":[[228,150],[-238,150],[-266,122],[-266,-122],[-238,-150],[228,-150],[256,-122],[256,122]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Approximate_OFF_DT","tt":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":0,"k":[307.16,307.16,100],"ix":6,"l":2}},"ao":0,"w":117,"h":98,"ip":0,"op":585,"st":-15,"bm":0}],"markers":[{"tm":91,"cm":"","dr":0}]} \ No newline at end of file
diff --git a/PermissionController/res/raw-night/coarse_loc_on.json b/PermissionController/res/raw-night/coarse_loc_on.json
new file mode 100644
index 000000000..b6f098d30
--- /dev/null
+++ b/PermissionController/res/raw-night/coarse_loc_on.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":91,"w":300,"h":300,"nm":"Location_Accuracy_Approximate_ON_DT","ddd":0,"assets":[{"id":"comp_0","nm":"Approximate_ON_DT","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.5,49,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":15,"s":[90,90,100]},{"t":60,"s":[62,62,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Highway_Signs","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.19],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.19],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.584,67.277],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.48,-0.218],[-0.001,3.653],[5.479,-0.218],[5.999,-3.653],[-6,-3.653]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.583,71.205],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.037],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.037],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.129],[0,-1.046],[-2.496,0.129],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.584,65.956],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.19],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.19],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,70.159],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.479,-0.218],[0.001,3.653],[5.48,-0.218],[5.999,-3.653],[-5.999,-3.653]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,74.086],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.037],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.037],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.129],[0,-1.046],[-2.496,0.129],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,68.837],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":4,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.191],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.191],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,222.357],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 13","np":4,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.479,-0.217],[-0.001,3.653],[5.48,-0.217],[5.999,-3.652],[-5.999,-3.652]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,226.283],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 14","np":4,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.038],[0,0],[0.292,-1.238],[0,0]],"o":[[0,0],[-1.358,0.038],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.238]],"v":[[5.065,-1.046],[2.496,0.128],[0,-1.046],[-2.496,0.128],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,221.035],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 15","np":4,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.191],[0,0],[0.041,0.177]],"o":[[-0.042,0.177],[0,0],[-0.03,-0.191],[0,0]],"v":[[-5.894,-0.276],[-6,0.275],[6,0.275],[5.894,-0.276]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.011,186.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 16","np":4,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.192],[0,0],[-1.626,3.1],[0.158,1.028],[0,0]],"o":[[1.626,3.1],[0,0],[0.626,-1.192],[0,0],[-0.158,1.028]],"v":[[-5.479,-0.218],[-0.001,3.652],[5.48,-0.218],[5.999,-3.652],[-5.999,-3.652]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.01,190.467],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 17","np":4,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.036],[0,0],[1.358,0.038],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.038],[0,0],[-1.321,-0.036],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.128],[0,-1.046],[-2.496,0.128],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.011,185.218],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 18","np":4,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".yellow200","cl":"yellow200","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-10.273,-62.792],[10.273,-19.932],[5.308,43.982],[3.529,62.792]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[304.691,67.791],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 19","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-69.845,50.02],[-43.615,20.333],[-43.615,-21.96],[-34.261,-28.874],[-24.095,-28.874],[-16.368,-34.974],[-1.728,-31.72],[1.931,-37.821],[7.218,-34.567],[23.079,-42.293],[55.331,-42.293],[69.845,-50.02]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[373.948,242.314],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 20","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-113.972,-119.335],[-113.972,-89.146],[-110.288,-76.336],[-118.014,-57.426],[-110.288,-39.939],[-93.615,-15.54],[-81.414,-1.306],[-84.668,17.808],[-75.721,29.193],[-63.521,36.921],[-49.288,39.36],[-41.968,48.714],[-28.954,48.714],[-17.568,53.594],[-2.115,58.067],[5.205,65.794],[21.066,68.233],[51.972,97.107],[62.545,99.954],[75.559,99.954],[118.014,119.335]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[245.421,175.3],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 21","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-214.415,-10.167],[-193.269,2.034],[-180.255,2.034],[-172.122,4.88],[-161.548,-0.406],[-149.349,0.001],[-132.675,-7.32],[-126.575,-5.693],[-113.969,-10.167],[-96.075,-8.133],[-88.755,-2.643],[-80.215,-2.643],[-43.208,-2.643],[-22.062,-6.1],[-13.319,0.407],[-0.61,2.034],[17.384,-2.643],[35.278,-0.406],[50.731,2.034],[53.985,6.913],[73.912,4.88],[86.111,10.167],[114.578,10.167],[150.365,10.167],[159.311,4.88],[214.415,2.034]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[223.889,101.607],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 22","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-152.296,-34.933],[-152.296,-17.527],[-140.503,-1.668],[-129.523,3.62],[-129.523,10.94],[-120.577,15.413],[-117.729,22.327],[-88.449,34.933],[-76.251,30.867],[-68.117,23.953],[-38.43,27.207],[-25.417,28.833],[-7.93,24.767],[20.943,25.986],[31.924,29.24],[38.837,25.58],[51.443,25.58],[60.391,21.513],[86.01,18.667],[97.804,26.8],[152.297,26.393]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[304.103,105.308],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 24","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[21.757,-76.408],[21.757,-63.89],[13.214,-63.89],[-14.031,-38.677],[-19.724,-23.223],[-21.757,54.857],[-18.098,63.803],[-21.757,76.409]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,177.698],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 25","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-67.1,-42.533],[-37.413,-29.478],[-34.567,-20.938],[-28.873,-24.191],[-17.08,-23.378],[-5.693,-7.518],[12.2,-3.046],[18.3,5.495],[24.807,9.562],[38.227,11.594],[52.867,19.729],[62.525,21.355],[67.1,29.896],[67.1,42.533]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[160.753,59.919],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 26","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-139.893,-158.396],[-135.827,-143.349],[-95.566,-100.243],[-75.233,-93.736],[-51.646,-67.303],[-48.8,-54.29],[-35.38,-42.496],[-32.94,-22.57],[-24.807,-18.91],[-10.574,-14.844],[8.947,16.47],[58.56,53.477],[82.147,84.79],[102.074,96.99],[125.66,133.997],[139.893,142.536],[139.893,158.396]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.992156862745,0.886274509804,0.576470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[243.307,169.317],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 28","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey300","cl":"grey300","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[31.717,-1.007],[-31.717,1.007]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[287.883,119.11],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 29","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-35.365,-14.212],[-27.159,-14.715],[-22.239,-18.741],[-8.414,-18.741],[6.556,-19.629],[15.02,-18.202],[28.069,-14.212],[28.069,-5.406],[28.069,1.64],[28.069,10.949],[29.704,14.22],[35.239,16.731],[35.365,19.629]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[291.531,123.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 30","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-41.561,-23.371],[-37.444,-22.113],[-31.154,-25.384],[-26.499,-26.767],[-21.215,-28.151],[-18.196,-31.05],[-12.41,-31.528],[-8.761,-31.05],[-7.628,-28.906],[9.733,-22.113],[23.067,-5.129],[29.106,-0.14],[37.283,12.608],[40.931,21.004],[41.561,31.528]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[348.112,171.29],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 31","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[4.64,-38.155],[4.64,-8.29],[7.771,-2.101],[6.974,5.21],[2.462,11.432],[-0.966,22.989],[-7.771,38.155]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[251.527,139.762],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 32","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-19.621,11.458],[-23.788,-11.458],[23.788,-11.458]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[259.299,233.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 33","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-36.573,-3.452],[3.542,-3.452],[8.492,-1.858],[25.051,-3.452],[31.026,-2.655],[35.293,0.133],[36.573,2.352],[34.013,2.352],[32.887,3.451]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[297.973,269.1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 34","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-25.691,-18.223],[-10.328,-18.734],[-5.207,-17.88],[1.281,-18.307],[18.009,-19.077],[25.691,-12.504],[22.789,-10.797],[14.168,0.47],[12.376,5.846],[8.45,13.615],[4.011,19.077]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[307.575,287.115],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 35","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[50.742,46.448],[51.669,43.468],[50.742,37.157],[55.148,32.302],[72.276,31.185],[72.276,5.64],[70.623,-1.526],[70.623,-40.523],[69.383,-42.865],[62.906,-43.554],[56.843,-42.865],[54.224,-44.381],[49.264,-44.381],[45.13,-46.448],[-72.276,-46.448]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[226.967,241.72],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 36","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-119.509,-44.611],[-97.824,-40.96],[-89.824,-34.688],[-72.741,-36.266],[-66.754,-33.154],[40.537,-34.971],[59.633,-38.882],[68.047,-38.882],[73.387,-37.306],[74.626,-33.154],[77.919,-31.804],[77.919,-26.232],[80.508,-24.928],[89.57,-21.215],[97.823,-16.038],[105.684,-14.257],[108.827,-8.431],[113.844,5.663],[119.508,8.884],[117.654,19.078],[110.284,25.092],[107.048,37.527],[104.562,44.612]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[278.637,267.243],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 37","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-51.352,2.509],[13.052,3.155],[35.386,-3.155],[51.352,-3.155]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[301.911,213.904],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 38","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[275.481,202.819],[275.481,222.082]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 39","np":2,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.805,-24.131],[-0.446,-14.804],[1.18,18.209],[-2.805,24.131]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[269.292,219.403],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 40","np":2,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-16.325,-7.523],[-4.713,-0.839],[5.806,-0.839],[16.325,7.523]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[261.745,182.35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 41","np":2,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-52.188,-19.67],[-41.346,-24.51],[-9.628,-12.712],[5.098,-9.312],[20.956,-3.564],[26.135,-4.62],[34.719,-0.671],[41.671,4.326],[52.189,7.729],[46.039,15.452],[46.202,24.509]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[213.259,163.422],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 42","np":2,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[73.548,-6.19],[75.996,7.565],[69.525,24.299],[59.976,26.175],[53.504,28.441],[47.03,33.188],[47.03,39.488],[34.894,32.698],[26.767,21.986],[17.423,17.347],[-1.49,12.605],[-28.757,3.71],[-37.055,-16.252],[-66.354,-16.252],[-66.354,-18.782],[-69.801,-23.38],[-68.077,-26.092],[-70.654,-30.31],[-74.309,-30.31],[-75.996,-39.488]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[230.554,138.453],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 43","np":2,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-68.814,-67.689],[-33.186,-67.127],[-13.566,-64.287],[9.77,-52.647],[17.081,-46.04],[58.129,-44.971],[60.347,-29.873],[60.347,-12.806],[61.785,6.876],[59.395,9.068],[60.347,54.334],[65.44,54.334],[68.814,58.411],[63.612,63.472],[65.581,64.877],[63.612,67.689]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[243.756,235.438],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 44","np":2,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-6.636,-10.36],[-2.642,-10.95],[4.106,-13.762],[4.478,-12.242],[5.933,-6.307],[6.636,13.761]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[170.836,181.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 45","np":2,"cix":2,"bm":0,"ix":17,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[150.903,178.874],[176.77,178.874]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 46","np":2,"cix":2,"bm":0,"ix":18,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-46.11,-24.495],[-24.601,-24.495],[-18.978,-22.285],[10.012,-24.495],[26.288,-21.262],[39.924,-19.433],[46.11,-14.795],[41.028,24.495]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[194.482,197.586],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 47","np":2,"cix":2,"bm":0,"ix":19,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[163.403,74.086],[163.403,98.964]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 48","np":2,"cix":2,"bm":0,"ix":20,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.298,-13.017],[-2.298,-7.952],[0.038,-1.57],[0.038,13.017]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[138.776,74.953],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 49","np":2,"cix":2,"bm":0,"ix":21,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-25.094,-4.83],[-14.691,-5.955],[-13.004,-4.127],[-6.116,-5.041],[25.094,-5.041],[25.094,5.955]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[113.72,93.01],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 50","np":2,"cix":2,"bm":0,"ix":22,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-21.799,-26.171],[-21.799,13.635],[-20.774,20.008],[16.176,26.171],[21.799,24.503]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[143.443,79.138],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 51","np":2,"cix":2,"bm":0,"ix":23,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[88.626,74.857],[151.806,74.857]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 52","np":2,"cix":2,"bm":0,"ix":24,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[93.653,52.401],[93.653,94.366]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 53","np":2,"cix":2,"bm":0,"ix":25,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.723,-35.415],[-1.723,-14.469],[0.62,-12.953],[1.723,35.415]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[162.477,159.858],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 54","np":2,"cix":2,"bm":0,"ix":26,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-11.575,3.259],[-0.551,-3.259],[4.821,-3.259],[7.166,-1.191],[9.784,-1.191],[11.575,-3.259]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[155.932,166.01],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 55","np":2,"cix":2,"bm":0,"ix":27,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.489,-23.839],[-6.05,-13.917],[-7.841,-12.401],[-7.841,1.329],[-11.562,10.887],[-11.562,23.84],[11.562,23.84]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[179.069,138.912],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 56","np":2,"cix":2,"bm":0,"ix":28,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey700","cl":"grey700","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-84.172,-14.163],[-87.624,-10.786],[-87.624,-7.25],[-91.245,-3.685],[-94.866,-5.154],[-98.486,-11.792],[-101.705,-10.384],[-105.729,-11.792],[-110.154,-14.163],[-116.994,-11.792],[-121.218,-5.355],[-119.247,-2.337],[-117.598,-2.337],[-116.19,1.083],[-110.959,2.286],[-107.137,3.899],[-97.49,3.899],[-97.49,10.725],[-88.428,11.544],[-86.819,19.59],[-68.714,22.205],[-62.64,26.027],[-55.235,33.068],[-45.78,31.79],[-35.723,31.79],[-22.848,31.79],[-17.818,34.678],[-3.957,38.5],[5.315,37.695],[13.563,34.678],[23.622,33.873],[46.757,36.891],[58.826,36.891],[67.678,33.068],[75.14,30.654],[87.996,32.666],[90.209,31.79],[108.313,29.649],[117.768,25.022],[130.243,25.424],[144.523,25.022],[148.34,20.596],[149.955,15.969],[148.34,10.725],[127.193,-42.264],[-25.865,-48.73],[-69.318,-44.784],[-72.134,-37.519],[-82.394,-31.305]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.376470588235,0.388235294118,0.407843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[308.06,59.651],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 57","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-53.521,-13.307],[-41.048,-20.863],[-32.801,-18.738],[-26.967,-20.863],[-15.902,-15.576],[-11.075,-15.576],[-11.075,-13.307],[4.818,-13.307],[6.83,-15.576],[17.29,-15.576],[20.308,-23.708],[24.532,-22.041],[35.194,-20.863],[45.454,-19.945],[47.264,-15.576],[52.695,-6.064],[46.459,23.708],[42.881,20.101],[34.646,17.883],[-4.626,17.883],[-13.177,20.101],[-23.946,20.101],[-30.387,15.26],[-38.835,16.264],[-49.296,17.271],[-56.009,14.456]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.376470588235,0.388235294118,0.407843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[69.974,127.35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 61","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey800","cl":"grey800","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-2.851,0.95],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[2.851,-0.95],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-209.239,-23.701],[-197.204,-12.299],[-193.403,-15.149],[-193.403,-18.95],[-181.051,-21.801],[-177.251,-23.701],[-166.482,-23.701],[-157.931,-25.918],[-154.447,-25.918],[-135.761,-25.918],[-131.327,-25.918],[-124.36,-25.918],[-118.658,-25.918],[-110.424,-23.701],[-104.406,-19.108],[-98.389,-12.299],[-88.254,0.49],[-84.77,0],[-84.77,-6.282],[-81.92,-1.531],[-83.351,0],[-84.736,0.49],[-86.024,1.81],[-86.644,2.239],[-81.92,12.262],[-74.427,29.3],[-73.52,32.545],[-69.607,41.517],[-69.607,44.142],[-68.7,45.383],[-66.791,56.121],[-66.791,60.226],[-68.748,61.371],[-68.748,62.564],[-70.322,63.995],[-70.37,65.523],[-72.327,67.146],[-73.615,67.146],[-74.14,68.195],[-75.477,68.195],[-75.477,69.389],[-76.908,70.439],[-77.481,71.059],[-77.481,72.014],[-78.293,72.872],[-76.813,73.445],[-76.67,74.877],[-75.572,75.402],[-75.668,77.788],[-74.904,77.788],[-74.904,81.367],[-74.093,81.367],[-73.854,83.611],[-72.947,84.231],[-72.947,85.52],[-71.754,86.331],[-69.655,86.331],[-64.643,86.331],[-64.023,85.472],[-61.302,85.902],[-61.064,86.331],[-57.485,86.665],[-56.005,88.287],[-56.005,89.815],[-54.287,90.912],[-53.428,90.244],[-52.998,90.912],[-51.614,91.199],[-51.614,92.249],[-49.133,92.249],[-48.751,93.299],[-46.651,93.299],[-45.935,94.253],[-45.076,94.253],[-42.833,96.688],[-40.256,96.688],[-40.256,97.498],[-38.108,97.498],[-37.678,98.501],[-35.817,98.501],[-34.815,99.169],[-34.004,99.169],[-33.622,98.453],[-31.14,98.453],[-31.427,97.642],[-31.904,96.782],[-33.431,96.735],[-33.622,91.58],[-33.097,91.58],[-32.763,92.583],[-31.713,92.44],[-31.14,92.965],[-29.279,92.869],[-29.279,91.676],[-30.138,91.342],[-30.043,89.385],[-30.902,88.669],[-30.949,87.237],[-31.904,86.808],[-30.997,86.092],[-30.854,84.66],[-30.138,84.374],[-30.091,80.556],[-31.045,79.984],[-30.759,76.691],[-29.47,78.313],[-29.279,79.268],[-27.37,80.222],[-26.559,79.888],[-25.89,78.934],[-25.604,77.884],[-24.459,76.357],[-21.882,75.402],[-17.586,74.973],[-14.771,74.734],[-14.771,75.831],[-22.072,77.74],[-23.361,78.122],[-24.268,78.6],[-25.127,79.124],[-25.509,80.174],[-27.036,81.272],[-28.181,81.749],[-29.088,83.229],[-29.088,85.997],[-28.849,87.953],[-27.752,88.287],[-26.177,89.385],[-24.173,88.097],[-23.648,88.097],[-22.502,87.143],[-22.74,86.331],[-23.027,83.372],[-22.12,83.133],[-21.548,83.897],[-21.5,84.518],[-20.88,84.66],[-20.88,85.902],[-20.02,86.188],[-19.734,85.328],[-16.918,85.568],[-16.966,84.708],[-19.114,82.895],[-19.591,82.035],[-19.495,81.272],[-16.441,81.415],[-16.107,80.509],[-13.72,80.509],[-12.862,79.076],[-11.239,79.984],[-10.809,83.849],[-9.473,83.897],[-8.996,84.566],[-6.848,84.852],[-6.085,85.949],[-3.364,86.236],[-3.078,84.756],[-3.698,81.701],[-6.037,79.554],[-6.323,78.504],[-3.603,78.456],[-2.219,78.027],[-0.406,77.359],[0.549,77.74],[2.84,77.645],[4.94,77.645],[6.133,78.027],[8.567,78.6],[9.14,79.458],[11.096,79.458],[11.43,80.413],[13.149,80.413],[13.435,80.986],[14.628,81.033],[14.628,80.413],[15.868,79.936],[15.868,78.934],[16.346,81.272],[18.064,81.32],[18.064,80.222],[17.539,80.174],[17.539,78.074],[18.446,78.552],[19.973,80.031],[21.452,80.079],[21.452,82.704],[20.976,83.42],[20.976,84.518],[19.782,84.804],[19.83,85.71],[22.026,86.188],[22.884,86.999],[24.698,87.047],[25.414,87.953],[27.275,87.953],[30.855,87.953],[31.523,86.999],[32.764,87.047],[32.286,88.335],[31.761,88.956],[32.048,90.436],[36.915,90.818],[37.583,98.787],[39.97,102.366],[42.738,103.368],[44.552,106.185],[54.431,117.018],[58.582,120.216],[67.889,127.518],[73.711,130.429],[85.165,134.294],[88.458,136.013],[91.179,136.013],[97.717,142.026],[98.338,144.89],[104.494,147.849],[107.262,150],[211.975,150],[211.975,-151.69],[-210.962,-153.764]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.235294117647,0.250980392157,0.262745098039,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[223.279,171.151],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 62","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".grey900","cl":"grey900","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-206,148.684],[206,148.684],[206,-148.684],[-206,-148.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.125490196078,0.129411764706,0.141176470588,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[223.279,172.467],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 63","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[101.086,101.086,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.34,0.34],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":10,"s":[300,300]},{"t":25,"s":[294,294]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.552941203117,0.670588254929,0.921568632126,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":25,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[98.741,98.741],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Pale Orange Solid 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Blue Circle Outlines","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[64.361,64.36,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":0,"s":[66.714,66.714,100]},{"t":45,"s":[208.333,208.333,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-34.164],[34.165,0],[0,34.165],[-34.165,0]],"o":[[0,34.165],[-34.165,0],[0,-34.164],[34.165,0]],"v":[[61.861,-0.001],[0,61.861],[-61.861,-0.001],[0,-61.861]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.829000016755,0.885999971278,0.955999995213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[70]},{"t":21,"s":[100]}],"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[64.361,64.361],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-34.164],[34.165,0],[0,34.165],[-34.165,0]],"o":[[0,34.165],[-34.165,0],[0,-34.164],[34.165,0]],"v":[[61.861,-0.001],[0,61.861],[-61.861,-0.001],[0,-61.861]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0,0,0,1]},{"t":21,"s":[0.552941203117,0.670588254929,0.921568632126,1]}],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[20]},{"t":21,"s":[50]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[64.361,64.361],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":585,"st":-15,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"CircleMatte 4","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[82.705,0],[0,-82.705],[-82.705,0],[0,82.705]],"o":[[-82.705,0],[0,82.705],[82.705,0],[0,-82.705]],"v":[[-0.125,-149.938],[-149.875,-0.188],[-0.125,149.562],[149.625,-0.188]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":-1,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[15.4,0],[0,0],[0,15.4],[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0]],"o":[[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0],[15.4,0],[0,0],[0,15.4]],"v":[[228,150],[-238,150],[-266,122],[-266,-122],[-238,-150],[228,-150],[256,-122],[256,122]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Approximate_ON_DT","tt":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":0,"k":[307.2,307.2,100],"ix":6,"l":2}},"ao":0,"w":117,"h":98,"ip":0,"op":585,"st":-15,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/PermissionController/res/raw-night/fine_loc_off.json b/PermissionController/res/raw-night/fine_loc_off.json
new file mode 100644
index 000000000..559cafa58
--- /dev/null
+++ b/PermissionController/res/raw-night/fine_loc_off.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":91,"w":300,"h":300,"nm":"Location_Accuracy_Exact_OFF_DT","ddd":0,"assets":[{"id":"comp_0","nm":"Exact_OFF_DT","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.5,49,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":15,"s":[102,102,100]},{"t":60,"s":[80,80,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Location Pointer Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.298,63.254,0],"ix":2,"l":2},"a":{"a":0,"k":[18.25,51.679,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.967,0.967,0.2],"y":[1,1,1]},"o":{"x":[0.499,0.499,0.4],"y":[0,0,0]},"t":24,"s":[54,54,100]},{"t":42,"s":[10,10,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.551,0],[0,-3.55],[3.55,0],[0,3.551]],"o":[[3.55,0],[0,3.551],[-3.551,0],[0,-3.55]],"v":[[0.001,-6.429],[6.428,-0.001],[0.001,6.429],[-6.428,-0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":4,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[18.249,18.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[9.952,0],[0,-9.951],[0,0],[0,13.5]],"o":[[-9.952,0],[0,13.5],[0,0],[0,-9.951]],"v":[[0,-25.715],[-18,-7.715],[0,25.715],[18,-7.715]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":4,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[18.25,25.965],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":43,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Location bottom Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.298,63.254,0],"ix":2,"l":2},"a":{"a":0,"k":[13.432,6.364,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.967,0.967,0.2],"y":[1,1,1]},"o":{"x":[0.499,0.499,0.4],"y":[0,0,0]},"t":24,"s":[54,54,100]},{"t":42,"s":[10,10,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.135],[6.037,0],[0,2.134],[-6.038,0]],"o":[[0,2.134],[-6.038,0],[0,-2.135],[6.037,0]],"v":[[10.933,0],[0.001,3.863],[-10.933,0],[0.001,-3.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[13.432,6.364],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.135],[6.037,0],[0,2.134],[-6.038,0]],"o":[[0,2.134],[-6.038,0],[0,-2.135],[6.037,0]],"v":[[10.933,0],[0.001,3.863],[-10.933,0],[0.001,-3.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[13.432,6.364],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":43,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Pins_Blue","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.878],[0,1.37]],"o":[[-1.734,0],[0,1.37],[2.201,-1.878],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[24.4,206.353],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 16","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.055],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.879],[0,-2.055]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[38.555,165.221],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 17","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[49.115,205.645],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 18","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[105.514,213.902],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 19","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.055],[-2.2,-1.878],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.878],[0,-2.055]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[171.789,167.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 20","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[368.126,111.436],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 21","np":4,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[281.531,116.994],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 22","np":4,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.055],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.055]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[312.438,125.251],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 23","np":4,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[329.856,217.762],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 24","np":4,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Pins_Orange","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.703,137.9],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 15","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.744],[0,4.128],[3.303,-0.744]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[381.288,270.794],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 25","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[368.126,210.482],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 26","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.057],[-2.201,-1.878],[0,1.37]],"o":[[-1.734,0],[0,1.37],[2.2,-1.878],[0,-2.057]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[350.754,184.962],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 27","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[183.575,115.565],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 28","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey300","cl":"grey300","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[65.394,-87.762],[21.956,-25.29],[-65.394,87.762]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[77.34,207.456],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-29.617,35.822],[12.473,-5.331],[29.618,-35.821]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[199.566,278.889],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-8.219,9.823],[8.218,-9.823]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[201.146,227.853],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-33.868,-25.038],[8.293,9.519],[33.867,25.038]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[209.364,226.554],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[19.167,15.54],[-19.167,-15.54]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[119.548,75.138],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-72.23,5.545],[32.282,-5.545],[55.773,-5.545],[72.23,5.545]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[387.97,276.339],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[6.098,-157.973],[9.854,-43.904],[-9.854,53.949],[-8.736,83.862],[-3.121,157.973]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[393.327,169.155],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-82.146,-18.015],[-17.286,18.015],[82.146,18.015]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[364.737,26.721],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[142.734,59.599],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[108.816,73.718],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 13","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[105.449,70.281],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 14","np":2,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-61.089,79.79],[-10.563,22.215],[30.571,-28.773],[46.604,-61.512],[61.089,-79.79]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[197.779,251.592],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 29","np":2,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-181.176,-130.71],[-103.059,-67.44],[-30.743,-4.641],[-18.758,22.842],[23.397,30.81],[65.134,54.857],[89.912,66.865],[181.176,130.71]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[252.512,144.213],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 30","np":2,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-197.801,-135.93],[-169.578,-81.272],[-25.091,29.988],[22.053,51.34],[69.798,92.618],[93.582,100.74],[139.02,95.547],[197.801,135.931]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[245.942,149.433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 31","np":2,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-43.312,-42.235],[22.43,13.494],[43.312,42.235]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[45.812,287.214],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 32","np":2,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[18.949,23.783],[-18.949,-23.783]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[109.621,305.667],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 33","np":2,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-161.26,59.553],[-73.09,134.19],[-18.949,92.406],[161.26,-134.191]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[163.76,147.693],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 34","np":2,"cix":2,"bm":0,"ix":17,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-170.929,-3.155],[-48.146,106.479],[7.347,155.978],[56.846,100.678],[93.971,24.495],[114.081,-34.03],[140.8,-83.029],[163.001,-98.674],[170.928,-155.978]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[176.522,171.15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 35","np":2,"cix":2,"bm":0,"ix":18,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-128.003,-94.842],[128.003,94.842]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[128.569,230.353],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 36","np":2,"cix":2,"bm":0,"ix":19,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-143.858,-121.852],[-139.604,-101.518],[143.858,121.852]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[169.948,211.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 37","np":2,"cix":2,"bm":0,"ix":20,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-101.126,-61.295],[6.382,43.506],[25.329,50.08],[48.533,50.08],[101.126,61.295]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[339.523,72.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 38","np":2,"cix":2,"bm":0,"ix":21,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[75.549,33.199],[0.527,16.447],[-24.997,16.447],[-75.549,-33.199]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[358.139,131.247],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 39","np":2,"cix":2,"bm":0,"ix":22,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[56.267,11.975],[-22.236,-5.401],[-51.24,-5.401],[-56.267,-11.975]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[381.289,156.961],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 40","np":2,"cix":2,"bm":0,"ix":23,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-23.396,-110.585],[23.396,-69.624],[14.115,-1.562],[14.115,110.585]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[301.625,214.61],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 41","np":2,"cix":2,"bm":0,"ix":24,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-152.301,-106.862],[-115.301,-91.911],[68.768,50.786],[98.546,63.161],[152.3,106.862]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[163.44,175.022],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 43","np":2,"cix":2,"bm":0,"ix":25,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[137.091,-155.846],[25.716,1.527],[6.767,17.403],[-27.65,61.101],[-76.956,108.281],[-137.091,155.846]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[205.333,169.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 44","np":2,"cix":2,"bm":0,"ix":26,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-56.46,-44.426],[56.46,44.426]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[221.768,59.599],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 45","np":2,"cix":2,"bm":0,"ix":27,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-157.626,-125.059],[157.625,125.059]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[276.914,138.562],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 46","np":2,"cix":2,"bm":0,"ix":28,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[128.583,-155.739],[-82.951,117.438],[-128.583,155.739]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[173.622,164.445],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 47","np":2,"cix":2,"bm":0,"ix":29,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[123.363,-155.846],[-123.363,155.846]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[149.452,169.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 48","np":2,"cix":2,"bm":0,"ix":30,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[119.688,-154.492],[-119.689,154.493]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[128.376,167.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 49","np":2,"cix":2,"bm":0,"ix":31,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[94.165,-121.947],[-94.165,121.948]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[102.853,137.12],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 50","np":2,"cix":2,"bm":0,"ix":32,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey700","cl":"grey700","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-3.871,-11.834],[12.775,-0.847],[2.691,11.834],[-12.775,-0.336]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.376470588235,0.388235294118,0.407843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[260.038,79.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 54","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-35.787,-24.613],[-37.505,-22.753],[-34.63,-21.167],[-31.031,-23.281],[-9.88,-6.238],[-12.139,-3.2],[24.52,24.613],[37.505,7.844],[1.732,-21.035],[3.318,-23.281],[1.924,-24.613]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.376470588235,0.388235294118,0.407843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[231.647,43.549],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 55","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[37.533,-30.467],[28.951,-30.467],[20.804,-26.143],[14.324,-20.586],[4.618,-16.81],[1.118,-16.23],[-6.173,-7.064],[-37.533,17.139],[-22.607,30.467],[8.069,1.113],[26.699,-16.712]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.376470588235,0.388235294118,0.407843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[150.984,247.163],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 56","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey800","cl":"grey800","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-206,150],[206,150],[206,-150],[-206,-150]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.234999997008,0.250999989229,0.263000009574,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[220.851,168.936],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 58","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":3,"nm":"Exact Light Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64.082,57.55,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[48.646,48.646,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[101.086,101.086,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.66,0.66],"y":[0,0]},"t":0,"s":[294,294]},{"t":10,"s":[300,300]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.552941203117,0.670588254929,0.921568632126,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.66],"y":[0]},"t":0,"s":[6]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[98.741,98.741],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":1,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,149,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.8],"y":[0,0,0]},"t":0,"s":[306,306,100]},{"t":45,"s":[240,240,100]}],"ix":6,"l":2}},"ao":0,"sw":117,"sh":98,"sc":"#a77b57","ip":0,"op":585,"st":-15,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"CircleMatte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[82.705,0],[0,-82.705],[-82.705,0],[0,82.705]],"o":[[-82.705,0],[0,82.705],[82.705,0],[0,-82.705]],"v":[[-0.125,-149.938],[-149.875,-0.188],[-0.125,149.562],[149.625,-0.188]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":-1,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[15.4,0],[0,0],[0,15.4],[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0]],"o":[[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0],[15.4,0],[0,0],[0,15.4]],"v":[[228,150],[-238,150],[-266,122],[-266,-122],[-238,-150],[228,-150],[256,-122],[256,122]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Exact_OFF_DT","tt":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":0,"k":[307.2,307.2,100],"ix":6,"l":2}},"ao":0,"w":117,"h":98,"ip":0,"op":585,"st":-15,"bm":0}],"markers":[{"tm":91,"cm":"","dr":0}]} \ No newline at end of file
diff --git a/PermissionController/res/raw-night/fine_loc_on.json b/PermissionController/res/raw-night/fine_loc_on.json
new file mode 100644
index 000000000..7731d7548
--- /dev/null
+++ b/PermissionController/res/raw-night/fine_loc_on.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":91,"w":300,"h":300,"nm":"Location_Accuracy_Exact_ON_DT","ddd":0,"assets":[{"id":"comp_0","nm":"Exact_ON_DT","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.5,49,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":15,"s":[80,80,100]},{"t":60,"s":[102,102,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Location Pointer Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.298,63.254,0],"ix":2,"l":2},"a":{"a":0,"k":[18.25,51.679,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.552,0.552,0.667],"y":[1,1,1]},"o":{"x":[0.046,0.046,0.333],"y":[0,0,0]},"t":28,"s":[20,20,100]},{"i":{"x":[0.468,0.468,0.667],"y":[1,1,1]},"o":{"x":[0.441,0.441,0.333],"y":[0,0,0]},"t":42,"s":[58,58,100]},{"t":60,"s":[54,54,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.551,0],[0,-3.55],[3.55,0],[0,3.551]],"o":[[3.55,0],[0,3.551],[-3.551,0],[0,-3.55]],"v":[[0.001,-6.429],[6.428,-0.001],[0.001,6.429],[-6.428,-0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":4,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[18.249,18.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[9.952,0],[0,-9.951],[0,0],[0,13.5]],"o":[[-9.952,0],[0,13.5],[0,0],[0,-9.951]],"v":[[0,-25.715],[-18,-7.715],[0,25.715],[18,-7.715]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":4,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[18.25,25.965],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":28,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Location bottom Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.298,63.254,0],"ix":2,"l":2},"a":{"a":0,"k":[13.432,6.364,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":28,"s":[20,20,100]},{"t":41,"s":[54,54,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.135],[6.037,0],[0,2.134],[-6.038,0]],"o":[[0,2.134],[-6.038,0],[0,-2.135],[6.037,0]],"v":[[10.933,0],[0.001,3.863],[-10.933,0],[0.001,-3.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.61568627451,0.964705882353,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[13.432,6.364],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.135],[6.037,0],[0,2.134],[-6.038,0]],"o":[[0,2.134],[-6.038,0],[0,-2.135],[6.037,0]],"v":[[10.933,0],[0.001,3.863],[-10.933,0],[0.001,-3.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[13.432,6.364],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":28,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Pins_Blue","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.878],[0,1.37]],"o":[[-1.734,0],[0,1.37],[2.201,-1.878],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[24.4,206.353],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 16","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.055],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.879],[0,-2.055]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[38.555,165.221],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 17","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[49.115,205.645],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 18","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[105.514,213.902],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 19","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.055],[-2.2,-1.878],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.878],[0,-2.055]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[171.789,167.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 20","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[368.126,111.436],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 21","np":4,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[281.531,116.994],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 22","np":4,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.055],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.055]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[312.438,125.251],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 23","np":4,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.4,0.616000007181,0.964999988032,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[329.856,217.762],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 24","np":4,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Pins_Orange","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.703,137.9],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 15","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.744],[0,4.128],[3.303,-0.744]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[381.288,270.794],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 25","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[368.126,210.482],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 26","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.057],[-2.201,-1.878],[0,1.37]],"o":[[-1.734,0],[0,1.37],[2.2,-1.878],[0,-2.057]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[350.754,184.962],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 27","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[183.575,115.565],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 28","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey300","cl":"grey300","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[65.394,-87.762],[21.956,-25.29],[-65.394,87.762]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[77.34,207.456],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-29.617,35.822],[12.473,-5.331],[29.618,-35.821]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[199.566,278.889],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-8.219,9.823],[8.218,-9.823]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[201.146,227.853],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-33.868,-25.038],[8.293,9.519],[33.867,25.038]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[209.364,226.554],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[19.167,15.54],[-19.167,-15.54]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[119.548,75.138],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-72.23,5.545],[32.282,-5.545],[55.773,-5.545],[72.23,5.545]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[387.97,276.339],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[6.098,-157.973],[9.854,-43.904],[-9.854,53.949],[-8.736,83.862],[-3.121,157.973]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[393.327,169.155],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-82.146,-18.015],[-17.286,18.015],[82.146,18.015]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[364.737,26.721],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[142.734,59.599],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[108.816,73.718],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 13","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[105.449,70.281],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 14","np":2,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-61.089,79.79],[-10.563,22.215],[30.571,-28.773],[46.604,-61.512],[61.089,-79.79]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[197.779,251.592],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 29","np":2,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-181.176,-130.71],[-103.059,-67.44],[-30.743,-4.641],[-18.758,22.842],[23.397,30.81],[65.134,54.857],[89.912,66.865],[181.176,130.71]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[252.512,144.213],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 30","np":2,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-197.801,-135.93],[-169.578,-81.272],[-25.091,29.988],[22.053,51.34],[69.798,92.618],[93.582,100.74],[139.02,95.547],[197.801,135.931]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[245.942,149.433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 31","np":2,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-43.312,-42.235],[22.43,13.494],[43.312,42.235]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[45.812,287.214],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 32","np":2,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[18.949,23.783],[-18.949,-23.783]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[109.621,305.667],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 33","np":2,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-161.26,59.553],[-73.09,134.19],[-18.949,92.406],[161.26,-134.191]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[163.76,147.693],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 34","np":2,"cix":2,"bm":0,"ix":17,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-170.929,-3.155],[-48.146,106.479],[7.347,155.978],[56.846,100.678],[93.971,24.495],[114.081,-34.03],[140.8,-83.029],[163.001,-98.674],[170.928,-155.978]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[176.522,171.15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 35","np":2,"cix":2,"bm":0,"ix":18,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-128.003,-94.842],[128.003,94.842]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[128.569,230.353],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 36","np":2,"cix":2,"bm":0,"ix":19,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-143.858,-121.852],[-139.604,-101.518],[143.858,121.852]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[169.948,211.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 37","np":2,"cix":2,"bm":0,"ix":20,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-101.126,-61.295],[6.382,43.506],[25.329,50.08],[48.533,50.08],[101.126,61.295]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[339.523,72.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 38","np":2,"cix":2,"bm":0,"ix":21,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[75.549,33.199],[0.527,16.447],[-24.997,16.447],[-75.549,-33.199]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[358.139,131.247],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 39","np":2,"cix":2,"bm":0,"ix":22,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[56.267,11.975],[-22.236,-5.401],[-51.24,-5.401],[-56.267,-11.975]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[381.289,156.961],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 40","np":2,"cix":2,"bm":0,"ix":23,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-23.396,-110.585],[23.396,-69.624],[14.115,-1.562],[14.115,110.585]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[301.625,214.61],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 41","np":2,"cix":2,"bm":0,"ix":24,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-152.301,-106.862],[-115.301,-91.911],[68.768,50.786],[98.546,63.161],[152.3,106.862]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[163.44,175.022],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 43","np":2,"cix":2,"bm":0,"ix":25,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[137.091,-155.846],[25.716,1.527],[6.767,17.403],[-27.65,61.101],[-76.956,108.281],[-137.091,155.846]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[205.333,169.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 44","np":2,"cix":2,"bm":0,"ix":26,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-56.46,-44.426],[56.46,44.426]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[221.768,59.599],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 45","np":2,"cix":2,"bm":0,"ix":27,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-157.626,-125.059],[157.625,125.059]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[276.914,138.562],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 46","np":2,"cix":2,"bm":0,"ix":28,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[128.583,-155.739],[-82.951,117.438],[-128.583,155.739]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[173.622,164.445],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 47","np":2,"cix":2,"bm":0,"ix":29,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[123.363,-155.846],[-123.363,155.846]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[149.452,169.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 48","np":2,"cix":2,"bm":0,"ix":30,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[119.688,-154.492],[-119.689,154.493]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[128.376,167.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 49","np":2,"cix":2,"bm":0,"ix":31,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[94.165,-121.947],[-94.165,121.948]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[102.853,137.12],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 50","np":2,"cix":2,"bm":0,"ix":32,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[7.927,-159.713],[-0.193,-105.055],[-1.741,54.527],[-7.927,159.714]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[344.164,173.216],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 42","np":2,"cix":2,"bm":0,"ix":33,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-10.143,-7.085],[10.143,7.085]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.854901960784,0.862745098039,0.878431372549,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[89.154,175.08],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":34,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey700","cl":"grey700","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-3.871,-11.834],[12.775,-0.847],[2.691,11.834],[-12.775,-0.336]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.376470588235,0.388235294118,0.407843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[260.038,79.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 54","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-35.787,-24.613],[-37.505,-22.753],[-34.63,-21.167],[-31.031,-23.281],[-9.88,-6.238],[-12.139,-3.2],[24.52,24.613],[37.505,7.844],[1.732,-21.035],[3.318,-23.281],[1.924,-24.613]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.376470588235,0.388235294118,0.407843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[231.647,43.549],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 55","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[37.533,-30.467],[28.951,-30.467],[20.804,-26.143],[14.324,-20.586],[4.618,-16.81],[1.118,-16.23],[-6.173,-7.064],[-37.533,17.139],[-22.607,30.467],[8.069,1.113],[26.699,-16.712]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.376470588235,0.388235294118,0.407843137255,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[150.984,247.163],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 56","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey800","cl":"grey800","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-206,150],[206,150],[206,-150],[-206,-150]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.234999997008,0.250999989229,0.263000009574,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[220.851,168.936],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 58","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":3,"nm":"Exact Light Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64.082,57.55,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[48.646,48.646,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue400","cl":"blue400","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[101.086,101.086,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.34,0.34],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":10,"s":[300,300]},{"t":25,"s":[294,294]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.552941203117,0.670588254929,0.921568632126,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":25,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[98.741,98.741],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":1,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,149,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":0,"s":[240,240,100]},{"t":45,"s":[306,306,100]}],"ix":6,"l":2}},"ao":0,"sw":117,"sh":98,"sc":"#a77b57","ip":0,"op":585,"st":-15,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"CircleMatte 3","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[82.705,0],[0,-82.705],[-82.705,0],[0,82.705]],"o":[[-82.705,0],[0,82.705],[82.705,0],[0,-82.705]],"v":[[-0.125,-149.938],[-149.875,-0.188],[-0.125,149.562],[149.625,-0.188]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":-1,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[15.4,0],[0,0],[0,15.4],[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0]],"o":[[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0],[15.4,0],[0,0],[0,15.4]],"v":[[228,150],[-238,150],[-266,122],[-266,-122],[-238,-150],[228,-150],[256,-122],[256,122]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Exact_ON_DT","tt":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":0,"k":[307.2,307.2,100],"ix":6,"l":2}},"ao":0,"w":117,"h":98,"ip":0,"op":585,"st":-15,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/PermissionController/res/raw/coarse_loc_off.json b/PermissionController/res/raw/coarse_loc_off.json
new file mode 100644
index 000000000..52927c852
--- /dev/null
+++ b/PermissionController/res/raw/coarse_loc_off.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":91,"w":300,"h":300,"nm":"Location_Accuracy_Approximate_OFF_LT","ddd":0,"assets":[{"id":"comp_0","nm":"Approximate_OFF_LT","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.5,49,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":15,"s":[62,62,100]},{"t":60,"s":[90,90,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Highway_Signs","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.19],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.19],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.584,67.277],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.48,-0.218],[-0.001,3.653],[5.479,-0.218],[5.999,-3.653],[-6,-3.653]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.583,71.205],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.037],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.037],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.129],[0,-1.046],[-2.496,0.129],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.584,65.956],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.19],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.19],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,70.159],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.479,-0.218],[0.001,3.653],[5.48,-0.218],[5.999,-3.653],[-5.999,-3.653]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,74.086],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.037],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.037],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.129],[0,-1.046],[-2.496,0.129],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,68.837],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":4,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.191],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.191],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,222.357],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 13","np":4,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.479,-0.217],[-0.001,3.653],[5.48,-0.217],[5.999,-3.652],[-5.999,-3.652]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,226.283],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 14","np":4,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.038],[0,0],[0.292,-1.238],[0,0]],"o":[[0,0],[-1.358,0.038],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.238]],"v":[[5.065,-1.046],[2.496,0.128],[0,-1.046],[-2.496,0.128],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,221.035],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 15","np":4,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.191],[0,0],[0.041,0.177]],"o":[[-0.042,0.177],[0,0],[-0.03,-0.191],[0,0]],"v":[[-5.894,-0.276],[-6,0.275],[6,0.275],[5.894,-0.276]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.011,186.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 16","np":4,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.192],[0,0],[-1.626,3.1],[0.158,1.028],[0,0]],"o":[[1.626,3.1],[0,0],[0.626,-1.192],[0,0],[-0.158,1.028]],"v":[[-5.479,-0.218],[-0.001,3.652],[5.48,-0.218],[5.999,-3.652],[-5.999,-3.652]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.01,190.467],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 17","np":4,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.036],[0,0],[1.358,0.038],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.038],[0,0],[-1.321,-0.036],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.128],[0,-1.046],[-2.496,0.128],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.011,185.218],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 18","np":4,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".yellow500","cl":"yellow500","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-10.273,-62.792],[10.273,-19.932],[5.308,43.982],[3.529,62.792]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[304.691,67.791],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 19","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-69.845,50.02],[-43.615,20.333],[-43.615,-21.96],[-34.261,-28.874],[-24.095,-28.874],[-16.368,-34.974],[-1.728,-31.72],[1.931,-37.821],[7.218,-34.567],[23.079,-42.293],[55.331,-42.293],[69.845,-50.02]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[373.948,242.314],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 20","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-113.972,-119.335],[-113.972,-89.146],[-110.288,-76.336],[-118.014,-57.426],[-110.288,-39.939],[-93.615,-15.54],[-81.414,-1.306],[-84.668,17.808],[-75.721,29.193],[-63.521,36.921],[-49.288,39.36],[-41.968,48.714],[-28.954,48.714],[-17.568,53.594],[-2.115,58.067],[5.205,65.794],[21.066,68.233],[51.972,97.107],[62.545,99.954],[75.559,99.954],[118.014,119.335]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[245.421,175.3],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 21","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-214.415,-10.167],[-193.269,2.034],[-180.255,2.034],[-172.122,4.88],[-161.548,-0.406],[-149.349,0.001],[-132.675,-7.32],[-126.575,-5.693],[-113.969,-10.167],[-96.075,-8.133],[-88.755,-2.643],[-80.215,-2.643],[-43.208,-2.643],[-22.062,-6.1],[-13.319,0.407],[-0.61,2.034],[17.384,-2.643],[35.278,-0.406],[50.731,2.034],[53.985,6.913],[73.912,4.88],[86.111,10.167],[114.578,10.167],[150.365,10.167],[159.311,4.88],[214.415,2.034]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[223.889,101.607],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 22","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-152.296,-34.933],[-152.296,-17.527],[-140.503,-1.668],[-129.523,3.62],[-129.523,10.94],[-120.577,15.413],[-117.729,22.327],[-88.449,34.933],[-76.251,30.867],[-68.117,23.953],[-38.43,27.207],[-25.417,28.833],[-7.93,24.767],[20.943,25.986],[31.924,29.24],[38.837,25.58],[51.443,25.58],[60.391,21.513],[86.01,18.667],[97.804,26.8],[152.297,26.393]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[304.103,105.308],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 24","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[21.757,-76.408],[21.757,-63.89],[13.214,-63.89],[-14.031,-38.677],[-19.724,-23.223],[-21.757,54.857],[-18.098,63.803],[-21.757,76.409]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,177.698],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 25","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-67.1,-42.533],[-37.413,-29.478],[-34.567,-20.938],[-28.873,-24.191],[-17.08,-23.378],[-5.693,-7.518],[12.2,-3.046],[18.3,5.495],[24.807,9.562],[38.227,11.594],[52.867,19.729],[62.525,21.355],[67.1,29.896],[67.1,42.533]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[160.753,59.919],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 26","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-139.893,-158.396],[-135.827,-143.349],[-95.566,-100.243],[-75.233,-93.736],[-51.646,-67.303],[-48.8,-54.29],[-35.38,-42.496],[-32.94,-22.57],[-24.807,-18.91],[-10.574,-14.844],[8.947,16.47],[58.56,53.477],[82.147,84.79],[102.074,96.99],[125.66,133.997],[139.893,142.536],[139.893,158.396]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[243.307,169.317],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 28","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey500","cl":"grey500","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[31.717,-1.007],[-31.717,1.007]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[287.883,119.11],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 29","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-35.365,-14.212],[-27.159,-14.715],[-22.239,-18.741],[-8.414,-18.741],[6.556,-19.629],[15.02,-18.202],[28.069,-14.212],[28.069,-5.406],[28.069,1.64],[28.069,10.949],[29.704,14.22],[35.239,16.731],[35.365,19.629]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[291.531,123.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 30","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-41.561,-23.371],[-37.444,-22.113],[-31.154,-25.384],[-26.499,-26.767],[-21.215,-28.151],[-18.196,-31.05],[-12.41,-31.528],[-8.761,-31.05],[-7.628,-28.906],[9.733,-22.113],[23.067,-5.129],[29.106,-0.14],[37.283,12.608],[40.931,21.004],[41.561,31.528]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[348.112,171.29],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 31","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[4.64,-38.155],[4.64,-8.29],[7.771,-2.101],[6.974,5.21],[2.462,11.432],[-0.966,22.989],[-7.771,38.155]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[251.527,139.762],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 32","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-19.621,11.458],[-23.788,-11.458],[23.788,-11.458]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[259.299,233.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 33","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-36.573,-3.452],[3.542,-3.452],[8.492,-1.858],[25.051,-3.452],[31.026,-2.655],[35.293,0.133],[36.573,2.352],[34.013,2.352],[32.887,3.451]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[297.973,269.1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 34","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-25.691,-18.223],[-10.328,-18.734],[-5.207,-17.88],[1.281,-18.307],[18.009,-19.077],[25.691,-12.504],[22.789,-10.797],[14.168,0.47],[12.376,5.846],[8.45,13.615],[4.011,19.077]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[307.575,287.115],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 35","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[50.742,46.448],[51.669,43.468],[50.742,37.157],[55.148,32.302],[72.276,31.185],[72.276,5.64],[70.623,-1.526],[70.623,-40.523],[69.383,-42.865],[62.906,-43.554],[56.843,-42.865],[54.224,-44.381],[49.264,-44.381],[45.13,-46.448],[-72.276,-46.448]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[226.967,241.72],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 36","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-119.509,-44.611],[-97.824,-40.96],[-89.824,-34.688],[-72.741,-36.266],[-66.754,-33.154],[40.537,-34.971],[59.633,-38.882],[68.047,-38.882],[73.387,-37.306],[74.626,-33.154],[77.919,-31.804],[77.919,-26.232],[80.508,-24.928],[89.57,-21.215],[97.823,-16.038],[105.684,-14.257],[108.827,-8.431],[113.844,5.663],[119.508,8.884],[117.654,19.078],[110.284,25.092],[107.048,37.527],[104.562,44.612]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[278.637,267.243],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 37","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-51.352,2.509],[13.052,3.155],[35.386,-3.155],[51.352,-3.155]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[301.911,213.904],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 38","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[275.481,202.819],[275.481,222.082]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 39","np":2,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.805,-24.131],[-0.446,-14.804],[1.18,18.209],[-2.805,24.131]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[269.292,219.403],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 40","np":2,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-16.325,-7.523],[-4.713,-0.839],[5.806,-0.839],[16.325,7.523]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[261.745,182.35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 41","np":2,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-52.188,-19.67],[-41.346,-24.51],[-9.628,-12.712],[5.098,-9.312],[20.956,-3.564],[26.135,-4.62],[34.719,-0.671],[41.671,4.326],[52.189,7.729],[46.039,15.452],[46.202,24.509]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[213.259,163.422],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 42","np":2,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[73.548,-6.19],[75.996,7.565],[69.525,24.299],[59.976,26.175],[53.504,28.441],[47.03,33.188],[47.03,39.488],[34.894,32.698],[26.767,21.986],[17.423,17.347],[-1.49,12.605],[-28.757,3.71],[-37.055,-16.252],[-66.354,-16.252],[-66.354,-18.782],[-69.801,-23.38],[-68.077,-26.092],[-70.654,-30.31],[-74.309,-30.31],[-75.996,-39.488]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[230.554,138.453],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 43","np":2,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-68.814,-67.689],[-33.186,-67.127],[-13.566,-64.287],[9.77,-52.647],[17.081,-46.04],[58.129,-44.971],[60.347,-29.873],[60.347,-12.806],[61.785,6.876],[59.395,9.068],[60.347,54.334],[65.44,54.334],[68.814,58.411],[63.612,63.472],[65.581,64.877],[63.612,67.689]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[243.756,235.438],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 44","np":2,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-6.636,-10.36],[-2.642,-10.95],[4.106,-13.762],[4.478,-12.242],[5.933,-6.307],[6.636,13.761]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[170.836,181.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 45","np":2,"cix":2,"bm":0,"ix":17,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[150.903,178.874],[176.77,178.874]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 46","np":2,"cix":2,"bm":0,"ix":18,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-46.11,-24.495],[-24.601,-24.495],[-18.978,-22.285],[10.012,-24.495],[26.288,-21.262],[39.924,-19.433],[46.11,-14.795],[41.028,24.495]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[194.482,197.586],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 47","np":2,"cix":2,"bm":0,"ix":19,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[163.403,74.086],[163.403,98.964]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 48","np":2,"cix":2,"bm":0,"ix":20,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.298,-13.017],[-2.298,-7.952],[0.038,-1.57],[0.038,13.017]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[138.776,74.953],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 49","np":2,"cix":2,"bm":0,"ix":21,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-25.094,-4.83],[-14.691,-5.955],[-13.004,-4.127],[-6.116,-5.041],[25.094,-5.041],[25.094,5.955]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[113.72,93.01],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 50","np":2,"cix":2,"bm":0,"ix":22,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-21.799,-26.171],[-21.799,13.635],[-20.774,20.008],[16.176,26.171],[21.799,24.503]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[143.443,79.138],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 51","np":2,"cix":2,"bm":0,"ix":23,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[88.626,74.857],[151.806,74.857]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 52","np":2,"cix":2,"bm":0,"ix":24,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[93.653,52.401],[93.653,94.366]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 53","np":2,"cix":2,"bm":0,"ix":25,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.723,-35.415],[-1.723,-14.469],[0.62,-12.953],[1.723,35.415]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[162.477,159.858],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 54","np":2,"cix":2,"bm":0,"ix":26,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-11.575,3.259],[-0.551,-3.259],[4.821,-3.259],[7.166,-1.191],[9.784,-1.191],[11.575,-3.259]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[155.932,166.01],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 55","np":2,"cix":2,"bm":0,"ix":27,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.489,-23.839],[-6.05,-13.917],[-7.841,-12.401],[-7.841,1.329],[-11.562,10.887],[-11.562,23.84],[11.562,23.84]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[179.069,138.912],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 56","np":2,"cix":2,"bm":0,"ix":28,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".green100","cl":"green100","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-84.172,-14.163],[-87.624,-10.786],[-87.624,-7.25],[-91.245,-3.685],[-94.866,-5.154],[-98.486,-11.792],[-101.705,-10.384],[-105.729,-11.792],[-110.154,-14.163],[-116.994,-11.792],[-121.218,-5.355],[-119.247,-2.337],[-117.598,-2.337],[-116.19,1.083],[-110.959,2.286],[-107.137,3.899],[-97.49,3.899],[-97.49,10.725],[-88.428,11.544],[-86.819,19.59],[-68.714,22.205],[-62.64,26.027],[-55.235,33.068],[-45.78,31.79],[-35.723,31.79],[-22.848,31.79],[-17.818,34.678],[-3.957,38.5],[5.315,37.695],[13.563,34.678],[23.622,33.873],[46.757,36.891],[58.826,36.891],[67.678,33.068],[75.14,30.654],[87.996,32.666],[90.209,31.79],[108.313,29.649],[117.768,25.022],[130.243,25.424],[144.523,25.022],[148.34,20.596],[149.955,15.969],[148.34,10.725],[127.193,-42.264],[-25.865,-48.73],[-69.318,-44.784],[-72.134,-37.519],[-82.394,-31.305]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843137255,0.917647058824,0.839215686275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[308.06,59.651],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 57","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-53.521,-13.307],[-41.048,-20.863],[-32.801,-18.738],[-26.967,-20.863],[-15.902,-15.576],[-11.075,-15.576],[-11.075,-13.307],[4.818,-13.307],[6.83,-15.576],[17.29,-15.576],[20.308,-23.708],[24.532,-22.041],[35.194,-20.863],[45.454,-19.945],[47.264,-15.576],[52.695,-6.064],[46.459,23.708],[42.881,20.101],[34.646,17.883],[-4.626,17.883],[-13.177,20.101],[-23.946,20.101],[-30.387,15.26],[-38.835,16.264],[-49.296,17.271],[-56.009,14.456]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843137255,0.917647058824,0.839215686275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[69.974,127.35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 61","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey100","cl":"grey100","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-2.851,0.95],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[2.851,-0.95],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-209.239,-23.701],[-197.204,-12.299],[-193.403,-15.149],[-193.403,-18.95],[-181.051,-21.801],[-177.251,-23.701],[-166.482,-23.701],[-157.931,-25.918],[-154.447,-25.918],[-135.761,-25.918],[-131.327,-25.918],[-124.36,-25.918],[-118.658,-25.918],[-110.424,-23.701],[-104.406,-19.108],[-98.389,-12.299],[-88.254,0.49],[-84.77,0],[-84.77,-6.282],[-81.92,-1.531],[-83.351,0],[-84.736,0.49],[-86.024,1.81],[-86.644,2.239],[-81.92,12.262],[-74.427,29.3],[-73.52,32.545],[-69.607,41.517],[-69.607,44.142],[-68.7,45.383],[-66.791,56.121],[-66.791,60.226],[-68.748,61.371],[-68.748,62.564],[-70.322,63.995],[-70.37,65.523],[-72.327,67.146],[-73.615,67.146],[-74.14,68.195],[-75.477,68.195],[-75.477,69.389],[-76.908,70.439],[-77.481,71.059],[-77.481,72.014],[-78.293,72.872],[-76.813,73.445],[-76.67,74.877],[-75.572,75.402],[-75.668,77.788],[-74.904,77.788],[-74.904,81.367],[-74.093,81.367],[-73.854,83.611],[-72.947,84.231],[-72.947,85.52],[-71.754,86.331],[-69.655,86.331],[-64.643,86.331],[-64.023,85.472],[-61.302,85.902],[-61.064,86.331],[-57.485,86.665],[-56.005,88.287],[-56.005,89.815],[-54.287,90.912],[-53.428,90.244],[-52.998,90.912],[-51.614,91.199],[-51.614,92.249],[-49.133,92.249],[-48.751,93.299],[-46.651,93.299],[-45.935,94.253],[-45.076,94.253],[-42.833,96.688],[-40.256,96.688],[-40.256,97.498],[-38.108,97.498],[-37.678,98.501],[-35.817,98.501],[-34.815,99.169],[-34.004,99.169],[-33.622,98.453],[-31.14,98.453],[-31.427,97.642],[-31.904,96.782],[-33.431,96.735],[-33.622,91.58],[-33.097,91.58],[-32.763,92.583],[-31.713,92.44],[-31.14,92.965],[-29.279,92.869],[-29.279,91.676],[-30.138,91.342],[-30.043,89.385],[-30.902,88.669],[-30.949,87.237],[-31.904,86.808],[-30.997,86.092],[-30.854,84.66],[-30.138,84.374],[-30.091,80.556],[-31.045,79.984],[-30.759,76.691],[-29.47,78.313],[-29.279,79.268],[-27.37,80.222],[-26.559,79.888],[-25.89,78.934],[-25.604,77.884],[-24.459,76.357],[-21.882,75.402],[-17.586,74.973],[-14.771,74.734],[-14.771,75.831],[-22.072,77.74],[-23.361,78.122],[-24.268,78.6],[-25.127,79.124],[-25.509,80.174],[-27.036,81.272],[-28.181,81.749],[-29.088,83.229],[-29.088,85.997],[-28.849,87.953],[-27.752,88.287],[-26.177,89.385],[-24.173,88.097],[-23.648,88.097],[-22.502,87.143],[-22.74,86.331],[-23.027,83.372],[-22.12,83.133],[-21.548,83.897],[-21.5,84.518],[-20.88,84.66],[-20.88,85.902],[-20.02,86.188],[-19.734,85.328],[-16.918,85.568],[-16.966,84.708],[-19.114,82.895],[-19.591,82.035],[-19.495,81.272],[-16.441,81.415],[-16.107,80.509],[-13.72,80.509],[-12.862,79.076],[-11.239,79.984],[-10.809,83.849],[-9.473,83.897],[-8.996,84.566],[-6.848,84.852],[-6.085,85.949],[-3.364,86.236],[-3.078,84.756],[-3.698,81.701],[-6.037,79.554],[-6.323,78.504],[-3.603,78.456],[-2.219,78.027],[-0.406,77.359],[0.549,77.74],[2.84,77.645],[4.94,77.645],[6.133,78.027],[8.567,78.6],[9.14,79.458],[11.096,79.458],[11.43,80.413],[13.149,80.413],[13.435,80.986],[14.628,81.033],[14.628,80.413],[15.868,79.936],[15.868,78.934],[16.346,81.272],[18.064,81.32],[18.064,80.222],[17.539,80.174],[17.539,78.074],[18.446,78.552],[19.973,80.031],[21.452,80.079],[21.452,82.704],[20.976,83.42],[20.976,84.518],[19.782,84.804],[19.83,85.71],[22.026,86.188],[22.884,86.999],[24.698,87.047],[25.414,87.953],[27.275,87.953],[30.855,87.953],[31.523,86.999],[32.764,87.047],[32.286,88.335],[31.761,88.956],[32.048,90.436],[36.915,90.818],[37.583,98.787],[39.97,102.366],[42.738,103.368],[44.552,106.185],[54.431,117.018],[58.582,120.216],[67.889,127.518],[73.711,130.429],[85.165,134.294],[88.458,136.013],[91.179,136.013],[97.717,142.026],[98.338,144.89],[104.494,147.849],[107.262,150],[211.975,150],[211.975,-151.69],[-210.962,-153.764]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.952941176471,0.956862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[223.279,171.151],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 62","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".blue100","cl":"blue100","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-206,148.684],[206,148.684],[206,-148.684],[-206,-148.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.890196078431,0.988235294118,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[223.279,172.467],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 63","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue900","cl":"blue900","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[101.086,101.086,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.66,0.66],"y":[0,0]},"t":0,"s":[294,294]},{"t":10,"s":[300,300]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.66],"y":[0]},"t":0,"s":[6]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[98.741,98.741],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".blue900","cl":"blue900","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[100]},{"t":20,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[64.361,64.36,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":0,"s":[150,150,100]},{"t":45,"s":[48.034,48.034,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-34.164],[34.165,0],[0,34.165],[-34.165,0]],"o":[[0,34.165],[-34.165,0],[0,-34.164],[34.165,0]],"v":[[61.861,-0.001],[0,61.861],[-61.861,-0.001],[0,-61.861]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.829000016755,0.885999971278,0.955999995213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[100]},{"t":32,"s":[70]}],"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[64.361,64.361],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-34.164],[34.165,0],[0,34.165],[-34.165,0]],"o":[[0,34.165],[-34.165,0],[0,-34.164],[34.165,0]],"v":[[61.861,-0.001],[0,61.861],[-61.861,-0.001],[0,-61.861]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":11,"s":[50]},{"t":32,"s":[20]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[64.361,64.361],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":585,"st":-15,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"CircleMatte 2","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[82.705,0],[0,-82.705],[-82.705,0],[0,82.705]],"o":[[-82.705,0],[0,82.705],[82.705,0],[0,-82.705]],"v":[[-0.125,-149.938],[-149.875,-0.188],[-0.125,149.562],[149.625,-0.188]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":-1,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[15.4,0],[0,0],[0,15.4],[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0]],"o":[[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0],[15.4,0],[0,0],[0,15.4]],"v":[[228,150],[-238,150],[-266,122],[-266,-122],[-238,-150],[228,-150],[256,-122],[256,122]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":0,"nm":"Approximate_OFF_LT","tt":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":0,"k":[307.2,307.2,100],"ix":6,"l":2}},"ao":0,"w":117,"h":98,"ip":0,"op":585,"st":-15,"bm":0}],"markers":[{"tm":91,"cm":"","dr":0}]} \ No newline at end of file
diff --git a/PermissionController/res/raw/coarse_loc_on.json b/PermissionController/res/raw/coarse_loc_on.json
new file mode 100644
index 000000000..2be289c67
--- /dev/null
+++ b/PermissionController/res/raw/coarse_loc_on.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":91,"w":300,"h":300,"nm":"Location_Accuracy_Approximate_ON_LT","ddd":0,"assets":[{"id":"comp_0","nm":"Approximate_ON_LT","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.5,49,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":15,"s":[90,90,100]},{"t":60,"s":[62,62,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Highway_Signs","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.19],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.19],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.584,67.277],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.48,-0.218],[-0.001,3.653],[5.479,-0.218],[5.999,-3.653],[-6,-3.653]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.583,71.205],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.037],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.037],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.129],[0,-1.046],[-2.496,0.129],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[130.584,65.956],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.19],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.19],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,70.159],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.479,-0.218],[0.001,3.653],[5.48,-0.218],[5.999,-3.653],[-5.999,-3.653]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,74.086],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.037],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.037],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.129],[0,-1.046],[-2.496,0.129],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.904,68.837],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":4,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.191],[0,0],[0.041,0.176]],"o":[[-0.042,0.176],[0,0],[-0.03,-0.191],[0,0]],"v":[[-5.894,-0.275],[-6,0.275],[6,0.275],[5.894,-0.275]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,222.357],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 13","np":4,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.193],[0,0],[-1.626,3.099],[0.158,1.029],[0,0]],"o":[[1.626,3.099],[0,0],[0.626,-1.193],[0,0],[-0.158,1.029]],"v":[[-5.479,-0.217],[-0.001,3.653],[5.48,-0.217],[5.999,-3.652],[-5.999,-3.652]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,226.283],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 14","np":4,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.037],[0,0],[1.358,0.038],[0,0],[0.292,-1.238],[0,0]],"o":[[0,0],[-1.358,0.038],[0,0],[-1.321,-0.037],[0,0],[0,0],[-0.292,-1.238]],"v":[[5.065,-1.046],[2.496,0.128],[0,-1.046],[-2.496,0.128],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,221.035],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 15","np":4,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0.029,-0.191],[0,0],[0.041,0.177]],"o":[[-0.042,0.177],[0,0],[-0.03,-0.191],[0,0]],"v":[[-5.894,-0.276],[-6,0.275],[6,0.275],[5.894,-0.276]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.910000011968,0.941000007181,0.995999983245,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.011,186.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 16","np":4,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.626,-1.192],[0,0],[-1.626,3.1],[0.158,1.028],[0,0]],"o":[[1.626,3.1],[0,0],[0.626,-1.192],[0,0],[-0.158,1.028]],"v":[[-5.479,-0.218],[-0.001,3.652],[5.48,-0.218],[5.999,-3.652],[-5.999,-3.652]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.258999992819,0.522000002394,0.957000014361,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.01,190.467],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 17","np":4,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.321,-0.036],[0,0],[1.358,0.038],[0,0],[0.292,-1.237],[0,0]],"o":[[0,0],[-1.358,0.038],[0,0],[-1.321,-0.036],[0,0],[0,0],[-0.292,-1.237]],"v":[[5.065,-1.046],[2.496,0.128],[0,-1.046],[-2.496,0.128],[-5.065,-1.046],[-5.894,1.046],[5.894,1.046]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999985639,0.263000009574,0.20800000359,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[190.011,185.218],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 18","np":4,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".yellow500","cl":"yellow500","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-10.273,-62.792],[10.273,-19.932],[5.308,43.982],[3.529,62.792]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[304.691,67.791],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 19","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-69.845,50.02],[-43.615,20.333],[-43.615,-21.96],[-34.261,-28.874],[-24.095,-28.874],[-16.368,-34.974],[-1.728,-31.72],[1.931,-37.821],[7.218,-34.567],[23.079,-42.293],[55.331,-42.293],[69.845,-50.02]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[373.948,242.314],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 20","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-113.972,-119.335],[-113.972,-89.146],[-110.288,-76.336],[-118.014,-57.426],[-110.288,-39.939],[-93.615,-15.54],[-81.414,-1.306],[-84.668,17.808],[-75.721,29.193],[-63.521,36.921],[-49.288,39.36],[-41.968,48.714],[-28.954,48.714],[-17.568,53.594],[-2.115,58.067],[5.205,65.794],[21.066,68.233],[51.972,97.107],[62.545,99.954],[75.559,99.954],[118.014,119.335]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[245.421,175.3],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 21","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-214.415,-10.167],[-193.269,2.034],[-180.255,2.034],[-172.122,4.88],[-161.548,-0.406],[-149.349,0.001],[-132.675,-7.32],[-126.575,-5.693],[-113.969,-10.167],[-96.075,-8.133],[-88.755,-2.643],[-80.215,-2.643],[-43.208,-2.643],[-22.062,-6.1],[-13.319,0.407],[-0.61,2.034],[17.384,-2.643],[35.278,-0.406],[50.731,2.034],[53.985,6.913],[73.912,4.88],[86.111,10.167],[114.578,10.167],[150.365,10.167],[159.311,4.88],[214.415,2.034]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[223.889,101.607],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 22","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-152.296,-34.933],[-152.296,-17.527],[-140.503,-1.668],[-129.523,3.62],[-129.523,10.94],[-120.577,15.413],[-117.729,22.327],[-88.449,34.933],[-76.251,30.867],[-68.117,23.953],[-38.43,27.207],[-25.417,28.833],[-7.93,24.767],[20.943,25.986],[31.924,29.24],[38.837,25.58],[51.443,25.58],[60.391,21.513],[86.01,18.667],[97.804,26.8],[152.297,26.393]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[304.103,105.308],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 24","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[21.757,-76.408],[21.757,-63.89],[13.214,-63.89],[-14.031,-38.677],[-19.724,-23.223],[-21.757,54.857],[-18.098,63.803],[-21.757,76.409]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[210.57,177.698],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 25","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-67.1,-42.533],[-37.413,-29.478],[-34.567,-20.938],[-28.873,-24.191],[-17.08,-23.378],[-5.693,-7.518],[12.2,-3.046],[18.3,5.495],[24.807,9.562],[38.227,11.594],[52.867,19.729],[62.525,21.355],[67.1,29.896],[67.1,42.533]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[160.753,59.919],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 26","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-139.893,-158.396],[-135.827,-143.349],[-95.566,-100.243],[-75.233,-93.736],[-51.646,-67.303],[-48.8,-54.29],[-35.38,-42.496],[-32.94,-22.57],[-24.807,-18.91],[-10.574,-14.844],[8.947,16.47],[58.56,53.477],[82.147,84.79],[102.074,96.99],[125.66,133.997],[139.893,142.536],[139.893,158.396]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.98431372549,0.737254901961,0.01568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[243.307,169.317],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 28","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".grey500","cl":"grey500","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[31.717,-1.007],[-31.717,1.007]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[287.883,119.11],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 29","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-35.365,-14.212],[-27.159,-14.715],[-22.239,-18.741],[-8.414,-18.741],[6.556,-19.629],[15.02,-18.202],[28.069,-14.212],[28.069,-5.406],[28.069,1.64],[28.069,10.949],[29.704,14.22],[35.239,16.731],[35.365,19.629]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[291.531,123.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 30","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-41.561,-23.371],[-37.444,-22.113],[-31.154,-25.384],[-26.499,-26.767],[-21.215,-28.151],[-18.196,-31.05],[-12.41,-31.528],[-8.761,-31.05],[-7.628,-28.906],[9.733,-22.113],[23.067,-5.129],[29.106,-0.14],[37.283,12.608],[40.931,21.004],[41.561,31.528]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[348.112,171.29],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 31","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[4.64,-38.155],[4.64,-8.29],[7.771,-2.101],[6.974,5.21],[2.462,11.432],[-0.966,22.989],[-7.771,38.155]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[251.527,139.762],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 32","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-19.621,11.458],[-23.788,-11.458],[23.788,-11.458]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[259.299,233.54],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 33","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-36.573,-3.452],[3.542,-3.452],[8.492,-1.858],[25.051,-3.452],[31.026,-2.655],[35.293,0.133],[36.573,2.352],[34.013,2.352],[32.887,3.451]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[297.973,269.1],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 34","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-25.691,-18.223],[-10.328,-18.734],[-5.207,-17.88],[1.281,-18.307],[18.009,-19.077],[25.691,-12.504],[22.789,-10.797],[14.168,0.47],[12.376,5.846],[8.45,13.615],[4.011,19.077]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[307.575,287.115],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 35","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[50.742,46.448],[51.669,43.468],[50.742,37.157],[55.148,32.302],[72.276,31.185],[72.276,5.64],[70.623,-1.526],[70.623,-40.523],[69.383,-42.865],[62.906,-43.554],[56.843,-42.865],[54.224,-44.381],[49.264,-44.381],[45.13,-46.448],[-72.276,-46.448]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[226.967,241.72],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 36","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-119.509,-44.611],[-97.824,-40.96],[-89.824,-34.688],[-72.741,-36.266],[-66.754,-33.154],[40.537,-34.971],[59.633,-38.882],[68.047,-38.882],[73.387,-37.306],[74.626,-33.154],[77.919,-31.804],[77.919,-26.232],[80.508,-24.928],[89.57,-21.215],[97.823,-16.038],[105.684,-14.257],[108.827,-8.431],[113.844,5.663],[119.508,8.884],[117.654,19.078],[110.284,25.092],[107.048,37.527],[104.562,44.612]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[278.637,267.243],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 37","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-51.352,2.509],[13.052,3.155],[35.386,-3.155],[51.352,-3.155]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[301.911,213.904],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 38","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[275.481,202.819],[275.481,222.082]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 39","np":2,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.805,-24.131],[-0.446,-14.804],[1.18,18.209],[-2.805,24.131]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[269.292,219.403],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 40","np":2,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-16.325,-7.523],[-4.713,-0.839],[5.806,-0.839],[16.325,7.523]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[261.745,182.35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 41","np":2,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-52.188,-19.67],[-41.346,-24.51],[-9.628,-12.712],[5.098,-9.312],[20.956,-3.564],[26.135,-4.62],[34.719,-0.671],[41.671,4.326],[52.189,7.729],[46.039,15.452],[46.202,24.509]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[213.259,163.422],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 42","np":2,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[73.548,-6.19],[75.996,7.565],[69.525,24.299],[59.976,26.175],[53.504,28.441],[47.03,33.188],[47.03,39.488],[34.894,32.698],[26.767,21.986],[17.423,17.347],[-1.49,12.605],[-28.757,3.71],[-37.055,-16.252],[-66.354,-16.252],[-66.354,-18.782],[-69.801,-23.38],[-68.077,-26.092],[-70.654,-30.31],[-74.309,-30.31],[-75.996,-39.488]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[230.554,138.453],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 43","np":2,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-68.814,-67.689],[-33.186,-67.127],[-13.566,-64.287],[9.77,-52.647],[17.081,-46.04],[58.129,-44.971],[60.347,-29.873],[60.347,-12.806],[61.785,6.876],[59.395,9.068],[60.347,54.334],[65.44,54.334],[68.814,58.411],[63.612,63.472],[65.581,64.877],[63.612,67.689]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[243.756,235.438],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 44","np":2,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-6.636,-10.36],[-2.642,-10.95],[4.106,-13.762],[4.478,-12.242],[5.933,-6.307],[6.636,13.761]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[170.836,181.51],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 45","np":2,"cix":2,"bm":0,"ix":17,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[150.903,178.874],[176.77,178.874]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 46","np":2,"cix":2,"bm":0,"ix":18,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-46.11,-24.495],[-24.601,-24.495],[-18.978,-22.285],[10.012,-24.495],[26.288,-21.262],[39.924,-19.433],[46.11,-14.795],[41.028,24.495]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[194.482,197.586],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 47","np":2,"cix":2,"bm":0,"ix":19,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[163.403,74.086],[163.403,98.964]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 48","np":2,"cix":2,"bm":0,"ix":20,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[2.298,-13.017],[-2.298,-7.952],[0.038,-1.57],[0.038,13.017]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[138.776,74.953],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 49","np":2,"cix":2,"bm":0,"ix":21,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-25.094,-4.83],[-14.691,-5.955],[-13.004,-4.127],[-6.116,-5.041],[25.094,-5.041],[25.094,5.955]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[113.72,93.01],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 50","np":2,"cix":2,"bm":0,"ix":22,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-21.799,-26.171],[-21.799,13.635],[-20.774,20.008],[16.176,26.171],[21.799,24.503]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[143.443,79.138],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 51","np":2,"cix":2,"bm":0,"ix":23,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[88.626,74.857],[151.806,74.857]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 52","np":2,"cix":2,"bm":0,"ix":24,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[93.653,52.401],[93.653,94.366]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 53","np":2,"cix":2,"bm":0,"ix":25,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-1.723,-35.415],[-1.723,-14.469],[0.62,-12.953],[1.723,35.415]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[162.477,159.858],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 54","np":2,"cix":2,"bm":0,"ix":26,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-11.575,3.259],[-0.551,-3.259],[4.821,-3.259],[7.166,-1.191],[9.784,-1.191],[11.575,-3.259]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[155.932,166.01],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 55","np":2,"cix":2,"bm":0,"ix":27,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-4.489,-23.839],[-6.05,-13.917],[-7.841,-12.401],[-7.841,1.329],[-11.562,10.887],[-11.562,23.84],[11.562,23.84]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[179.069,138.912],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 56","np":2,"cix":2,"bm":0,"ix":28,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".green100","cl":"green100","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-84.172,-14.163],[-87.624,-10.786],[-87.624,-7.25],[-91.245,-3.685],[-94.866,-5.154],[-98.486,-11.792],[-101.705,-10.384],[-105.729,-11.792],[-110.154,-14.163],[-116.994,-11.792],[-121.218,-5.355],[-119.247,-2.337],[-117.598,-2.337],[-116.19,1.083],[-110.959,2.286],[-107.137,3.899],[-97.49,3.899],[-97.49,10.725],[-88.428,11.544],[-86.819,19.59],[-68.714,22.205],[-62.64,26.027],[-55.235,33.068],[-45.78,31.79],[-35.723,31.79],[-22.848,31.79],[-17.818,34.678],[-3.957,38.5],[5.315,37.695],[13.563,34.678],[23.622,33.873],[46.757,36.891],[58.826,36.891],[67.678,33.068],[75.14,30.654],[87.996,32.666],[90.209,31.79],[108.313,29.649],[117.768,25.022],[130.243,25.424],[144.523,25.022],[148.34,20.596],[149.955,15.969],[148.34,10.725],[127.193,-42.264],[-25.865,-48.73],[-69.318,-44.784],[-72.134,-37.519],[-82.394,-31.305]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843137255,0.917647058824,0.839215686275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[308.06,59.651],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 57","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-53.521,-13.307],[-41.048,-20.863],[-32.801,-18.738],[-26.967,-20.863],[-15.902,-15.576],[-11.075,-15.576],[-11.075,-13.307],[4.818,-13.307],[6.83,-15.576],[17.29,-15.576],[20.308,-23.708],[24.532,-22.041],[35.194,-20.863],[45.454,-19.945],[47.264,-15.576],[52.695,-6.064],[46.459,23.708],[42.881,20.101],[34.646,17.883],[-4.626,17.883],[-13.177,20.101],[-23.946,20.101],[-30.387,15.26],[-38.835,16.264],[-49.296,17.271],[-56.009,14.456]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843137255,0.917647058824,0.839215686275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[69.974,127.35],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 61","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":".grey100","cl":"grey100","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-2.851,0.95],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[2.851,-0.95],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-209.239,-23.701],[-197.204,-12.299],[-193.403,-15.149],[-193.403,-18.95],[-181.051,-21.801],[-177.251,-23.701],[-166.482,-23.701],[-157.931,-25.918],[-154.447,-25.918],[-135.761,-25.918],[-131.327,-25.918],[-124.36,-25.918],[-118.658,-25.918],[-110.424,-23.701],[-104.406,-19.108],[-98.389,-12.299],[-88.254,0.49],[-84.77,0],[-84.77,-6.282],[-81.92,-1.531],[-83.351,0],[-84.736,0.49],[-86.024,1.81],[-86.644,2.239],[-81.92,12.262],[-74.427,29.3],[-73.52,32.545],[-69.607,41.517],[-69.607,44.142],[-68.7,45.383],[-66.791,56.121],[-66.791,60.226],[-68.748,61.371],[-68.748,62.564],[-70.322,63.995],[-70.37,65.523],[-72.327,67.146],[-73.615,67.146],[-74.14,68.195],[-75.477,68.195],[-75.477,69.389],[-76.908,70.439],[-77.481,71.059],[-77.481,72.014],[-78.293,72.872],[-76.813,73.445],[-76.67,74.877],[-75.572,75.402],[-75.668,77.788],[-74.904,77.788],[-74.904,81.367],[-74.093,81.367],[-73.854,83.611],[-72.947,84.231],[-72.947,85.52],[-71.754,86.331],[-69.655,86.331],[-64.643,86.331],[-64.023,85.472],[-61.302,85.902],[-61.064,86.331],[-57.485,86.665],[-56.005,88.287],[-56.005,89.815],[-54.287,90.912],[-53.428,90.244],[-52.998,90.912],[-51.614,91.199],[-51.614,92.249],[-49.133,92.249],[-48.751,93.299],[-46.651,93.299],[-45.935,94.253],[-45.076,94.253],[-42.833,96.688],[-40.256,96.688],[-40.256,97.498],[-38.108,97.498],[-37.678,98.501],[-35.817,98.501],[-34.815,99.169],[-34.004,99.169],[-33.622,98.453],[-31.14,98.453],[-31.427,97.642],[-31.904,96.782],[-33.431,96.735],[-33.622,91.58],[-33.097,91.58],[-32.763,92.583],[-31.713,92.44],[-31.14,92.965],[-29.279,92.869],[-29.279,91.676],[-30.138,91.342],[-30.043,89.385],[-30.902,88.669],[-30.949,87.237],[-31.904,86.808],[-30.997,86.092],[-30.854,84.66],[-30.138,84.374],[-30.091,80.556],[-31.045,79.984],[-30.759,76.691],[-29.47,78.313],[-29.279,79.268],[-27.37,80.222],[-26.559,79.888],[-25.89,78.934],[-25.604,77.884],[-24.459,76.357],[-21.882,75.402],[-17.586,74.973],[-14.771,74.734],[-14.771,75.831],[-22.072,77.74],[-23.361,78.122],[-24.268,78.6],[-25.127,79.124],[-25.509,80.174],[-27.036,81.272],[-28.181,81.749],[-29.088,83.229],[-29.088,85.997],[-28.849,87.953],[-27.752,88.287],[-26.177,89.385],[-24.173,88.097],[-23.648,88.097],[-22.502,87.143],[-22.74,86.331],[-23.027,83.372],[-22.12,83.133],[-21.548,83.897],[-21.5,84.518],[-20.88,84.66],[-20.88,85.902],[-20.02,86.188],[-19.734,85.328],[-16.918,85.568],[-16.966,84.708],[-19.114,82.895],[-19.591,82.035],[-19.495,81.272],[-16.441,81.415],[-16.107,80.509],[-13.72,80.509],[-12.862,79.076],[-11.239,79.984],[-10.809,83.849],[-9.473,83.897],[-8.996,84.566],[-6.848,84.852],[-6.085,85.949],[-3.364,86.236],[-3.078,84.756],[-3.698,81.701],[-6.037,79.554],[-6.323,78.504],[-3.603,78.456],[-2.219,78.027],[-0.406,77.359],[0.549,77.74],[2.84,77.645],[4.94,77.645],[6.133,78.027],[8.567,78.6],[9.14,79.458],[11.096,79.458],[11.43,80.413],[13.149,80.413],[13.435,80.986],[14.628,81.033],[14.628,80.413],[15.868,79.936],[15.868,78.934],[16.346,81.272],[18.064,81.32],[18.064,80.222],[17.539,80.174],[17.539,78.074],[18.446,78.552],[19.973,80.031],[21.452,80.079],[21.452,82.704],[20.976,83.42],[20.976,84.518],[19.782,84.804],[19.83,85.71],[22.026,86.188],[22.884,86.999],[24.698,87.047],[25.414,87.953],[27.275,87.953],[30.855,87.953],[31.523,86.999],[32.764,87.047],[32.286,88.335],[31.761,88.956],[32.048,90.436],[36.915,90.818],[37.583,98.787],[39.97,102.366],[42.738,103.368],[44.552,106.185],[54.431,117.018],[58.582,120.216],[67.889,127.518],[73.711,130.429],[85.165,134.294],[88.458,136.013],[91.179,136.013],[97.717,142.026],[98.338,144.89],[104.494,147.849],[107.262,150],[211.975,150],[211.975,-151.69],[-210.962,-153.764]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.952941176471,0.956862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[223.279,171.151],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 62","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":".blue100","cl":"blue100","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[57.694,49,0],"ix":2,"l":2},"a":{"a":0,"k":[232.123,166.357,0],"ix":1,"l":2},"s":{"a":0,"k":[79.922,79.922,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-206,148.684],[206,148.684],[206,-148.684],[-206,-148.684]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.823529411765,0.890196078431,0.988235294118,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[223.279,172.467],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 63","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue900","cl":"blue900","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[101.086,101.086,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.34,0.34],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":10,"s":[300,300]},{"t":25,"s":[294,294]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":25,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[98.741,98.741],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":3,"nm":"Pale Orange Solid 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[206,150,0],"ix":1,"l":2},"s":{"a":0,"k":[72,72,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Blue Circle Outlines","parent":2,"sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":10,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[206,150,0],"ix":2,"l":2},"a":{"a":0,"k":[64.361,64.36,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":0,"s":[66.714,66.714,100]},{"t":45,"s":[208.333,208.333,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-34.164],[34.165,0],[0,34.165],[-34.165,0]],"o":[[0,34.165],[-34.165,0],[0,-34.164],[34.165,0]],"v":[[61.861,-0.001],[0,61.861],[-61.861,-0.001],[0,-61.861]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.829000016755,0.885999971278,0.955999995213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[70]},{"t":21,"s":[100]}],"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[64.361,64.361],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-34.164],[34.165,0],[0,34.165],[-34.165,0]],"o":[[0,34.165],[-34.165,0],[0,-34.164],[34.165,0]],"v":[[61.861,-0.001],[0,61.861],[-61.861,-0.001],[0,-61.861]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0,0,0,1]},{"t":21,"s":[0.552941203117,0.670588254929,0.921568632126,1]}],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[20]},{"t":21,"s":[50]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[64.361,64.361],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":585,"st":-15,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"CircleMatte 4","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[82.705,0],[0,-82.705],[-82.705,0],[0,82.705]],"o":[[-82.705,0],[0,82.705],[82.705,0],[0,-82.705]],"v":[[-0.125,-149.938],[-149.875,-0.188],[-0.125,149.562],[149.625,-0.188]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":-1,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[15.4,0],[0,0],[0,15.4],[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0]],"o":[[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0],[15.4,0],[0,0],[0,15.4]],"v":[[228,150],[-238,150],[-266,122],[-266,-122],[-238,-150],[228,-150],[256,-122],[256,122]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":0,"nm":"Approximate_ON_LT","tt":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":0,"k":[307.2,307.2,100],"ix":6,"l":2}},"ao":0,"w":117,"h":98,"ip":0,"op":585,"st":-15,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/PermissionController/res/raw/fine_loc_off.json b/PermissionController/res/raw/fine_loc_off.json
new file mode 100644
index 000000000..3aa81bab5
--- /dev/null
+++ b/PermissionController/res/raw/fine_loc_off.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":91,"w":300,"h":300,"nm":"Location_Accuracy_Exact_OFF_LT","ddd":0,"assets":[{"id":"comp_0","nm":"Exact_OFF_LT","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.5,49,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":15,"s":[102,102,100]},{"t":60,"s":[80,80,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Location Pointer Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.298,63.254,0],"ix":2,"l":2},"a":{"a":0,"k":[18.25,51.679,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.967,0.967,0.2],"y":[1,1,1]},"o":{"x":[0.499,0.499,0.4],"y":[0,0,0]},"t":24,"s":[54,54,100]},{"t":42,"s":[10,10,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.551,0],[0,-3.55],[3.55,0],[0,3.551]],"o":[[3.55,0],[0,3.551],[-3.551,0],[0,-3.55]],"v":[[0.001,-6.429],[6.428,-0.001],[0.001,6.429],[-6.428,-0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":4,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[18.249,18.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[9.952,0],[0,-9.951],[0,0],[0,13.5]],"o":[[-9.952,0],[0,13.5],[0,0],[0,-9.951]],"v":[[0,-25.715],[-18,-7.715],[0,25.715],[18,-7.715]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":4,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196080506,0.305882364511,0.65098041296,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[18.25,25.965],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":43,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Location bottom Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.298,63.254,0],"ix":2,"l":2},"a":{"a":0,"k":[13.432,6.364,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.967,0.967,0.2],"y":[1,1,1]},"o":{"x":[0.499,0.499,0.4],"y":[0,0,0]},"t":24,"s":[54,54,100]},{"t":42,"s":[10,10,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.135],[6.037,0],[0,2.134],[-6.038,0]],"o":[[0,2.134],[-6.038,0],[0,-2.135],[6.037,0]],"v":[[10.933,0],[0.001,3.863],[-10.933,0],[0.001,-3.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[13.432,6.364],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.135],[6.037,0],[0,2.134],[-6.038,0]],"o":[[0,2.134],[-6.038,0],[0,-2.135],[6.037,0]],"v":[[10.933,0],[0.001,3.863],[-10.933,0],[0.001,-3.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[13.432,6.364],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":43,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue900","cl":"blue900","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.878],[0,1.37]],"o":[[-1.734,0],[0,1.37],[2.201,-1.878],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[24.4,206.353],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 16","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.055],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.879],[0,-2.055]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[38.555,165.221],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 17","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[49.115,205.645],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 18","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[105.514,213.902],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 19","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.055],[-2.2,-1.878],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.878],[0,-2.055]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[171.789,167.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 20","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[368.126,111.436],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 21","np":4,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[281.531,116.994],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 22","np":4,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.055],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.055]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[312.438,125.251],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 23","np":4,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[329.856,217.762],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 24","np":4,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Pins_Orange","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.703,137.9],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 15","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.744],[0,4.128],[3.303,-0.744]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[381.288,270.794],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 25","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[368.126,210.482],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 26","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.057],[-2.201,-1.878],[0,1.37]],"o":[[-1.734,0],[0,1.37],[2.2,-1.878],[0,-2.057]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[350.754,184.962],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 27","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[183.575,115.565],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 28","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey500","cl":"grey500","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[65.394,-87.762],[21.956,-25.29],[-65.394,87.762]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[77.34,207.456],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-29.617,35.822],[12.473,-5.331],[29.618,-35.821]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[199.566,278.889],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-8.219,9.823],[8.218,-9.823]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[201.146,227.853],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-33.868,-25.038],[8.293,9.519],[33.867,25.038]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[209.364,226.554],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[19.167,15.54],[-19.167,-15.54]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[119.548,75.138],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-72.23,5.545],[32.282,-5.545],[55.773,-5.545],[72.23,5.545]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[387.97,276.339],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[6.098,-157.973],[9.854,-43.904],[-9.854,53.949],[-8.736,83.862],[-3.121,157.973]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[393.327,169.155],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-82.146,-18.015],[-17.286,18.015],[82.146,18.015]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[364.737,26.721],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[142.734,59.599],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[108.816,73.718],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 13","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[105.449,70.281],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 14","np":2,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-61.089,79.79],[-10.563,22.215],[30.571,-28.773],[46.604,-61.512],[61.089,-79.79]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[197.779,251.592],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 29","np":2,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-181.176,-130.71],[-103.059,-67.44],[-30.743,-4.641],[-18.758,22.842],[23.397,30.81],[65.134,54.857],[89.912,66.865],[181.176,130.71]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[252.512,144.213],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 30","np":2,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-197.801,-135.93],[-169.578,-81.272],[-25.091,29.988],[22.053,51.34],[69.798,92.618],[93.582,100.74],[139.02,95.547],[197.801,135.931]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[245.942,149.433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 31","np":2,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-43.312,-42.235],[22.43,13.494],[43.312,42.235]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[45.812,287.214],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 32","np":2,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[18.949,23.783],[-18.949,-23.783]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[109.621,305.667],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 33","np":2,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-161.26,59.553],[-73.09,134.19],[-18.949,92.406],[161.26,-134.191]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[163.76,147.693],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 34","np":2,"cix":2,"bm":0,"ix":17,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-170.929,-3.155],[-48.146,106.479],[7.347,155.978],[56.846,100.678],[93.971,24.495],[114.081,-34.03],[140.8,-83.029],[163.001,-98.674],[170.928,-155.978]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[176.522,171.15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 35","np":2,"cix":2,"bm":0,"ix":18,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-128.003,-94.842],[128.003,94.842]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[128.569,230.353],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 36","np":2,"cix":2,"bm":0,"ix":19,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-143.858,-121.852],[-139.604,-101.518],[143.858,121.852]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[169.948,211.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 37","np":2,"cix":2,"bm":0,"ix":20,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-101.126,-61.295],[6.382,43.506],[25.329,50.08],[48.533,50.08],[101.126,61.295]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[339.523,72.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 38","np":2,"cix":2,"bm":0,"ix":21,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[75.549,33.199],[0.527,16.447],[-24.997,16.447],[-75.549,-33.199]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[358.139,131.247],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 39","np":2,"cix":2,"bm":0,"ix":22,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[56.267,11.975],[-22.236,-5.401],[-51.24,-5.401],[-56.267,-11.975]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[381.289,156.961],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 40","np":2,"cix":2,"bm":0,"ix":23,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-23.396,-110.585],[23.396,-69.624],[14.115,-1.562],[14.115,110.585]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[301.625,214.61],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 41","np":2,"cix":2,"bm":0,"ix":24,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-152.301,-106.862],[-115.301,-91.911],[68.768,50.786],[98.546,63.161],[152.3,106.862]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[163.44,175.022],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 43","np":2,"cix":2,"bm":0,"ix":25,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[137.091,-155.846],[25.716,1.527],[6.767,17.403],[-27.65,61.101],[-76.956,108.281],[-137.091,155.846]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[205.333,169.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 44","np":2,"cix":2,"bm":0,"ix":26,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-56.46,-44.426],[56.46,44.426]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[221.768,59.599],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 45","np":2,"cix":2,"bm":0,"ix":27,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-157.626,-125.059],[157.625,125.059]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[276.914,138.562],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 46","np":2,"cix":2,"bm":0,"ix":28,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[128.583,-155.739],[-82.951,117.438],[-128.583,155.739]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[173.622,164.445],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 47","np":2,"cix":2,"bm":0,"ix":29,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[123.363,-155.846],[-123.363,155.846]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[149.452,169.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 48","np":2,"cix":2,"bm":0,"ix":30,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[119.688,-154.492],[-119.689,154.493]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[128.376,167.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 49","np":2,"cix":2,"bm":0,"ix":31,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[94.165,-121.947],[-94.165,121.948]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[102.853,137.12],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 50","np":2,"cix":2,"bm":0,"ix":32,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".green100","cl":"green100","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-3.871,-11.834],[12.775,-0.847],[2.691,11.834],[-12.775,-0.336]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843137255,0.917647058824,0.839215686275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[260.038,79.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 54","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-35.787,-24.613],[-37.505,-22.753],[-34.63,-21.167],[-31.031,-23.281],[-9.88,-6.238],[-12.139,-3.2],[24.52,24.613],[37.505,7.844],[1.732,-21.035],[3.318,-23.281],[1.924,-24.613]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843137255,0.917647058824,0.839215686275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[231.647,43.549],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 55","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[37.533,-30.467],[28.951,-30.467],[20.804,-26.143],[14.324,-20.586],[4.618,-16.81],[1.118,-16.23],[-6.173,-7.064],[-37.533,17.139],[-22.607,30.467],[8.069,1.113],[26.699,-16.712]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843137255,0.917647058824,0.839215686275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[150.984,247.163],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 56","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey100","cl":"grey100","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-206,150],[206,150],[206,-150],[-206,-150]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.952941176471,0.956862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[220.851,168.936],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 58","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":3,"nm":"Exact Light Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64.082,57.55,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[48.646,48.646,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue900","cl":"blue900","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[101.086,101.086,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.833,0.833],"y":[0.833,0.833]},"o":{"x":[0.66,0.66],"y":[0,0]},"t":0,"s":[294,294]},{"t":10,"s":[300,300]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.66],"y":[0]},"t":0,"s":[6]},{"t":10,"s":[0]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[98.741,98.741],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":1,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,149,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.2,0.2,0.6],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.8],"y":[0,0,0]},"t":0,"s":[306,306,100]},{"t":45,"s":[240,240,100]}],"ix":6,"l":2}},"ao":0,"sw":117,"sh":98,"sc":"#a77b57","ip":0,"op":585,"st":-15,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"CircleMatte","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[82.705,0],[0,-82.705],[-82.705,0],[0,82.705]],"o":[[-82.705,0],[0,82.705],[82.705,0],[0,-82.705]],"v":[[-0.125,-149.938],[-149.875,-0.188],[-0.125,149.562],[149.625,-0.188]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":-1,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[15.4,0],[0,0],[0,15.4],[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0]],"o":[[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0],[15.4,0],[0,0],[0,15.4]],"v":[[228,150],[-238,150],[-266,122],[-266,-122],[-238,-150],[228,-150],[256,-122],[256,122]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Exact_OFF_LT","tt":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":0,"k":[307.2,307.2,100],"ix":6,"l":2}},"ao":0,"w":117,"h":98,"ip":0,"op":585,"st":-15,"bm":0}],"markers":[{"tm":91,"cm":"","dr":0}]} \ No newline at end of file
diff --git a/PermissionController/res/raw/fine_loc_on.json b/PermissionController/res/raw/fine_loc_on.json
new file mode 100644
index 000000000..344942361
--- /dev/null
+++ b/PermissionController/res/raw/fine_loc_on.json
@@ -0,0 +1 @@
+{"v":"5.9.0","fr":60,"ip":0,"op":91,"w":300,"h":300,"nm":"Location_Accuracy_Exact_ON_LT","ddd":0,"assets":[{"id":"comp_0","nm":"Exact_ON_LT","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.5,49,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":15,"s":[80,80,100]},{"t":60,"s":[102,102,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Location Pointer Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.298,63.254,0],"ix":2,"l":2},"a":{"a":0,"k":[18.25,51.679,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.552,0.552,0.667],"y":[1,1,1]},"o":{"x":[0.046,0.046,0.333],"y":[0,0,0]},"t":28,"s":[20,20,100]},{"i":{"x":[0.468,0.468,0.667],"y":[1,1,1]},"o":{"x":[0.441,0.441,0.333],"y":[0,0,0]},"t":42,"s":[58,58,100]},{"t":60,"s":[54,54,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.551,0],[0,-3.55],[3.55,0],[0,3.551]],"o":[[3.55,0],[0,3.551],[-3.551,0],[0,-3.55]],"v":[[0.001,-6.429],[6.428,-0.001],[0.001,6.429],[-6.428,-0.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":4,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[18.249,18.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[9.952,0],[0,-9.951],[0,0],[0,13.5]],"o":[[-9.952,0],[0,13.5],[0,0],[0,-9.951]],"v":[[0,-25.715],[-18,-7.715],[0,25.715],[18,-7.715]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":4,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196080506,0.305882364511,0.65098041296,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[18.25,25.965],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":28,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Location bottom Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[58.298,63.254,0],"ix":2,"l":2},"a":{"a":0,"k":[13.432,6.364,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.001,0.001,0.001],"y":[0,0,0]},"t":28,"s":[20,20,100]},{"t":41,"s":[54,54,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.135],[6.037,0],[0,2.134],[-6.038,0]],"o":[[0,2.134],[-6.038,0],[0,-2.135],[6.037,0]],"v":[[10.933,0],[0.001,3.863],[-10.933,0],[0.001,-3.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[13.432,6.364],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.135],[6.037,0],[0,2.134],[-6.038,0]],"o":[[0,2.134],[-6.038,0],[0,-2.135],[6.037,0]],"v":[[10.933,0],[0.001,3.863],[-10.933,0],[0.001,-3.863]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[13.432,6.364],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":28,"op":600,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".blue900","cl":"blue900","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.878],[0,1.37]],"o":[[-1.734,0],[0,1.37],[2.201,-1.878],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[24.4,206.353],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 16","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.055],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.879],[0,-2.055]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[38.555,165.221],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 17","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[49.115,205.645],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 18","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[105.514,213.902],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 19","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.055],[-2.2,-1.878],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.878],[0,-2.055]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[171.789,167.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 20","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[368.126,111.436],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 21","np":4,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[281.531,116.994],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 22","np":4,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.055],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.055]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[312.438,125.251],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 23","np":4,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[329.856,217.762],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 24","np":4,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Pins_Orange","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.703,137.9],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 15","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.744],[0,4.128],[3.303,-0.744]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[381.288,270.794],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 25","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.2,-1.879],[0,1.371]],"o":[[-1.733,0],[0,1.371],[2.201,-1.879],[0,-2.056]],"v":[[0,-4.128],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[368.126,210.482],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 26","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.733,0],[0,-2.057],[-2.201,-1.878],[0,1.37]],"o":[[-1.734,0],[0,1.37],[2.2,-1.878],[0,-2.057]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.128],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[350.754,184.962],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 27","np":4,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.734,0],[0,-2.056],[-2.201,-1.879],[0,1.371]],"o":[[-1.734,0],[0,1.371],[2.2,-1.879],[0,-2.056]],"v":[[0,-4.129],[-3.303,-0.743],[0,4.129],[3.303,-0.743]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.948999980852,0.6,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[183.575,115.565],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 28","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey500","cl":"grey500","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[65.394,-87.762],[21.956,-25.29],[-65.394,87.762]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[77.34,207.456],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-29.617,35.822],[12.473,-5.331],[29.618,-35.821]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[199.566,278.889],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-8.219,9.823],[8.218,-9.823]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[201.146,227.853],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 6","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-33.868,-25.038],[8.293,9.519],[33.867,25.038]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[209.364,226.554],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[19.167,15.54],[-19.167,-15.54]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[119.548,75.138],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":2,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-72.23,5.545],[32.282,-5.545],[55.773,-5.545],[72.23,5.545]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[387.97,276.339],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":2,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[6.098,-157.973],[9.854,-43.904],[-9.854,53.949],[-8.736,83.862],[-3.121,157.973]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[393.327,169.155],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-82.146,-18.015],[-17.286,18.015],[82.146,18.015]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[364.737,26.721],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":2,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[142.734,59.599],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":2,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[108.816,73.718],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 13","np":2,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-50.193,59.098],[50.193,-59.098]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[105.449,70.281],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 14","np":2,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-61.089,79.79],[-10.563,22.215],[30.571,-28.773],[46.604,-61.512],[61.089,-79.79]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[197.779,251.592],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 29","np":2,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-181.176,-130.71],[-103.059,-67.44],[-30.743,-4.641],[-18.758,22.842],[23.397,30.81],[65.134,54.857],[89.912,66.865],[181.176,130.71]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[252.512,144.213],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 30","np":2,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-197.801,-135.93],[-169.578,-81.272],[-25.091,29.988],[22.053,51.34],[69.798,92.618],[93.582,100.74],[139.02,95.547],[197.801,135.931]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[245.942,149.433],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 31","np":2,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-43.312,-42.235],[22.43,13.494],[43.312,42.235]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[45.812,287.214],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 32","np":2,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[18.949,23.783],[-18.949,-23.783]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[109.621,305.667],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 33","np":2,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-161.26,59.553],[-73.09,134.19],[-18.949,92.406],[161.26,-134.191]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[163.76,147.693],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 34","np":2,"cix":2,"bm":0,"ix":17,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-170.929,-3.155],[-48.146,106.479],[7.347,155.978],[56.846,100.678],[93.971,24.495],[114.081,-34.03],[140.8,-83.029],[163.001,-98.674],[170.928,-155.978]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[176.522,171.15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 35","np":2,"cix":2,"bm":0,"ix":18,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-128.003,-94.842],[128.003,94.842]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[128.569,230.353],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 36","np":2,"cix":2,"bm":0,"ix":19,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-143.858,-121.852],[-139.604,-101.518],[143.858,121.852]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[169.948,211.078],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 37","np":2,"cix":2,"bm":0,"ix":20,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-101.126,-61.295],[6.382,43.506],[25.329,50.08],[48.533,50.08],[101.126,61.295]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[339.523,72.477],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 38","np":2,"cix":2,"bm":0,"ix":21,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[75.549,33.199],[0.527,16.447],[-24.997,16.447],[-75.549,-33.199]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[358.139,131.247],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 39","np":2,"cix":2,"bm":0,"ix":22,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[56.267,11.975],[-22.236,-5.401],[-51.24,-5.401],[-56.267,-11.975]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[381.289,156.961],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 40","np":2,"cix":2,"bm":0,"ix":23,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-23.396,-110.585],[23.396,-69.624],[14.115,-1.562],[14.115,110.585]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[301.625,214.61],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 41","np":2,"cix":2,"bm":0,"ix":24,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-152.301,-106.862],[-115.301,-91.911],[68.768,50.786],[98.546,63.161],[152.3,106.862]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[163.44,175.022],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 43","np":2,"cix":2,"bm":0,"ix":25,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[137.091,-155.846],[25.716,1.527],[6.767,17.403],[-27.65,61.101],[-76.956,108.281],[-137.091,155.846]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[205.333,169.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 44","np":2,"cix":2,"bm":0,"ix":26,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-56.46,-44.426],[56.46,44.426]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[221.768,59.599],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 45","np":2,"cix":2,"bm":0,"ix":27,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-157.626,-125.059],[157.625,125.059]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[276.914,138.562],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 46","np":2,"cix":2,"bm":0,"ix":28,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[128.583,-155.739],[-82.951,117.438],[-128.583,155.739]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[173.622,164.445],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 47","np":2,"cix":2,"bm":0,"ix":29,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[123.363,-155.846],[-123.363,155.846]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[149.452,169.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 48","np":2,"cix":2,"bm":0,"ix":30,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[119.688,-154.492],[-119.689,154.493]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[128.376,167.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 49","np":2,"cix":2,"bm":0,"ix":31,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[94.165,-121.947],[-94.165,121.948]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[102.853,137.12],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 50","np":2,"cix":2,"bm":0,"ix":32,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[7.927,-159.713],[-0.193,-105.055],[-1.741,54.527],[-7.927,159.714]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[344.164,173.216],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 42","np":2,"cix":2,"bm":0,"ix":33,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-10.143,-7.085],[10.143,7.085]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.505882352941,0.525490196078,0.545098039216,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[89.154,175.08],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":34,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":".green100","cl":"green100","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-3.871,-11.834],[12.775,-0.847],[2.691,11.834],[-12.775,-0.336]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843137255,0.917647058824,0.839215686275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[260.038,79.995],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 54","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[-35.787,-24.613],[-37.505,-22.753],[-34.63,-21.167],[-31.031,-23.281],[-9.88,-6.238],[-12.139,-3.2],[24.52,24.613],[37.505,7.844],[1.732,-21.035],[3.318,-23.281],[1.924,-24.613]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843137255,0.917647058824,0.839215686275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[231.647,43.549],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 55","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[37.533,-30.467],[28.951,-30.467],[20.804,-26.143],[14.324,-20.586],[4.618,-16.81],[1.118,-16.23],[-6.173,-7.064],[-37.533,17.139],[-22.607,30.467],[8.069,1.113],[26.699,-16.712]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.807843137255,0.917647058824,0.839215686275,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[150.984,247.163],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 56","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":".grey100","cl":"grey100","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[230.884,168.455,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-206,150],[206,150],[206,-150],[-206,-150]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.949019607843,0.952941176471,0.956862745098,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[220.851,168.936],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 58","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":3,"nm":"Exact Light Outlines","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64.082,57.55,0],"ix":2,"l":2},"a":{"a":0,"k":[231.35,167.715,0],"ix":1,"l":2},"s":{"a":0,"k":[48.646,48.646,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue900","cl":"blue900","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[101.086,101.086,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.34,0.34],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0.167,0.167]},"t":10,"s":[300,300]},{"t":25,"s":[294,294]}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.090196078431,0.305882352941,0.650980392157,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.34],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":25,"s":[6]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[98.741,98.741],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":1,"nm":"Scale Controller","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,149,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0,0,0.2],"y":[1,1,1]},"o":{"x":[0.4,0.4,0.4],"y":[0,0,0]},"t":0,"s":[240,240,100]},{"t":45,"s":[306,306,100]}],"ix":6,"l":2}},"ao":0,"sw":117,"sh":98,"sc":"#a77b57","ip":0,"op":585,"st":-15,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"CircleMatte 3","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":0,"k":{"i":[[82.705,0],[0,-82.705],[-82.705,0],[0,82.705]],"o":[[-82.705,0],[0,82.705],[82.705,0],[0,-82.705]],"v":[[-0.125,-149.938],[-149.875,-0.188],[-0.125,149.562],[149.625,-0.188]],"c":true},"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":-1,"ix":4},"nm":"Mask 1"}],"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[15.4,0],[0,0],[0,15.4],[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0]],"o":[[0,0],[-15.4,0],[0,0],[0,-15.4],[0,0],[15.4,0],[0,0],[0,15.4]],"v":[[228,150],[-238,150],[-266,122],[-266,-122],[-238,-150],[228,-150],[256,-122],[256,122]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1800,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":0,"nm":"Exact_ON_LT","tt":1,"refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[150,150,0],"ix":2,"l":2},"a":{"a":0,"k":[58.5,49,0],"ix":1,"l":2},"s":{"a":0,"k":[307.2,307.2,100],"ix":6,"l":2}},"ao":0,"w":117,"h":98,"ip":0,"op":585,"st":-15,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/PermissionController/res/values-af/strings.xml b/PermissionController/res/values-af/strings.xml
index 9f4b10a4b..5ebf6b60f 100644
--- a/PermissionController/res/values-af/strings.xml
+++ b/PermissionController/res/values-af/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Hou \"Terwyl die program gebruik word\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Hou \"Net hierdie keer\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Meer inligting"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Gee toegang tot alle foto’s"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Kies foto’s"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Kies meer foto’s"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Moenie meer foto’s kies nie"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Moet steeds nie toelaat nie"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Maak toe"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> van <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dag}other{# dae}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 uur}other{# uur}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min.}other{# min.}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sek.}other{# sek.}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dag}other{# dae}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# uur}other{# uur}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min.}other{# min.}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sek.}other{# sek.}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Enige toestemming"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Enige tyd"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Afgelope 7 dae"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Afgelope 24 uur"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Afgelope 1 uur"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Afgelope 15 minute"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Afgelope 1 minuut"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Afgelope # dag}other{Afgelope # dae}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Afgelope # uur}other{Afgelope # uur}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Afgelope # minuut}other{Afgelope # minute}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Geen toestemminggebruike nie"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Mees onlangse toegang in enige tydperk"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Mees onlangse toegang in afgelope 7 dae"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Toestemminggebruik in afgelope 1 uur"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Toestemminggebruik in afgelope 15 minute"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Toestemminggebruik in afgelope 1 minuut"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Nie in die afgelope 24 uur gebruik nie"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Nie in die afgelope 7 dae gebruik nie"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Nie in die afgelope # dag gebruik nie}other{Nie in die afgelope # dae gebruik nie}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Nie in die afgelope # uur gebruik nie}other{Nie in die afgelope # uur gebruik nie}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Gebruik deur 1 program}other{Gebruik deur # programme}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Sien alles in Dashboard"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Gefiltreer volgens: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Laat net toegang tot media toe"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Laat altyd toe"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Laat net toe terwyl jy program gebruik"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Laat alle foto’s toe"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Laat geselekteerde foto’s toe"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Vra elke keer"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Moenie toelaat nie"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Presiese ligging"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nie toegelaat nie"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Sien meer programme wat toegang tot alle lêers het"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dag}other{# dae}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 uur}other{# uur}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuut}other{# minute}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekonde}other{# sekondes}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# uur}other{# uur}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuut}other{# minute}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekonde}other{# sekondes}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Toestemmingonthounotas"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ongebruikte program"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ongebruikte programme"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Stel <xliff:g id="APP_NAME">%1$s</xliff:g> as jou navigasieprogram?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Geen toestemmings is nodig nie"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> sal interaksie met jou kennisgewings mag hê en toegang kry tot jou Foon-, SMS-, Kontakte- en Kalender-toestemmings."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> sal toegelaat word om interaksie met jou kennisgewings te hê en sal toegang tot jou Foon-, SMS-, Kontakte-, Mikrofoon, en Toestelle in die Omtrek-toestemmings hê."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> sal toegelaat word om interaksie met jou kennisgewings te hê en jou programme na die gekoppelde toestel te stroom."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> sal toegelaat word om inhoud na toestelle in die omtrek te stroom."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Hierdie diens deel jou foto\'s, media en kennisgewings van jou foon af na ander toestelle toe."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Huidige verstek"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Moenie weer vra nie"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Stel as verstek"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot &lt;b&gt;foto\'s, video\'s, musiek, oudio en ander lêers&lt;/b&gt; op hierdie toestel?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot musiek en oudio op hierdie toestel?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot foto\'s en video\'s op hierdie toestel?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Gee &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang tot meer foto’s?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om oudio op te neem?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Die program sal net kan oudio opneem terwyl jy die program gebruik"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Laat &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toe om oudio op te neem?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Wys ’n boodskap wanneer programme toegang het tot teks, prente of ander inhoud wat jy gekopieer het"</string>
<string name="show_password_title" msgid="2877269286984684659">"Wys wagwoorde"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Wys karakters kortliks terwyl jy tik"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Hierdie app het verklaar dat dit dalk <xliff:g id="PERMISSION_NAME">%s</xliff:g>-data met derde party sal deel"</string>
</resources>
diff --git a/PermissionController/res/values-am/strings.xml b/PermissionController/res/values-am/strings.xml
index abe78d1a8..c24c9b629 100644
--- a/PermissionController/res/values-am/strings.xml
+++ b/PermissionController/res/values-am/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"«መተግበሪያው በጥቅም ላይ እያለ» አቆይ"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"«አሁን ብቻ»ን አቆይ"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"ተጨማሪ መረጃ"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"ለሁሉም ፎቶዎች መዳረሻ ፍቀድ"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ፎቶዎችን ምረጥ"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"ተጨማሪ ፎቶዎችን ምረጥ"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"ተጨማሪ ፎቶዎችን አትምረጥ"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"ለማንኛውም አትፍቀድ"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"አሰናብት"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> ከ<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ቀን}one{# ቀኖች}other{# ቀኖች}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ሰዓት}one{# ሰዓቶች}other{# ሰዓቶች}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 ደቂቃ}one{# ደቂቃዎች}other{# ደቂቃዎች}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 ሴኮ}one{# ሰከንዶች}other{# ሰከንዶች}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ቀን}one{# ቀን}other{# ቀናት}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ሰዓት}one{# ሰዓት}other{# ሰዓታት}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# ደቂቃ}one{# ደቂቃ}other{# ደቂቃዎች}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# ሰከንድ}one{# ሰከንድ}other{# ሰከንዶች}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ማናቸውም ፈቃድ"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"በማንኛውም ጊዜ"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"ባለፉት 7 ቀኖች"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"ባለፉት 24 ሰዓቶች"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"ባለፈው 1 ሰዓት"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"ባለፉት 15 ደቂቃዎች"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"ባለፈው 1 ደቂቃ"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{ያለፈው # ቀን}one{ያለፈው # ቀን}other{ያለፉት # ቀናት}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{ባለፈው # ሰዓት}one{ባለፈው # ሰዓት}other{ባለፉት # ሰዓታት}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{ባለፈው # ደቂቃ}one{ባለፈው # ደቂቃ}other{ባለፉት # ደቂቃዎች}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"ምንም ፈቃድ አጠቃቀሞች የሉም"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"በማናቸውም ጊዜ ያለ የቅርብ ጊዜ መዳረስ"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"ባለፉት 7 ቀናት በጣም የቅርብ ጊዜ መዳረስ"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"ባለፈው 1 ሰዓት የፈቃድ አጠቃቀም"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"ባለፉት 15 ደቂቃዎች ውስጥ የፈቃድ አጠቃቀም"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"ባለፈው 1 ደቂቃ የፈቃድ አጠቃቀም"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"ባለፉት 24 ሰዓታት ውስጥ ስራ ላይ አልዋለም"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"ባለፉት 7 ቀናት ውስጥ ስራ ላይ አልዋለም"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{ባለፈው # ቀን ውስጥ ስራ ላይ አልዋለም}one{ባለፈው # ቀን ውስጥ ስራ ላይ አልዋለም}other{ባለፉት # ቀናት ውስጥ ስራ ላይ አልዋለም}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{ባለፈው # ሰዓት ውስጥ ስራ ላይ አልዋለም}one{ባለፈው # ሰዓት ውስጥ ስራ ላይ አልዋለም}other{ባለፉት # ሰዓታት ውስጥ ስራ ላይ አልዋለም}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{በ1 መተግበሪያ ስራ ላይ ውሏል}one{በ# መተግበሪያዎች ስራ ላይ ውለዋል}other{በ# መተግበሪያዎች ስራ ላይ ውለዋል}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ሁሉንም በዳሽ ቦርድ ውስጥ ይመልከቱ"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"የተጣራው በ፦ <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"ለሚዲያ ብቻ መዳረሻ ይፍቀዱ"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"ሁልጊዜ ፍቀድ"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"መተግበሪያዎን እየተጠቀሙ እያሉ ብቻ ይፍቀዱ"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"ሁሉንም ፎቶዎች ፍቀድ"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"የተመረጡትን ፎቶዎች ፍቀድ"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"ሁልጊዜ ጠይቅ"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"አትፍቀድ"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ትክክለኛ አካባቢ"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"አይፈቀድም"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"ሁሉንም ፋይሎች መድረስ የሚችሉ ተጨማሪ መተግበሪያዎችን ይመልከቱ"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ቀን}one{# ቀኖች}other{# ቀኖች}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ሰዓት}one{# ሰዓቶች}other{# ሰዓቶች}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 ደቂቃ}one{# ደቂቃዎች}other{# ደቂቃዎች}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 ሰከንድ}one{# ሰከንዶች}other{# ሰከንዶች}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ሰዓት}one{# ሰዓት}other{# ሰዓታት}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# ደቂቃ}one{# ደቂቃ}other{# ደቂቃዎች}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# ሰከንድ}one{# ሰከንድ}other{# ሰከንዶች}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"የፈቃድ አስታዋሾች"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ጥቅም ላይ ያልዋለ መተግበሪያ"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ጥቅም ላይ ያልዋሉ መተግበሪያዎች"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> እንደ ነባሪ የአሰሳ መተግበሪያዎ ይዋቀር?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"ምንም ፈቃዶች አያስፈልጉም"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> ከማሳወቂያዎችዎ ጋር መስተጋብር እንዲፈጥር እና የእርስዎን ስልክ፣ ኤስኤምኤስ፣ እውቂያዎች እና የቀን መቁጠሪያ ፈቃዶች እንዲደርስ ይፈቀድለታል።"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> ከማሳወቂያዎችዎ ጋር መስተጋብር እንዲፈጥር እና የእርስዎን ስልክ፣ ኤስኤምኤስ፣ ዕውቂያዎች፣ ማይክሮፎን እና በአቅራቢያ ያሉ መሣሪያዎች ፈቃዶችን እንዲደርስ ይፈቀድለታል።"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> ከማሳወቂያዎችዎ ጋር መስተጋብር እንዲፈጥር እና መተግበሪያዎችዎን ወደ የተገናኘ መሣሪያ እንዲለቅ ይፈቀድለታል።"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> በአቅራቢያ ላሉ መሣሪያዎች ይዘት በዥረት እንዲለቅ ይፈቀድለታል።"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"ይህ አገልግሎት የእርስዎን ፎቶዎች፣ ሚዲያዎች እና ማሳወቂያዎች ከስልክዎ ለሌሎች መሣሪያዎች ያጋራል።"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"አሁን ያለ ነባሪ"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"ዳግም አትጠይቅ"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"እንደ ነባሪ አዘጋጅ"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; በመሣሪያዎ ላይ ያሉ &lt;b&gt;ፎቶዎችን፣ ቪዲዮዎችን፣ ሙዚቃን፣ ኦዲዮን፣ ቪዲዮዎችን እና ሌሎች ፋይሎችን&lt;/b&gt; ዘንድ እንዲደርስ ይፈቀድለት?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; በዚህ መሣሪያ ላይ ያለ ሙዚቃን እና ሌሎች የኦዲዮ ፋይሎችን እንዲደርስ ይፈቀድለት?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; በዚህ መሣሪያ ላይ ያሉ ፎቶዎችን እና ቪዲዮዎችን እንዲደርስ ይፈቀድለት?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"ለ&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ለተጨማሪ ፎቶዎች መዳረሻ ይሰጥ?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ኦዲዮን እንዲቀዳ ይፈቀድለት?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"መተግበሪያው ኦዲዮን መቅዳት የሚችለው መተግበሪያውን እርስዎ ሲጠቀሙበት ብቻ ነው"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ኦዲዮን እንዲቀዳ ይፈቀድለት?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"መተግበሪያዎች ጽሑፍን፣ ምስሎችን ወይም እርስዎ የቀዱትን ሌላ ይዘት ሲደርሱ መልዕክት አሳይ"</string>
<string name="show_password_title" msgid="2877269286984684659">"የይለፍ ቃላትን አሳይ"</string>
<string name="show_password_summary" msgid="1110166488865981610">"እርስዎ በሚተይቡበት ጊዜ ቁምፊዎችን በአጭሩ ያሳይ"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"ይህ መተግበሪያ የ<xliff:g id="PERMISSION_NAME">%s</xliff:g> ውሂብን ከሦስተኛ ወገኖች ጋር ሊያጋራ እንደሚችል ገልጿል"</string>
</resources>
diff --git a/PermissionController/res/values-ar/strings.xml b/PermissionController/res/values-ar/strings.xml
index 6a8f6aa9b..ee25097fd 100644
--- a/PermissionController/res/values-ar/strings.xml
+++ b/PermissionController/res/values-ar/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"عدم تغيير الإذن \"السماح فقط أثناء استخدام التطبيق\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"مواصلة استخدام الإذن \"هذه المرة فقط\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"معلومات أكثر"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"السماح بالوصول إلى كل الصور"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"اختيار صور"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"اختيار المزيد من الصور"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"عدم اختيار المزيد من الصور"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"عدم السماح على أي حال"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"رفض"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> من <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{يوم واحد}zero{# يوم}two{يومان}few{# أيام}many{# يومًا}other{# يوم}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{ساعة واحدة}zero{# ساعة}two{ساعتان}few{# ساعات}many{# ساعةً}other{# ساعة}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{دقيقة واحدة}zero{# دقيقة}two{دقيقتان}few{# دقائق}many{# دقيقةً}other{# دقيقة}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{ثانية واحدة}zero{# ثانية}two{ثانيتان}few{# ثوانٍ}many{# ثانيةً}other{# ثانية}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{يوم واحد}zero{# يوم}two{يومان}few{# أيام}many{# يومًا}other{# يوم}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{ساعة واحدة}zero{# ساعة}two{ساعتان}few{# ساعات}many{# ساعةً}other{# ساعة}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{دقيقة واحدة}zero{# دقيقة}two{دقيقتان}few{# دقائق}many{# دقيقة}other{# دقيقة}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{ثانية واحدة}zero{# ثانية}two{ثانيتان}few{# ثوانٍ}many{# ثانيةً}other{# ثانية}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"أيّ إذن"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"أي وقت"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"آخر 7 أيام"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"آخر 24 ساعة"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"آخر ساعة"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"آخر 15 دقيقة"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"الدقيقة الماضية"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{آخر يوم}zero{آخر # يوم}two{آخر يومَين}few{آخر # أيام}many{آخر # يومًا}other{آخر # يوم}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{آخر ساعة}zero{آخر # ساعة}two{آخر ساعتين}few{آخر # ساعات}many{آخر # ساعة}other{آخر # ساعة}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{آخر دقيقة}zero{آخر # دقيقة}two{آخر دقيقتين}few{آخر # دقائق}many{آخر # دقيقة}other{آخر # دقيقة}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"لم يتمّ استخدام الأذونات"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"آخر إذن وصول في أي وقت"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"الاستخدامات الحديثة لإذن الوصول خلال آخر 7 أيام"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"استخدام الإذن خلال الساعة الأخيرة"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"استخدام الإذن خلال آخر 15 دقيقة"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"استخدام الإذن خلال الدقيقة الماضية"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"لم يتم استخدام الإذن في آخر 24 ساعة."</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"لم يتم استخدام الإذن في آخر 7 أيام."</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{لم يتم استخدام الإذن في اليوم السابق.}zero{لم يتم استخدام الإذن في الأيام الـ # السابقة.}two{لم يتم استخدام الإذن في اليومين السابقين.}few{لم يتم استخدام الإذن في الأيام الـ # السابقة.}many{لم يتم استخدام الإذن في الأيام الـ # السابقة.}other{لم يتم استخدام الإذن في الأيام الـ # السابقة.}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{لم يتم استخدام الإذن خلال الساعة السابقة}zero{لم يتم استخدام الإذن خلال الساعات الـ # السابقة.}two{لم يتم استخدام الإذن خلال الساعتين السابقتين.}few{لم يتم استخدام الإذن خلال الساعات الـ # السابقة.}many{لم يتم استخدام الإذن خلال الساعات الـ # السابقة.}other{لم يتم استخدام الإذن خلال الساعات الـ # السابقة.}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{يستخدِمه تطبيق واحد.}zero{يستخدِمه # تطبيق.}two{يستخدِمه تطبيقان.}few{يستخدِمه # تطبيقات.}many{يستخدِمه # تطبيقًا.}other{يستخدِمه # تطبيق.}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"الاطّلاع على الكل في \"لوحة البيانات\""</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"تمّت الفلترة حسب: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"السماح بالوصول إلى الوسائط فقط"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"السماح طوال الوقت"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"السماح عند استخدام التطبيق فقط"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"السماح بكل الصور"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"السماح بصور محددة"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"الطلب في كل مرة"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"عدم السماح"</string>
<string name="precise_image_description" msgid="6349638632303619872">"الموقع الجغرافي الدقيق"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"الأذونات غير المسموح بها"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"الاطّلاع على تطبيقات أكثر يمكنها الوصول إلى كل الملفات"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{يوم واحد}zero{# يوم}two{يومان}few{# أيام}many{# يومًا}other{# يوم}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{ساعة واحدة}zero{# ساعة}two{ساعتان}few{# ساعات}many{# ساعةً}other{# ساعة}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{دقيقة واحدة}zero{# دقيقة}two{دقيقتان}few{# دقائق}many{# دقيقةً}other{# دقيقة}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{ثانية واحدة}zero{# ثانية}two{ثانيتان}few{# ثوانٍ}many{# ثانيةً}other{# ثانية}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{ساعة واحدة}zero{# ساعة}two{ساعتان}few{# ساعات}many{# ساعةً}other{# ساعة}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{دقيقة واحدة}zero{# دقيقة}two{دقيقتان}few{# دقائق}many{# دقيقةً}other{# دقيقة}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{ثانية واحدة}zero{# ثانية}two{ثانيتان}few{# ثوانٍ}many{# ثانية}other{# ثانية}}"</string>
<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>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"هل تريد ضبط <xliff:g id="APP_NAME">%1$s</xliff:g> باعتباره التطبيق التلقائي للتنقّل؟"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"ما مِن أذونات مطلوبة."</string>
<string name="role_watch_description" msgid="267003778693177779">"سيتم السماح لتطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> بالتفاعل مع الإشعارات والوصول إلى أذونات الهاتف والرسائل القصيرة وجهات الاتصال والتقويم."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"سيتم السماح لتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" بالتفاعل مع الإشعارات والوصول إلى أذونات الهاتف والرسائل القصيرة وجهات الاتصال والميكروفون والأجهزة المجاورة."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"سيتم السماح لتطبيق <xliff:g id="APP_NAME">%1$s</xliff:g> بالتفاعل مع الإشعارات وبث التطبيقات إلى الجهاز المتصل."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"سيتم السماح للتطبيق \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" ببث المحتوى إلى الأجهزة المجاورة."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"هذه الخدمة تشارك الصور والوسائط والإشعارات من هاتفك مع أجهزة أخرى."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"التطبيق التلقائي الحالي"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"لا تسألني مرة أخرى."</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ضبط كتطبيق تلقائي"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"‏هل تسمح بوصول &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; إلى &lt;b&gt;الصور والفيديوهات والموسيقى والملفات الصوتية وملفات أخرى&lt;/b&gt; على هذا الجهاز؟"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"‏هل تريد السماح بوصول &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; إلى المقاطع الموسيقية والملفات الصوتية على هذا الجهاز؟"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"‏هل تريد السماح بوصول &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; إلى الصور والفيديوهات على هذا الجهاز؟"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"‏هل تريد منح &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; الإذن بالوصول إلى الصور؟"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"‏هل تريد السماح لتطبيق &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بتسجيل الصوت؟"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"لن يتمكن هذا التطبيق من تسجيل الصوت إلا عندما يكون قيد الاستخدام"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"‏هل تريد السماح لتطبيق &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; بتسجيل الصوت؟"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"عرض رسالة عندما يصل التطبيق إلى نص أو صور أو محتوى آخر تم نسخه."</string>
<string name="show_password_title" msgid="2877269286984684659">"عرض كلمات المرور"</string>
<string name="show_password_summary" msgid="1110166488865981610">"عرض الأحرف لفترة وجيزة أثناء الكتابة"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"وضَّح هذا التطبيق أنه يمكنه مشاركة بيانات <xliff:g id="PERMISSION_NAME">%s</xliff:g> مع جهات خارجية."</string>
</resources>
diff --git a/PermissionController/res/values-as/strings.xml b/PermissionController/res/values-as/strings.xml
index c339ec354..a79bf5e3b 100644
--- a/PermissionController/res/values-as/strings.xml
+++ b/PermissionController/res/values-as/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“এপ্‌টো ব্যৱহাৰ হৈ থকা অৱস্থাত” ৰাখক"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“কেৱল এইবাৰৰ বাবে অনুমতি দিয়ক” বিকল্পটো ৰাখক"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"অধিক তথ্য"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"আটাইবোৰ ফট’ এক্সেছৰ অনুমতি দিয়ক"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ফট’ বাছনি কৰক"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"অধিক ফট’ বাছক"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"অধিক ফট’ বাছনি নকৰিব"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"তথাপি অনুমতি নিদিব"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"অগ্ৰাহ্য কৰক"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>ৰ ভিতৰত <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>টা"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{১ দিন}one{# দিন}other{# দিন}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{১ ঘণ্টা}one{# ঘণ্টা}other{# ঘণ্টা}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{১ মিনিট}one{# মিনিট}other{# মিনিট}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{১ ছেকেণ্ড}one{# ছেকেণ্ড}other{# ছেকেণ্ড}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# দিন}one{# দিন}other{# দিন}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ঘণ্টা}one{# ঘণ্টা}other{# ঘণ্টা}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# মিনিট}one{# মিনিট}other{# মিনিট}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# ছেকেণ্ড}one{# ছেকেণ্ড}other{# ছেকেণ্ড}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"যিকোনো অনুমতি"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"যিকোনো সময়ত"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"যোৱা ৭ দিনত"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"যোৱা ২৪ ঘণ্টাত"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"যোৱা ১ ঘণ্টাত"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"যোৱা ১৫ মিনিটত"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"যোৱা ১ মিনিটত"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{যোৱা # দিনত}one{যোৱা # দিনত}other{যোৱা # দিনত}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{যোৱা # ঘণ্টাত}one{যোৱা # ঘণ্টাত}other{যোৱা # ঘণ্টাত}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{যোৱা # মিনিটত}one{যোৱা # মিনিটত}other{যোৱা # মিনিটত}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"অনুমতি ব্যৱহাৰ কৰা হোৱা নাই"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"সৰ্বসময়ৰ একেবাৰে শেহতীয়াকৈ কৰা এক্সেছ"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"যোৱা ৭ দিনত একেবাৰে শেহতীয়া এক্সেছ"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"যোৱা ১ মিনিটত কৰা অনুমতিৰ ব্যৱহাৰ"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"যোৱা ১৫ মিনিটত কৰা অনুমতিৰ ব্যৱহাৰ"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"যোৱা ১ মিনিটত কৰা অনুমতিৰ ব্যৱহাৰ"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"যোৱা ২৪ ঘণ্টাত ব্যৱহাৰ কৰা হোৱা নাই"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"যোৱা ৭ দিনত ব্যৱহাৰ কৰা হোৱা নাই"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{যোৱা # দিনত ব্যৱহাৰ কৰা হোৱা নাই}one{যোৱা # দিনত ব্যৱহাৰ কৰা হোৱা নাই}other{যোৱা # দিনত ব্যৱহাৰ কৰা হোৱা নাই}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{যোৱা # ঘণ্টাত ব্যৱহাৰ কৰা হোৱা নাই}one{যোৱা # ঘণ্টাত ব্যৱহাৰ কৰা হোৱা নাই}other{যোৱা # ঘণ্টাত ব্যৱহাৰ কৰা হোৱা নাই}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{১ টা এপে ব্যৱহাৰ কৰিছে}one{# টা এপে ব্যৱহাৰ কৰিছে}other{# টা এপে ব্যৱহাৰ কৰিছে}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ডেশ্বব’ৰ্ডত আটাইবোৰ চাওক"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"এই অনুসৰি ফিল্টাৰ কৰা: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"কেৱল মিডিয়ালৈ এক্সেছৰ অনুমতি দিয়ক"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"সকলো সময়ৰ বাবে অনুমতি দিয়ক"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"কেৱল এপ্ ব্যৱহাৰ হৈ থাকোঁতে অনুমতি দিয়ক"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"আটাইবোৰ ফট’ৰ অনুমতি দিয়ক"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"বাছনি কৰা ফট’ৰ অনুমতি দিয়ক"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"প্ৰতিবাৰতে সোধক"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"অনুমতি নিদিব"</string>
<string name="precise_image_description" msgid="6349638632303619872">"সঠিক অৱস্থান"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"অনুমতি নাই"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"আটাইবোৰ ফাইল এক্সেছ কৰিব পৰা অধিক এপ্‌ চাওক"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{১ দিন}one{# দিন}other{# দিন}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{১ ঘণ্টা}one{# ঘণ্টা}other{# ঘণ্টা}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{১ মিনিট}one{# মিনিট}other{# মিনিট}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{১ ছেকেণ্ড}one{# ছেকেণ্ড}other{# ছেকেণ্ড}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ঘণ্টা}one{# ঘণ্টা}other{# ঘণ্টা}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# মিনিট}one{# মিনিট}other{# মিনিট}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# ছেকেণ্ড}one{# ছেকেণ্ড}other{# ছেকেণ্ড}}"</string>
<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>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক আপোনাৰ ডিফ’ল্ট নেভিগেশ্বনৰ এপ্ হিচাপে ছেট কৰিবনে?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"কোনো অনুমতিৰ প্ৰয়োজন নাই"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক আপোনাৰ জাননী ব্যৱহাৰ কৰিবলৈ আৰু আপোনাৰ ফ’ন, এছএমএছ, সম্পৰ্ক আৰু কেলেণ্ডাৰৰ অনুমতি এক্সেছ কৰিবলৈ দিয়া হ’ব।"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক এই অনুমতিসমূহৰ সৈতে ভাব-বিনিময় কৰিবলৈ আৰু আপোনাৰ ফ’ন, এছএমএছ, সম্পৰ্ক, মাইক্ৰ’ফ’ন আৰু নিকটৱৰ্তী ডিভাইচৰ অনুমতিসমূহ এক্সেছ কৰিবলৈ দিয়া হ’ব।"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক আপোনাৰ জাননী ব্যৱহাৰ কৰিবলৈ আৰু সংযুক্ত ডিভাইচলৈ আপোনাৰ এপ্‌বোৰ ষ্ট্ৰীম কৰিবলৈ দিয়া হ’ব।"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g>ক নিকটৱৰ্তী ডিভাইচসমূহলৈ সমল ষ্ট্ৰীম কৰিবলৈ দিয়া হ’ব।"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"এইটো সেৱাই আপোনাৰ ফট’, মিডিয়া আৰু আপোনাৰ ফ’নৰ পৰা অন্য ডিভাইচলৈ দিয়া জাননী শ্বেয়াৰ কৰে।"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"বৰ্তমানৰ ডিফ’ল্ট"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"পুনৰায় নুসুধিব"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ডিফ’ল্ট ৰূপে ছেট কৰক"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ক এই ডিভাইচটোত থকা &lt;b&gt;ফট’, ভিডিঅ’, সংগীত, অডিঅ’ আৰু অন্য ফাইল&lt;/b&gt; এক্সেছ কৰিবলৈ দিবনে?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ক এই ডিভাইচটোত থকা সংগীত আৰু অডিঅ’ এক্সেছ কৰিবলৈ অনুমতি দিবনে?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ক এই ডিভাইচটোত থকা ফট’ আৰু ভিডিঅ’ এক্সেছ কৰিবলৈ অনুমতি দিবনে?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ক অধিক ফট’ এক্সেছ কৰাৰ অনুমতি দিবনে?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ক অডিঅ\' ৰেকৰ্ড কৰিবলৈ অনুমতি দিবনে?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"আপুনি এই এপ্‌টো ব্যৱহাৰ কৰি থকাৰ সময়তহে কেৱল ই অডিঅ’ ৰেকৰ্ড কৰিব পাৰিব"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ক অডিঅ’ ৰেকৰ্ড কৰিবলৈ অনুমতি দিবনে?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"এপে আপুনি প্ৰতিলিপি কৰা পাঠ, প্ৰতিচ্ছবি অথবা অন্য সমল এক্সেছ কৰিলে এটা বাৰ্তা দেখুৱাওক"</string>
<string name="show_password_title" msgid="2877269286984684659">"পাছৱৰ্ডবোৰ দেখুৱাওক"</string>
<string name="show_password_summary" msgid="1110166488865981610">"আপুনি টাইপ কৰাৰ লগে লগে বৰ্ণসমূহ খন্তেকৰ বাবে দেখুৱাওক"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"এই এপ্‌টোৱে তৃতীয় পক্ষৰ সৈতে <xliff:g id="PERMISSION_NAME">%s</xliff:g>ৰ ডেটা শ্বেয়াৰ কৰিব পাৰে বুলি জনাইছে"</string>
</resources>
diff --git a/PermissionController/res/values-az/strings.xml b/PermissionController/res/values-az/strings.xml
index ca119aa53..c2eff62cc 100644
--- a/PermissionController/res/values-az/strings.xml
+++ b/PermissionController/res/values-az/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“Tətbiq istifadə edilən zaman” saxlansın"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“Ancaq bu dəfə” saxlanılsın"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Ətraflı məlumat"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Bütün fotolara girişə icazə verin"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Fotolar seçin"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Daha çox foto seçin"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Daha çox foto seçməyin"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"İstənilən halda icazə verməyin"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"İmtina edin"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>/<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 gün}other{# gün}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 saat}other{# saat}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 dəq}other{# dəq}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 san}other{# san}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# gün}other{# gün}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# saat}other{# saat}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# dəq}other{# dəq}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# san}other{# san}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Hər hansı icazə"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"İstənilən vaxt"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Son 7 gün"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Son 24 saat"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Son 1 saat"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Son 15 dəqiqə"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Son 1 dəqiqə"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Son # gün}other{Son # gün}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Son # saat}other{Son # saat}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Son # dəqiqə}other{Son # dəqiqə}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"İcazələrdən istifadə olunmayıb"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"İstənilən vaxt üçün fəaliyyət"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Son 7 gün ərzindəki fəaliyyət"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Son 1 saat ərzində icazə istifadəsi"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Son 15 dəqiqə ərzində icazə istifadəsi"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Son 1 dəqiqə ərinzdə icazə istifadəsi"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Son 24 saat ərzində istifadə edilməyib"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Son 7 gün ərzində istifadə edilməyib"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Son # gün ərzində istifadə edilməyib}other{Son # gün ərzində istifadə edilməyib}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Son # saat ərzində istifadə edilməyib}other{Son # saat ərzində istifadə edilməyib}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 tətbiq istifadə edir}other{# tətbiq istifadə edir}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Hamısına İdarə panelində baxın"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrlədi: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Yalnız mediaya giriş icazəsi verin"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Həmişə icazə verin"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Yalnız istifadə zamanı"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Bütün fotolara icazə verin"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Seçilmiş fotolara icazə verin"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Həmişə soruşulsun"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"İcazə verməyin"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Dəqiq məkan"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"İcazə verilməyib"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Bütün fayllara giriş edə bilən digər tətbiqlərə baxın"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 gün}other{# gün}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 saat}other{# saat}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 dəqiqə}other{# dəqiqə}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 saniyə}other{# saniyə}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# saat}other{# saat}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# dəqiqə}other{# dəqiqə}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# saniyə}other{# saniyə}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"İcazə xatırladıcıları"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 tətbiq istifadə edilmir"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> istifadə olunmayan tətbiq"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> defolt naviqasiya tətbiqi olaraq ayarlansın?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"İcazəyə ehtiyac yoxdur"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> bildirişlərinizə, Telefon, SMS, Kontaktlar və Təqvimə giriş əldə edəcək."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> bildirişlərinizə, Telefon, SMS, Kontaktlar, Mikrofon və Yaxınlıqdakı cihazlar icazələrinə giriş əldə edəcək."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> bildirişlərinizə giriş əldə edəcək və tətbiqlərinizi qoşulmuş cihazda yayımlaya biləcək"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> tətbiqinin yaxınlıqdakı cihazlara kontent yayımlamasına icazə veriləcək."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Bu xidmət telefonunuzdakı foto, media və bildirişləri digər cihazlarla paylaşır."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Cari defolt"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Bir daha soruşmayın"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Defolt olaraq seçin"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tətbiqinin bu cihazdakı &lt;b&gt;foto, video, musiqi, audio və digər fayllara&lt;/b&gt; girişinə icazə verilsin?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tətbiqinə bu cihazdakı musiqi və audioya girişinə icazə verilsin?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tətbiqinin cihazdakı foto və videolara girişinə icazə verilsin?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tətbiqinin daha çox fotoya girişinə icazə verilsin?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tətbiqinə səs yazmaq icazəsi verilsin?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Tətbiq yalnız ondan istifadə etiyiniz zaman audio yaza biləcək"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tətbiqinə audio yazmaq icazəsi verilsin?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Tətbiq kopyalanmış mətn, şəkil və ya digər kontent işlədəndə bildiriş göstərilsin"</string>
<string name="show_password_title" msgid="2877269286984684659">"Parolları göstərin"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Yazarkən simvollar qısa müddət göstərilsin"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Bu tətbiq <xliff:g id="PERMISSION_NAME">%s</xliff:g> datasını üçüncü tərəflərlə paylaşa biləcəyini bildirdi"</string>
</resources>
diff --git a/PermissionController/res/values-b+sr+Latn/strings.xml b/PermissionController/res/values-b+sr+Latn/strings.xml
index 32a2295f6..57ad178e9 100644
--- a/PermissionController/res/values-b+sr+Latn/strings.xml
+++ b/PermissionController/res/values-b+sr+Latn/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Zadrži „Dok se aplikacija koristi“"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Zadrži Samo ovaj put"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Više informacija"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Dozvoli pristup svim slikama"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Izaberite slike"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Izaberite još slika"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Nemojte da birate više slika"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ionako ne dozvoli"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Odbaci"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> od <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dan}one{# dan}few{# dana}other{# dana}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 sat}one{# sat}few{# sata}other{# sati}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}one{# min}few{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sek}one{# sek}few{# sek}other{# sek}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dan}one{# dan}few{# dana}other{# dana}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# sat}one{# sat}few{# sata}other{# sati}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}one{# min}few{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sek}one{# sek}few{# sek}other{# sek}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Bilo koja dozvola"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Bilo kada"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Poslednjih 7 dana"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Poslednja 24 sata"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Poslednji sat"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Poslednjih 15 minuta"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Poslednji minut"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Poslednji # dan}one{Poslednji # dan}few{Poslednja # dana}other{Poslednjih # dana}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Poslednji # sat}one{Poslednji # sat}few{Poslednja # sata}other{Poslednjih # sati}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Poslednji # minut}one{Poslednji # minut}few{Poslednja # minuta}other{Poslednjih # minuta}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Dozvole nisu korišćene"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Najskoriji pristup u bilo kom trenutku"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Najskoriji pristup u poslednjih 7 dana"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Korišćenje dozvola u poslednjih sat vremena"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Korišćenje dozvole u poslednjih 15 minuta"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Korišćenje dozvola u poslednjem minutu"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Nije korišćeno u poslednja 24 sata"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Nije korišćeno u poslednjih 7 dana"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Nije korišćeno tokom poslednjeg # dana}one{Nije korišćeno tokom poslednjeg # dana}few{Nije korišćeno tokom poslednja # dana}other{Nije korišćeno tokom poslednjih # dana}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Nije korišćeno tokom poslednjeg # sata}one{Nije korišćeno tokom poslednjeg # sata}few{Nije korišćeno tokom poslednja # sata}other{Nije korišćeno tokom poslednjih # sati}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Koristi 1 aplikacija}one{Koristi # aplikacija}few{Koriste # aplikacije}other{Koristi # aplikacija}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Prikaži sve na kontrolnoj tabli"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrirano prema: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Dozvoli samo pristup medijima"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Dozvoli uvek"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Dozv. samo dok se apl. koristi"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Dozvoli sve slike"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Dozvoli izabrane slike"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Pitaj svaki put"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Ne dozvoli"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Precizna lokacija"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nije dozvoljeno"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Prikaži još aplikacija sa pristupom svim fajlovima"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dan}one{# dan}few{# dana}other{# dana}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 sat}one{# sat}few{# sata}other{# sati}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minut}one{# minut}few{# minuta}other{# minuta}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekunda}one{# sekunda}few{# sekunde}other{# sekundi}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# sat}one{# sat}few{# sata}other{# sati}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minut}one{# minut}few{# minuta}other{# minuta}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekunda}one{# sekunda}few{# sekunde}other{# sekundi}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Podsetnici za dozvole"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 aplikacija koja se ne koristi"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Aplikacija koje se ne koriste: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Želite da podesite aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g> kao podrazumevanu aplikaciju za navigaciju?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nije potrebna nijedna dozvola"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> će dobiti dozvolu za interakciju sa obaveštenjima i pristup dozvolama za telefon, SMS poruke, kontakte i kalendar."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> će dobiti dozvolu za interakciju sa obaveštenjima i pristup dozvolama za telefon, SMS, kontakte, mikrofon i uređaje u blizini."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> će dobiti dozvolu za interakciju sa obaveštenjima i strimovanje aplikacija na povezanom uređaju."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> će moći da strimuje sadržaj na uređajima u blizini."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ova usluga deli slike, medijski sadržaj i obaveštenja sa telefona na drugim uređajima."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Trenutno podrazumevana"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ne pitaj ponovo"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Podesi kao podrazum."</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Pristup slikama, videu, muzici, zvuku i drugom na uređaju za &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Dozvoljavate li pristup muzici i zvuku na ovom uređaju za &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Dozvoljavate li pristup slikama i videu na ovom uređaju za &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Želite li da aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; dozvolite pristup većem broju slika?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Želite da dozvolite da &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; snima zvuk?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Aplikacija će moći da snima zvuk samo dok koristite aplikaciju"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Želite da dozvolite da &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; snima zvuk?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Prikazuje poruku kada aplikacije pristupaju tekstu, slikama ili drugom sadržaju koji ste kopirali"</string>
<string name="show_password_title" msgid="2877269286984684659">"Prikazuj lozinke"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Prikazuje znakove nakratko dok kucate"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Ova aplikacija navodi da može da deli podatke (<xliff:g id="PERMISSION_NAME">%s</xliff:g>) sa trećim stranama"</string>
</resources>
diff --git a/PermissionController/res/values-be/strings.xml b/PermissionController/res/values-be/strings.xml
index a45533fd6..13c5f748f 100644
--- a/PermissionController/res/values-be/strings.xml
+++ b/PermissionController/res/values-be/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Пакінуць \"У актыўным рэжыме праграмы\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Захаваць толькі на гэты раз"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Падрабязней"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Дазволіць доступ да ўсіх фота"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Выбраць фота"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Выбраць больш фота"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Больш не выбіраць фота"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Усё роўна не дазваляць"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Адхіліць"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> з <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 дзень}one{# дзень}few{# дні}many{# дзён}other{# дня}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 гадзіна}one{# гадзіна}few{# гадзіны}many{# гадзін}other{# гадзіны}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 хв}one{# хв}few{# хв}many{# хв}other{# хв}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 с}one{# с}few{# с}many{# с}other{# с}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# дзень}one{# дзень}few{# дні}many{# дзён}other{# дня}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# гадзіна}one{# гадзіна}few{# гадзіны}many{# гадзін}other{# гадзіны}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# хв}one{# хв}few{# хв}many{# хв}other{# хв}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# с}one{# с}few{# с}many{# с}other{# с}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Любы дазвол"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"За любы час"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"За апошнія 7 дзён"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"За апошнія 24 гадзіны"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"За апошнюю гадзіну"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"За апошнія 15 хвілін"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"За апошнюю хвіліну"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{За апошні # дзень}one{За апошні # дзень}few{За апошнія # дні}many{За апошнія # дзён}other{За апошнія # дня}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{За апошнюю # гадзіну}one{За апошнюю # гадзіну}few{За апошнія # гадзіны}many{За апошнія # гадзін}other{За апошнія # гадзіны}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{За апошнюю # хвіліну}one{За апошнюю # хвіліну}few{За апошнія # хвіліны}many{За апошнія # хвілін}other{За апошнія # хвіліны}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Без выкарыстання дазволаў"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Доступы за ўвесь час"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Доступы за апошнія 7 дзён"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Выкарыстанне дазволаў за апошнюю гадзіну"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Выкарыстаннне дазволаў за апошнія 15 хвілін"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Выкарыстанне дазволаў за апошнюю хвіліну"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"За апошнія 24 гадзіны доступ не выкарыстоўваўся"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"За апошнія 7 сутак доступ не выкарыстоўваўся"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{За апошнія # суткі дазвол не выкарыстоўваўся}one{За апошнія # суткі дазвол не выкарыстоўваўся}few{За апошнія # сутак дазвол не выкарыстоўваўся}many{За апошнія # сутак дазвол не выкарыстоўваўся}other{За апошнія # сутак дазвол не выкарыстоўваўся}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{За апошнюю # гадзіну дазвол не выкарыстоўваўся}one{За апошнюю # гадзіну дазвол не выкарыстоўваўся}few{За апошнія # гадзіны дазвол не выкарыстоўваўся}many{За апошнія # гадзін дазвол не выкарыстоўваўся}other{За апошнія # гадзіны дазвол не выкарыстоўваўся}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Выкарыстоўваецца 1 праграмай}one{Выкарыстоўваецца # праграмай}few{Выкарыстоўваецца # праграмамі}many{Выкарыстоўваецца # праграмамі}other{Выкарыстоўваецца # праграмы}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Паказаць усе на панэлі кіравання"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Параметр фільтравання: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Дазволіць доступ толькі да мультымедыя"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Дазволіць заўсёды"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Дазволіць толькі падчас карыстання праграмай"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Дазволіць усе фота"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Дазволіць выбраныя фота"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Заўсёды пытацца"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Не дазваляць"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Дакладнае месцазнаходжанне"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Забаронена"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Праглядзець іншыя праграмы, якія маюць доступ да ўсіх файлаў"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 дзень}one{# дзень}few{# дні}many{# дзён}other{# дня}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 гадзіна}one{# гадзіна}few{# гадзіны}many{# гадзін}other{# гадзіны}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 хвіліна}one{# хвіліна}few{# хвіліны}many{# хвілін}other{# хвіліны}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 секунда}one{# секунда}few{# секунды}many{# секунд}other{# секунды}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# гадзіна}one{# гадзіна}few{# гадзіны}many{# гадзін}other{# гадзіны}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# хвіліна}one{# хвіліна}few{# хвіліны}many{# хвілін}other{# хвіліны}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# секунда}one{# секунда}few{# секунды}many{# секунд}other{# секунды}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Напаміны пра дазволы"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 праграма не выкарыстоўваецца"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Колькасць праграм не ў карыстанні: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Прызначыць праграму \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" стандартнай праграмай навігацыі?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Дазволы не патрэбныя"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> зможа ўзаемадзейнічаць з вашымі апавяшчэннямі і атрымае доступ да тэлефона, SMS, кантактаў і календара."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> зможа ўзаемадзейнічаць з вашымі апавяшчэннямі і атрымае доступ да тэлефона, SMS, кантактаў, мікрафона і прылад паблізу."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> зможа ўзаемадзейнічаць з вашымі апавяшчэннямі і перадаваць праграмы плынню на падключаную прыладу."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> зможа перадаваць змесціва плынню на прылады паблізу."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Гэты сэрвіс абагульвае з іншымі прыладамі фота, мультымедыя і апавяшчэнні, якія захоўваюцца на вашым тэлефоне."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Цяперашняя стандартная"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Больш не пытацца"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Зрабіць стандартнай"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Дазволіць праграме &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ да &lt;b&gt;фота, відэа, музыкі, аўдыя і файлаў&lt;/b&gt; на гэтай прыладзе?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Дазволіць праграме &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ да музыкі і аўдыя на гэтай прыладзе?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Дазволіць праграме &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ да фота і відэа на гэтай прыладзе?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Адкрыць праграме &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ да большай колькасці фота?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Дазволіць праграме &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; запісваць аўдыя?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Гэта праграма зможа запісваць аўдыя толькі падчас яе выкарыстання"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Дазволіць праграме &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; запісваць аўдыя?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Паказваць паведамленне, калі праграмы атрымліваюць доступ да тэксту, відарысаў ці іншага змесціва, якое вы скапіравалі"</string>
<string name="show_password_title" msgid="2877269286984684659">"Паказваць паролі"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Падчас уводу сімвалаў на кароткі час паказваць іх"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Гэта праграма можа абагульваць з трэцімі бакамі даныя катэгорыі \"<xliff:g id="PERMISSION_NAME">%s</xliff:g>\""</string>
</resources>
diff --git a/PermissionController/res/values-bg/strings.xml b/PermissionController/res/values-bg/strings.xml
index 0275e3353..2a34cdf23 100644
--- a/PermissionController/res/values-bg/strings.xml
+++ b/PermissionController/res/values-bg/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Запазване на опцията „Докато приложението се използва“"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Запазване на настройката „Само този път“"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Още информация"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Разрешаване на достъп до всички снимки"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Избиране на снимки"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Избиране на още снимки"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Без избиране на още снимки"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Забраняване въпреки това"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Отхвърляне"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> от <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ден}other{# дни}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 час}other{# часа}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 мин}other{# мин}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 сек}other{# сек}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ден}other{# дни}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# час}other{# часа}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# мин}other{# мин}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# сек}other{# сек}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Всички разрешения"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"По всяко време"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Последните 7 дни"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Последните 24 часа"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Последния 1 час"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Последните 15 минути"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Последната минута"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Последният # ден}other{Последните # дни}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Последният # час}other{Последните # часа}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Последната # минута}other{Последните # минути}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Разрешенията не са използвани"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Най-скорошен достъп във всеки един момент"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Най-скорошен достъп през последните 7 дни"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Използвани разрешения през последния час"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Използвани разрешения през последните 15 минути"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Използвани разрешения през последната минута"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Не е използвано през последните 24 часа"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Не е използвано през последните 7 дни"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Не е използвано през последния # ден}other{Не е използвано през последните # дни}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Не е използвано през последния # час}other{Не е използвано през последните # часа}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Използва се от 1 приложение}other{Използва се от # приложения}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Преглед на всичко в таблото за управление"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Филтрирано по: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Разрешаване на достъп само до мултимедията"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Разрешаване във всички случаи"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Разрешаване само докато приложението се използва"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Разрешаване на всички снимки"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Разрешаване на избраните снимки"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Извеждане на запитване всеки път"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Забраняване"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Точно местоположение"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Не е разрешено"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Вижте още приложения, които имат достъп до всички файлове"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ден}other{# дни}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 час}other{# часа}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 минута}other{# минути}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 секунда}other{# секунди}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# час}other{# часа}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# минута}other{# минути}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# секунда}other{# секунди}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Напомняния за разрешения"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 неизползвано приложение"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> неизползвани приложения"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Да се зададе ли <xliff:g id="APP_NAME">%1$s</xliff:g> като основно приложение за навигация?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Не са необходими разрешения"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> ще получи разрешение да взаимодейства с известията ви и ще получи достъп до разрешенията за телефона, SMS съобщенията, контактите и календара."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> ще получи право да взаимодейства с известията ви, както и достъп до разрешенията за телефона, SMS съобщенията, контактите, микрофона и устройствата в близост."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> ще получи разрешение да взаимодейства с известията ви и да предава поточно приложенията ви към свързаните устройства."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> ще получи разрешение за поточно предаване на съдържание към устройства в близост."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Тази услуга споделя вашите снимки, мултимедия и известия от телефона ви на други устройства."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Текущо стандартно приложение"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Без повторно питане"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Задаване"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Разр. на &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; достъп до &lt;b&gt;снимките, видео- и аудиосъдърж. и другите файлове&lt;/b&gt; на у-вото?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Да се разреши ли на &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; да осъществява достъп до музиката и аудиофайловете на това устройство?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Да се разреши ли на &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; да осъществява достъп до снимките и видеоклиповете на това устройство?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Да се предостави ли достъп на &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; до още снимки?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Да се разреши ли на &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; да записва аудио?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Приложението ще може да записва аудио само когато го използвате"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Да се разреши ли на &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; да записва аудио?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Показване на съобщение, когато приложенията осъществяват достъп до копирани от вас текст, изображения или друго съдържание"</string>
<string name="show_password_title" msgid="2877269286984684659">"Показване на паролите"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Кратко показване на знаците, докато пишете"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Това приложение посочи, че може да споделя с трети страни данни за <xliff:g id="PERMISSION_NAME">%s</xliff:g>"</string>
</resources>
diff --git a/PermissionController/res/values-bn/strings.xml b/PermissionController/res/values-bn/strings.xml
index 356e84d92..c4312a5db 100644
--- a/PermissionController/res/values-bn/strings.xml
+++ b/PermissionController/res/values-bn/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“অ্যাপ ব্যবহার করার সময়” বিকল্পটি চালু রাখুন"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“শুধুমাত্র এই সময়ে অনুমতি দিন” বিকল্পটি রাখুন"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"আরও তথ্য"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"সব ফটোতে অ্যাক্সেস দেওয়া"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ফটো বেছে নেওয়া"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"আরও ফটো বেছে নেওয়া"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"আরও ফটো বেছে নেবেন না"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"যাই হোক, অনুমতি দেবেন না"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"বাতিল করুন"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>টির মধ্যে <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> নম্বর"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{১ দিন}one{# দিন}other{# দিন}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{১ ঘণ্টা}one{# ঘণ্টা}other{# ঘণ্টা}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{১ মিনিট}one{# মিনিট}other{# মিনিট}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{১ সেকেন্ড}one{# সেকেন্ড}other{# সেকেন্ড}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# দিন}one{# দিন}other{# দিন}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ঘণ্টা}one{# ঘণ্টা}other{# ঘণ্টা}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# মিনিট}one{# মিনিট}other{# মিনিট}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# সেকেন্ড}one{# সেকেন্ড}other{# সেকেন্ড}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"যেকোন অনুমতি"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"যেকোনও সময়"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"গত ৭ দিন"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"গত ২৪ ঘন্টায়"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"শেষ ১ ঘণ্টা"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"গত ১৫ মিনিট"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"গত ১ মিনিটে"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{গত # দিন}one{গত # দিন}other{গত # দিন}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{গত # ঘণ্টায়}one{গত # ঘণ্টায়}other{গত # ঘণ্টায়}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{গত # মিনিটে}one{গত # মিনিটে}other{গত # মিনিটে}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"কোন অনুমতির ব্যবহার হয়নি"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"সাম্প্রতিক ব্যবহার"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"গত ৭ দিনের মধ্যে সাম্প্রতিক অ্যাক্সেস"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"গত ১ ঘণ্টার মধ্যে অনুমতির ব্যবহার"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"গত ১৫ মিনিটের মধ্যে অনুমতির ব্যবহার"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"গত ১ মিনিটের মধ্যে অনুমতির ব্যবহার"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"গত ২৪ ঘন্টায় ব্যবহার করা হয়নি"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"গত ৭ দিনে ব্যবহার করা হয়নি"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{গত # দিনে ব্যবহার করা হয়নি}one{গত # দিনে ব্যবহার করা হয়নি}other{গত # দিনে ব্যবহার করা হয়নি}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{গত # ঘণ্টায় ব্যবহার করা হয়নি}one{গত # ঘণ্টায় ব্যবহার করা হয়নি}other{গত # ঘণ্টায় ব্যবহার করা হয়নি}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{১টি অ্যাপ ব্যবহার করেছে}one{#টি অ্যাপ ব্যবহার করেছে}other{#টি অ্যাপ ব্যবহার করেছে}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"সবকিছু ড্যাশবোর্ডে দেখুন"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"এই অনুযায়ী ফিল্টার করা হয়েছে: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"শুধু মিডিয়া অ্যাক্সেসের অনুমতি দিন"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"সব সময়ের জন্য অনুমতি দিন"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"শুধু অ্যাপ ব্যবহারের সময় অনুমতি দিন"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"সব ফটোকে অনুমতি দেওয়া"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"বাছাই করা ফটোকে অনুমতি দেওয়া"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"প্রতিবার জিজ্ঞাসা করা হবে"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"অনুমতি দেবেন না"</string>
<string name="precise_image_description" msgid="6349638632303619872">"সুনির্দিষ্ট লোকেশন"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"অনুমোদিত নয়"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"সব ফাইল অ্যাক্সেস করতে পারবে এমন আরও অ্যাপ দেখুন"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{১ দিন}one{# দিন}other{# দিন}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{১ ঘণ্টা}one{# ঘণ্টা}other{# ঘণ্টা}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{১ মিনিট}one{# মিনিট}other{# মিনিট}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{১ সেকেন্ড}one{# সেকেন্ড}other{# সেকেন্ড}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ঘণ্টা}one{# ঘণ্টা}other{# ঘণ্টা}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# মিনিট}one{# মিনিট}other{# মিনিট}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# সেকেন্ড}one{# সেকেন্ড}other{# সেকেন্ড}}"</string>
<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>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"আপনার ডিফল্ট নেভিগেশন অ্যাপ হিসেবে <xliff:g id="APP_NAME">%1$s</xliff:g> সেট করতে চান?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"কোনও অনুমতির প্রয়োজন নেই"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> আপনার বিজ্ঞপ্তির সাথে ইন্টার‌্যাক্ট করতে পারবে, তার সাথে আপনার ফোন, এমএসএস, পরিচিতি এবং ক্যালেন্ডারের অনুমতিও অ্যাক্সেস করতে পারবে।"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g>-কে আপনার বিজ্ঞপ্তির সাথে ইন্টার‌্যাক্ট করার জন্য অনুমতি দেওয়া হবে। এছাড়াও ফোন, এসএমএস, পরিচিতি, মাইক্রোফোন এবং আশেপাশের ডিভাইস ব্যবহার করার অনুমতি অ্যাক্সেস করতে দেওয়া হবে।"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g>-কে আপনার বিজ্ঞপ্তির সাথে কথোপকথন এবং কানেক্ট করা ডিভাইসের অ্যাপ স্ট্রিম করতে অনুমতি দেওয়া হবে।"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"আশেপাশের ডিভাইসে কন্টেন্ট স্ট্রিম করার জন্য <xliff:g id="APP_NAME">%1$s</xliff:g>-কে অনুমোদন দেওয়া হবে।"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"এই পরিষেবাটি আপনার ফোনের ফটো, মিডিয়া ও বিজ্ঞপ্তি অন্যান্য ডিভাইসে শেয়ার করে।"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"বর্তমান ডিফল্ট"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"আর দেখতে চাই না"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ডিফল্ট হিসেবে রাখুন"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"এই ডিভাইসে &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-কে &lt;b&gt;ফটো, ভিডিও, মিউজিক, অডিও ও অন্যান্য ফাইল&lt;/b&gt; অ্যাক্সেসের অনুমতি দেবেন?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"এই ডিভাইসে &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-কে মিউজিক ও অডিও ফাইল অ্যাক্সেসের অনুমতি দেবেন?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"এই ডিভাইসে &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-কে ফটো ও ভিডিও অ্যাক্সেসের অনুমতি দেবেন?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"আরও ফটো অ্যাক্সেস করতে &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-কে অনুমতি দেবেন?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-কে অডিও রেকর্ড করার অনুমতি দেবেন?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"আপনি এই অ্যাপ ব্যবহার করার সময়েই শুধুমাত্র সেটি অডিও রেকর্ড করতে পারবে"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-কে অডিও রেকর্ড করার অনুমতি দিতে চান?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"আপনার কপি করা টেক্সট, ছবি বা অন্যান্য কন্টেন্ট অ্যাপ অ্যাক্সেস করলে মেসেজ দেখায়"</string>
<string name="show_password_title" msgid="2877269286984684659">"পাসওয়ার্ড দেখুন"</string>
<string name="show_password_summary" msgid="1110166488865981610">"টাইপ করার সময় অক্ষরগুলি কয়েক মুহূর্তের জন্য দেখুন"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"এই অ্যাপ, থার্ড-পার্টির সাথে <xliff:g id="PERMISSION_NAME">%s</xliff:g> সংক্রান্ত ডেটা শেয়ার করার অনুমতি চাইতে পারে"</string>
</resources>
diff --git a/PermissionController/res/values-bs/strings.xml b/PermissionController/res/values-bs/strings.xml
index 97a5e4395..f7437fb39 100644
--- a/PermissionController/res/values-bs/strings.xml
+++ b/PermissionController/res/values-bs/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Zadrži “Kada se aplikacija koristi”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Zadrži “Samo ovaj put”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Više informacija"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Dozvoli pristup svim fotografijama"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Odaberi fotografije"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Odaberi više fotografija"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Nemoj odabrati više fotografija"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Svejedno nemoj dozvoliti"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Odbaci"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> od <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dan}one{# dan}few{# dana}other{# dana}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 h}one{# h}few{# h}other{# h}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}one{# min}few{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}one{# s}few{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dan}one{# dan}few{# dana}other{# dana}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# h}one{# h}few{# h}other{# h}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}one{# min}few{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}one{# s}few{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Sva odobrenja"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Bilo kad"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Posljednjih 7 dana"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Posljednja 24 sata"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Posljednji sat"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Posljednjih 15 minuta"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Posljednja minuta"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Posljednji dan}one{Posljednji # dan}few{Posljednja # dana}other{Posljednjih # dana}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Posljednji sat}one{Posljednjih # h}few{Posljednja # h}other{Posljednjih # h}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Posljednja minuta}one{Posljednja # min}few{Posljednje # min}other{Posljednjih # min}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Odobrenje nije upotrijebljeno"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Najnoviji pristup u bilo koje vrijeme"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Najnoviji pristup u posljednjih 7 dana"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Korištena odobrenja u posljenji 1 sat"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Korištena odobrenja u posljednjih 15 minuta"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Korištena odobrenja u protekloj minuti"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Nije korišteno u posljednja 24 h"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Nije korišteno u posljednjih 7 dana"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Nije korišteno u proteklom danu}one{Nije korišteno u proteklom # danu}few{Nije korišteno u protekla # dana}other{Nije korišteno u proteklih # dana}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Nije korišteno u proteklom satu}one{Nije korišteno u protekli # h}few{Nije korišteno u protekla # h}other{Nije korišteno u proteklih # h}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Koristi 1 aplikacija}one{Koristi # aplikacija}few{Koriste # aplikacije}other{Koristi # aplikacija}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Prikaži sve na kontrolnoj tabli"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrirano prema: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Dozvoli pristup samo medijima"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Dozvoli sve vrijeme"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Dozvoli samo dok se aplikacija koristi"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Dozvoli sve fotografije"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Dozvoli odabrane fotografije"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Pitaj svaki put"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Nemoj dozvoliti"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Tačna lokacija"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nije dozvoljeno"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Pogledajte više aplikacija koje imaju pristup svim fajlovima"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dan}one{# dan}few{# dana}other{# dana}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 h}one{# h}few{# h}other{# h}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 min}one{# min}few{# min}other{# min}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 s}one{# s}few{# s}other{# s}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# h}one{# h}few{# h}other{# h}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# min}one{# min}few{# min}other{# min}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# s}one{# s}few{# s}other{# s}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Podsjetnici odobrenja"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 nekorištena aplikacija"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Broj nekorištenih aplikacija: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Postaviti aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g> kao zadanu aplikaciju za navigiranje?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nije potrebno odobrenje"</string>
<string name="role_watch_description" msgid="267003778693177779">"Aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> će se dozvoliti da ostvari interakciju s vašim obavještenjima i pristupi odobrenjima za telefon, SMS, kontakte i kalendar."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> će biti dozvoljeno da stupi u interakciju s obavještenjima i pristupa odobrenjima telefona, SMS-a, kontakata, mikrofona i uređaja u blizini."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> će se dozvoliti da ostvari interakciju s vašim obavještenjima i prenosi aplikacije na povezani uređaj."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> će biti dozvoljen prijenos sadržaja na uređajima u blizini."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ova usluga dijeli vaše fotografije, medijske sadržaje i obavještenja s vašeg telefona na druge uređaje."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Trenutno zadano"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ne pitaj ponovo"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Postavi kao zadano"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Dozvoliti da &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pristupa &lt;b&gt;foto/video/muzičkim/audio i drugim fajlovima&lt;/b&gt; na uređaju?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Dozvoliti da &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pristupa muzici i zvuku na ovom uređaju?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Dozvoliti da &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pristupa fotografijama i videozapisima na ovom uređaju?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pristup dodatnim fotografijama?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; da snima zvuk?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Aplikacija će moći snimati zvuk samo za vrijeme korištenja"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Dozvoliti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; snimanje zvuka?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Vidite poruku kada aplikacije pristupe tekstu, slikama ili drugom sadržaju koji ste kopirali"</string>
<string name="show_password_title" msgid="2877269286984684659">"Prikaži lozinke"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Kratko prikazivanje znakova dok pišete"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Aplikacija je navela da može dijeliti podatke iz kategorije <xliff:g id="PERMISSION_NAME">%s</xliff:g> s trećim stranama"</string>
</resources>
diff --git a/PermissionController/res/values-ca/strings.xml b/PermissionController/res/values-ca/strings.xml
index b185400da..d75b18f33 100644
--- a/PermissionController/res/values-ca/strings.xml
+++ b/PermissionController/res/values-ca/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Només mentre s\'utilitza l\'aplicació"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Mantén el permís Només aquesta vegada"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Més informació"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Permet l\'accés a totes les fotos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Selecciona fotos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Selecciona més fotos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"No seleccionis més fotos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"No permetis de cap manera"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Ignora"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> de <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dia}many{# days}other{# dies}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hora}many{# hours}other{# hores}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}many{# mins}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}many{# secs}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dia}many{# days}other{# dies}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hora}many{# hours}other{# hores}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}many{# mins}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}many{# secs}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Qualsevol permís"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"En qualsevol moment"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"7 darrers dies"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Darreres 24 hores"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Última hora"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Últims 15 minuts"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Últim minut"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Darrer dia}many{Last # days}other{# darrers dies}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Darrera hora}many{Last # hours}other{Darreres # hores}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Darrer minut}many{Last # minutes}other{Darrers # minuts}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Cap ús de permisos"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Accés més recents en qualsevol moment"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Accés més recents durant els 7 darrers dies"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Ús de permisos durant l\'última hora"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Ús de permisos durant els últims 15 minuts"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Ús de permisos durant l\'últim minut"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"No s\'ha utilitzat en les 24 darreres hores"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"No s\'ha utilitzat en els 7 darrers dies"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{No s\'ha utilitzat en el darrer dia}many{Not used in past # days}other{No s\'ha utilitzat en els darrers # dies}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{No s\'ha utilitzat en la darrera hora}many{Not used in past # hours}other{No s\'ha utilitzat en les darreres # hores}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Utilitzat per 1 aplicació}many{Used by # apps}other{Utilitzat per # aplicacions}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Mostra-ho tot al tauler"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrats per: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Permet l\'accés només als fitxers multimèdia"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Permet sempre"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Permet només mentre s\'utilitza l\'aplicació"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Permet totes les fotos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Permet les fotos seleccionades"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Pregunta sempre"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"No permetis"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Ubicació exacta"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Sense permís"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Mostra més aplicacions que poden accedir a tots els fitxers"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dia}many{# days}other{# dies}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hora}many{# hours}other{# hores}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minut}many{# minutes}other{# minuts}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 segon}many{# seconds}other{# segons}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hora}many{# hours}other{# hores}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minut}many{# minutes}other{# minuts}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# segon}many{# seconds}other{# segons}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Recordatoris de permisos"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 aplicació no utilitzada"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> aplicacions no utilitzades"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Vols definir <xliff:g id="APP_NAME">%1$s</xliff:g> com a aplicació de navegació predefinida?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"No calen permisos"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> tindrà permís per interaccionar amb les teves notificacions i accedir al telèfon, SMS, contactes i calendari."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> tindrà permís per interaccionar amb les teves notificacions i accedir al telèfon, als SMS, als contactes, al micròfon i als dispositius propers."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> tindrà permís per interaccionar amb les teves notificacions i reproduir en continu les teves aplicacions al dispositiu connectat."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> tindrà permís per reproduir contingut en continu en dispositius propers."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Aquest servei comparteix les fotos, el contingut multimèdia i les notificacions del telèfon amb altres dispositius."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Predeterminada actualment"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"No m\'ho tornis a preguntar"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Estableix com a predeterminada"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Vols permetre que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; accedeixi als &lt;b&gt;vídeos, fotos, música, àudio i altres fitxers&lt;/b&gt;?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Vols permetre que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; accedeixi a la música i l\'àudio d\'aquest dispositiu?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Vols permetre que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; accedeixi a les fotos i els vídeos d\'aquest dispositiu?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Vols permetre que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; accedeixi a més fotos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Vols permetre que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; gravi àudio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"L\'aplicació només podrà gravar àudio mentre l\'estiguis utilitzant"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Vols permetre que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; gravi àudio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Mostra un missatge quan les aplicacions accedeixen al text, a les imatges o a qualsevol altre contingut que hagis copiat"</string>
<string name="show_password_title" msgid="2877269286984684659">"Mostra les contrasenyes"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Mostra els caràcters breument mentre escrius"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Aquesta aplicació ha indicat que és possible que comparteixi dades (<xliff:g id="PERMISSION_NAME">%s</xliff:g>) amb tercers"</string>
</resources>
diff --git a/PermissionController/res/values-cs/strings.xml b/PermissionController/res/values-cs/strings.xml
index 6aaa53581..c34a4d99e 100644
--- a/PermissionController/res/values-cs/strings.xml
+++ b/PermissionController/res/values-cs/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Ponechat „Během používání aplikace“"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Ponechat možnost Pouze tentokrát"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Další informace"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Povolit přístup ke všem fotkám"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Vybrat fotky"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Vybrat více fotek"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Nevybírat více fotek"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Přesto nepovolit"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Zavřít"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> z <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 den}few{# dny}many{# dne}other{# dní}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hodina}few{# hodiny}many{# hodiny}other{# hodin}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}few{# min}many{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}few{# s}many{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# den}few{# dny}many{# dne}other{# dní}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hodinu}few{# hodiny}many{# hodiny}other{# hodin}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}few{# min}many{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}few{# s}many{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Všechna oprávnění"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Kdykoli"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Posledních 7 dní"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Posledních 24 hodin"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Poslední hodina"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Posledních 15 minut"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Poslední minuta"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Poslední den}few{Poslední # dny}many{Posledních # dne}other{Posledních # dní}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Poslední hodina}few{Poslední # hodiny}many{Posledních # hodiny}other{Posledních # hodin}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Poslední minuta}few{Poslední # minuty}many{Posledních # minuty}other{Posledních # minut}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Žádné využití oprávnění"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Poslední využití kdykoli"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Poslední použití za 7 dnů"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Využití oprávnění za poslední hodinu"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Využití oprávnění za posledních 15 minut"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Využití oprávnění za poslední minutu"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Během uplynulých 24 hodin nepoužito"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Během posledních 7 dní nepoužito"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Během uplynulého dne nepoužito}few{Během uplynulých # dní nepoužito}many{Během uplynulých # dne nepoužito}other{Během uplynulých # dní nepoužito}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Během uplynulé hodiny nepoužito}few{Během uplynulých # hodin nepoužito}many{Během uplynulých # hodiny nepoužito}other{Během uplynulých # hodin nepoužito}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Využíváno 1 aplikací}few{Využíváno # aplikacemi}many{Využíváno # aplikace}other{Využíváno # aplikacemi}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Zobrazit vše na panelu"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrováno podle: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Povolit pouze přístup k médiím"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Povolit vždy"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Povolit jen při používání aplikace"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Povolit všechny fotky"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Povolit vybrané fotky"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Pokaždé se zeptat"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Nepovolovat"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Přesná poloha"</string>
@@ -235,7 +239,7 @@
<string name="permission_description_summary_nearby_devices" msgid="8269183818275073741">"Aplikace s tímto oprávněním mohou najít zařízení v okolí, připojit se k nim a zjistit jejich relativní polohu"</string>
<string name="permission_description_summary_microphone" msgid="630834800308329907">"Aplikace s tímto oprávněním mohou nahrávat zvuk"</string>
<string name="permission_description_summary_phone" msgid="4515277217435233619">"Aplikace s tímto oprávněním mohou realizovat a spravovat telefonické hovory"</string>
- <string name="permission_description_summary_sensors" msgid="1836045815643119949">"Aplikace s tímto oprávněním mají přístup k datům ze senzorů vašich životních funkcí"</string>
+ <string name="permission_description_summary_sensors" msgid="1836045815643119949">"Aplikace s tímto oprávněním mají přístup k datům ze snímačů vašich životních funkcí"</string>
<string name="permission_description_summary_sms" msgid="725999468547768517">"Aplikace s tímto oprávněním mohou odesílat a zobrazovat SMS"</string>
<string name="permission_description_summary_storage" msgid="6575759089065303346">"Aplikace s tímto oprávněním mají přístup k  fotkám, médiím a souborům v zařízení"</string>
<string name="permission_description_summary_read_media_aural" msgid="3354728149930482199">"Aplikace s tímto oprávněním mají přístup k hudbě a dalším zvukovým souborům v zařízení"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nepovoleno"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Zobrazit další aplikace s přístupem ke všem souborům"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 den}few{# dny}many{# dne}other{# dní}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hodina}few{# hodiny}many{# hodiny}other{# hodin}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuta}few{# minuty}many{# minuty}other{# minut}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekunda}few{# sekundy}many{# sekundy}other{# sekund}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hodinu}few{# hodiny}many{# hodiny}other{# hodin}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuta}few{# minuty}many{# minuty}other{# minut}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekunda}few{# sekundy}many{# sekundy}other{# sekund}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Připomenutí o oprávněních"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"Nepoužívané aplikace: 1"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Nepoužívané aplikace: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Nastavit <xliff:g id="APP_NAME">%1$s</xliff:g> jako výchozí navigační aplikaci?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Není potřeba žádné oprávnění"</string>
<string name="role_watch_description" msgid="267003778693177779">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> bude moci interagovat s vašimi oznámeními a získá přístup k telefonu, SMS, kontaktům a kalendáři."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> bude moci interagovat s vašimi oznámeními a získat přístup k vašim oprávněním k telefonu, SMS, kontaktům, mikrofonu a zařízením v okolí."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> bude moci interagovat s vašimi oznámeními a streamovat vaše aplikace do připojeného zařízení."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Aplikace <xliff:g id="APP_NAME">%1$s</xliff:g> bude moci streamovat obsah do zařízení v okolí."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Tato služba sdílí vaše fotky, média a oznámení z telefonu s ostatními zařízeními."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Aktuálně výchozí"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Příště se neptat"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Změnit na výchozí"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Povolit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; přístup k &lt;b&gt;fotkám, videím, hudbě, zvuku a dalším souborům&lt;/b&gt; v zařízení?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Povolit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; přístup k hudbě a zvuku v zařízení?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Povolit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; přístup k fotkám a videím v zařízení?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Udělit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; přístup k více fotkám?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Povolit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; nahrávat zvuk?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Aplikace bude moci zaznamenávat zvuk, pouze když ji budete používat"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Povolit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; nahrávat zvuk?"</string>
@@ -485,7 +500,7 @@
<string name="permgroupupgraderequestdetail_camera" msgid="6642747548010962597">"Tato aplikace chce pořizovat snímky a nahrávat videa kdykoli, dokonce i když ji nepoužíváte. "<annotation id="link">"Povolit v nastavení"</annotation></string>
<string name="permgrouprequest_calllog" msgid="2065327180175371397">"Povolit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; přístup k seznamu telefonních hovorů?"</string>
<string name="permgrouprequest_phone" msgid="1829234136997316752">"Povolit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uskutečňovat a spravovat telefonní hovory?"</string>
- <string name="permgrouprequest_sensors" msgid="4397358316850652235">"Povolit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; přístup k údajům ze senzorů vašich životních funkcí?"</string>
+ <string name="permgrouprequest_sensors" msgid="4397358316850652235">"Povolit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; přístup k údajům ze snímačů vašich životních funkcí?"</string>
<string name="permgroupupgraderequestdetail_sensors" msgid="6651914048792092835">"Aplikace požaduje přístup k datům ze senzorů vašich životních funkcí vždy, i když ji nebudete používat. Pokud tuto změnu chcete provést, "<annotation id="link">"přejděte do nastavení."</annotation></string>
<string name="permgroupbackgroundrequest_sensors" msgid="5661924322018503886">"Povolit aplikaci &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; přístup k datům ze senzorů vašich životních funkcí?"</string>
<string name="permgroupbackgroundrequestdetail_sensors" msgid="7726767635834043501">"Pokud chcete této aplikaci povolit trvalý přístup k datům z tělesných senzorů, i když aplikaci nepoužíváte, "<annotation id="link">"přejděte do nastavení"</annotation>"."</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Zobrazovat zprávu, když aplikace použijí text, obrázky nebo jiný obsah, který jste zkopírovali"</string>
<string name="show_password_title" msgid="2877269286984684659">"Zobrazovat hesla"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Při psaní se budou krátce zobrazovat zadané znaky"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Tato aplikace uvedla, že může sdílet data <xliff:g id="PERMISSION_NAME">%s</xliff:g> se třetími stranami"</string>
</resources>
diff --git a/PermissionController/res/values-da/strings.xml b/PermissionController/res/values-da/strings.xml
index 181348a88..a8ffebc07 100644
--- a/PermissionController/res/values-da/strings.xml
+++ b/PermissionController/res/values-da/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Behold \"Mens appen er i brug\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Behold \"Kun denne ene gang\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Mere info"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Giv adgang til alle billeder"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Vælg billeder"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Vælg flere billeder"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Vælg ikke flere billeder"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Tillad ikke alligevel"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Luk"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> ud af <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dag}one{# dag}other{# dage}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 time}one{# time}other{# timer}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min.}one{# min.}other{# min.}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sek.}one{# sek.}other{# sek.}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dag}one{# dag}other{# dage}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# time}one{# time}other{# timer}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min.}one{# min.}other{# min.}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sek.}one{# sek.}other{# sek.}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Alle tilladelser"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Nogensinde"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"De seneste 7 dage"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"De seneste 24 timer"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Den seneste time"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"De seneste 15 minutter"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Seneste minut"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Seneste dag}one{Seneste # dag}other{Seneste # dage}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Seneste time}one{Seneste # time}other{Seneste # timer}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Seneste minut}one{Seneste # minut}other{Seneste # minutter}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Ingen brug af tilladelsen"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Seneste adgang nogensinde"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Seneste adgang i de sidste 7 dage"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Anvendte tilladelser den seneste time"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Anvendte tilladelser i de sidste 15 minutter"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Anvendte tilladelser det seneste minut"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Ikke brugt i de seneste 24 timer"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Ikke brugt i de seneste 7 dage"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Ikke brugt i # dag}one{Ikke brugt i # dag}other{Ikke brugt i de seneste # dage}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Ikke brugt i # time}one{Ikke brugt i # time}other{Ikke brugt i de seneste # timer}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Anvendes af 1 app}one{Anvendes af # app}other{Anvendes af # apps}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Se alt i kontrolpanelet"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtreret efter: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Tillad kun adgang til mediefiler"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Tillad altid"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Tillad kun, mens appen er i brug"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Tillad alle billeder"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Tillad valgte billeder"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Spørg hver gang"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Tillad ikke"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Præcis lokation"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Ikke tilladt"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Se flere apps, som kan tilgå alle filer"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dag}one{# dag}other{# dage}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 time}one{# time}other{# timer}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minut}one{# minut}other{# minutter}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekund}one{# sekund}other{# sekunder}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# time}one{# time}other{# timer}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minut}one{# minut}other{# minutter}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekund}one{# sekund}other{# sekunder}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Påmindelser om tilladelse"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 app, du ikke bruger"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> apps, du ikke bruger"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Vil du angive <xliff:g id="APP_NAME">%1$s</xliff:g> som din standardapp til navigation?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Der kræves ingen tilladelser"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> får tilladelse til at interagere med dine notifikationer og får adgang til dine tilladelser for Opkald, Sms, Kontakter og Kalender."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> får tilladelse til at interagere med dine notifikationer og tilgå tilladelserne Telefon, Sms, Kontakter, Mikrofon og Enheder i nærheden."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> får tilladelse til at interagere med dine notifikationer og streame dine apps til forbundne enheder."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> får tilladelse til at streame indhold til enheder i nærheden."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Denne tjeneste deler dine billeder, medier og notifikationer fra din telefon til andre enheder."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Nuværende standardapp"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Spørg ikke igen"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Angiv som standard"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Vil du give &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; adgang til &lt;b&gt;billeder, videoer, musik, lyd og andre filer&lt;/b&gt; på denne enhed?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Vil du give &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; adgang til musik og lyd på denne enhed?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Vil du give &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; adgang til billeder og videoer på denne enhed?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Vil du give &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; adgang til flere billeder?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Vil du give &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tilladelse til at optage lyd?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Appen kan kun optage lyd, mens du bruger appen"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Vil du give &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tilladelse til at optage lyd?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Vis en meddelelse, når apps får adgang til tekst, billeder eller andet indhold, du har kopieret"</string>
<string name="show_password_title" msgid="2877269286984684659">"Vis adgangskoder"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Vis kort tegnene, mens du skriver"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Denne app har angivet, at den muligvis deler <xliff:g id="PERMISSION_NAME">%s</xliff:g>-data med tredjeparter"</string>
</resources>
diff --git a/PermissionController/res/values-de/strings.xml b/PermissionController/res/values-de/strings.xml
index 498ef4471..dfb484868 100644
--- a/PermissionController/res/values-de/strings.xml
+++ b/PermissionController/res/values-de/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"„Wenn die App verwendet wird“ beibehalten"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"\"Nur dieses Mal\" aktiviert lassen"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Weitere Infos"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Zugriff auf alle Fotos erlauben"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Fotos auswählen"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Weitere Fotos auswählen"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Keine weiteren Fotos auswählen"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Trotzdem nicht zulassen"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Schließen"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> von <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 Tag}other{# Tage}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 Stunde}other{# Stunden}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# Tag}other{# Tage}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# Stunde}other{# Stunden}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Alle Berechtigungen"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Beliebiger Zeitraum"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Letzte 7 Tage"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Letzte 24 Stunden"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Letzte Stunde"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Letzte 15 Minuten"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Letzte Minute"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Letzter Tag}other{Letzte # Tage}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Letzte Stunde}other{Letzte # Stunden}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Letzte Minute}other{Letzte # Minuten}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Keine Berechtigungen verwendet"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Letzter Zugriff zu einem beliebigen Zeitpunkt"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Letzter Zugriff in den letzten 7 Tagen"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Berechtigungsnutzungen (letzte Stunde)"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Berechtigungsnutzungen (letzte 15 Minuten)"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Berechtigungsnutzungen (letzte Minute)"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"In den letzten 24 Stunden nicht verwendet"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"In den letzten 7 Tagen nicht verwendet"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Innerhalb des letzten Tages nicht verwendet}other{In den letzten # Tagen nicht verwendet}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Innerhalb der letzten Stunde nicht verwendet}other{In den letzten # Stunden nicht verwendet}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Von 1 App verwendet}other{Von # Apps verwendet}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Alle im Dashboard ansehen"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Gefiltert nach: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Zugriff nur auf Mediendateien zulassen"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Immer zulassen"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Zugriff nur während der Nutzung der App zulassen"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Alle Fotos erlauben"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Ausgewählte Fotos erlauben"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Jedes Mal fragen"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Nicht zulassen"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Auf genauen Standort zugreifen"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nicht zugelassen"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Weitere Apps anzeigen, die auf alle Dateien zugreifen können"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 Tag}other{# Tage}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 Stunde}other{# Stunden}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 Minute}other{# Minuten}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 Sekunde}other{# Sekunden}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# Stunde}other{# Stunden}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# Minute}other{# Minuten}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# Sekunde}other{# Sekunden}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Berechtigungserinnerungen"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 nicht verwendete App"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> nicht verwendete Apps"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> als standardmäßige Navigations-App festlegen?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Keine Berechtigungen erforderlich"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> darf mit deinen Benachrichtigungen interagieren und auf die Berechtigungen „Telefon“, „SMS“, „Kontakte“ und „Kalender“ zugreifen."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> darf mit deinen Benachrichtigungen interagieren und auf die Berechtigungen „Telefon“, „SMS“, „Kontakte“, „Mikrofon“ und „Geräte in der Nähe“ zugreifen."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> darf mit deinen Benachrichtigungen interagieren und Apps auf deinen verbundenen Geräten streamen."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> darf Inhalte auf Geräte in der Nähe streamen."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Dieser Dienst teilt die Fotos, Medien und Benachrichtigungen von deinem Smartphone mit anderen Geräten."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Aktueller Standard"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Nicht mehr fragen"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Als Standard festlegen"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; erlauben, auf &lt;b&gt;Foto-, Video-, Musik-, Audio- und andere Dateien&lt;/b&gt; auf diesem Gerät zuzugreifen?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; erlauben, auf Musik- und Audiodateien auf diesem Gerät zuzugreifen?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; erlauben, auf Fotos und Videos auf diesem Gerät zuzugreifen?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; Zugriff auf weitere Fotos erlauben?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; erlauben, Audioaufnahmen zu machen?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Diese App kann nur Audioaufnahmen machen, solange du sie verwendest"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; erlauben, Audioaufnahmen zu machen?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Eine Meldung wird angezeigt, wenn Apps auf Text, Bilder oder andere Inhalte zugreifen, die du kopiert hast"</string>
<string name="show_password_title" msgid="2877269286984684659">"Passwörter anzeigen"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Zeichen während der Eingabe kurz anzeigen"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Für diese App wurde angegeben, dass sie eventuell Daten vom Typ „<xliff:g id="PERMISSION_NAME">%s</xliff:g>“ an Dritte weitergibt"</string>
</resources>
diff --git a/PermissionController/res/values-el/strings.xml b/PermissionController/res/values-el/strings.xml
index 045d2a1d6..a7a490e67 100644
--- a/PermissionController/res/values-el/strings.xml
+++ b/PermissionController/res/values-el/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Διατήρηση της επιλογής \"Όταν χρησιμοποιείται η εφαρμογή\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Διατήρηση μόνο αυτήν τη φορά"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Περισσότερα"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Να επιτρέπεται η πρόσβαση σε όλες τις φωτογραφίες"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Επιλογή φωτογραφιών"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Επιλογή περισσότερων φωτογραφιών"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Να μην επιλεγούν περισσότερες φωτογραφίες"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Να μην επιτρέπεται καθόλου"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Παράβλεψη"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> από <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ημέρα}other{# ημέρες}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ώρα}other{# ώρες}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 λεπτό}other{# λεπτά}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 δευτ.}other{# δευτ.}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ημέρα}other{# ημέρες}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ώρα}other{# ώρες}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# λεπτό}other{# λεπτά}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# δευτερόλεπτο}other{# δευτερόλεπτα}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Οποιαδήποτε άδεια"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"οποιαδήποτε στιγμή"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"τελευταίες 7 ημέρες"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"τελευταίες 24 ώρες"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"τελευταία 1 ώρα"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"τελευταία 15 λεπτά"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"τελευταίο 1 λεπτό"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Τελευταία # ημέρα}other{Τελευταίες # ημέρες}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Τελευταία # ώρα}other{Τελευταίες # ώρες}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Τελευταίο # λεπτό}other{Τελευταία # λεπτά}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Καμία χρήση δικαιωμάτων"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Πιο πρόσφατη πρόσβαση ανά πάσα στιγμή"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Πιο πρόσφατη πρόσβαση τις τελευταίες 7 ημέρες"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Χρήση άδειας την τελευταία 1 ώρα"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Χρήση αδειών τα τελευταία 15 λεπτά"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Χρήση άδειας το τελευταίο 1 λεπτό"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Δεν έχει χρησιμοποιηθεί τις τελευταίες 24 ώρες"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Δεν έχει χρησιμοποιηθεί τις τελευταίες επτά ημέρες"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Δεν έχει χρησιμοποιηθεί την τελευταία # ημέρα}other{Δεν έχει χρησιμοποιηθεί τις τελευταίες # ημέρες}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Δεν έχει χρησιμοποιηθεί την τελευταία # ώρα}other{Δεν έχει χρησιμοποιηθεί τις τελευταίες # ώρες}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Χρησιμοποιείται από 1 εφαρμογή}other{Χρησιμοποιείται από # εφαρμογές}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Εμφάνιση όλων στον Πίνακα ελέγχου"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Φιλτράρισμα κατά: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Να επιτρέπεται η διαχείριση μόνο των μέσων"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Να επιτρέπεται πάντα"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Μόνο με τη χρήση της εφαρμογής"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Να επιτρέπονται όλες οι φωτογραφίες"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Να επιτρέπονται οι επιλεγμένες φωτογραφίες"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Να ερωτώμαι κάθε φορά"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Να μην επιτρέπεται"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Ακριβής τοποθεσία"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Δεν επιτρέπεται"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Δείτε περισσότερες εφαρμογές με πρόσβαση σε όλα τα αρχεία"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ημέρα}other{# ημέρες}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ώρα}other{# ώρες}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 λεπτό}other{# λεπτά}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 δευτερόλεπτο}other{# δευτερόλεπτα}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ώρα}other{# ώρες}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# λεπτό}other{# λεπτά}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# δευτερόλεπτο}other{# δευτερόλεπτα}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Υπενθυμίσεις άδειας"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 εφαρμογή που δεν χρησιμοποιείται"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> εφαρμογές που δεν χρησιμοποιούνται"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Ορισμός <xliff:g id="APP_NAME">%1$s</xliff:g> ως προεπιλεγμένης εφαρμογής πλοήγησης;"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Δεν απαιτούνται άδειες"</string>
<string name="role_watch_description" msgid="267003778693177779">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> θα επιτρέπεται να αλληλεπιδρά με τις ειδοποιήσεις σας και να έχει πρόσβαση στις άδειες Τηλεφώνου, SMS, Επαφών και Ημερολογίου."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Θα επιτρέπεται στην εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> να αλληλεπιδρά με τις ειδοποιήσεις σας και να αποκτά πρόσβαση στις άδειες για το Τηλέφωνο, τα SMS, τις Επαφές, το Μικρόφωνο και τις Συσκευές σε κοντινή απόσταση."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> θα επιτρέπεται να αλληλεπιδρά με τις ειδοποιήσεις σας και να μεταδίδει τις εφαρμογές σας μέσω ροής στη συνδεδεμένη συσκευή."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Η εφαρμογή <xliff:g id="APP_NAME">%1$s</xliff:g> θα μπορεί να κάνει ροή περιεχομένου σε κοντινές συσκευές."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Αυτή η υπηρεσία κοινοποιεί τις φωτογραφίες, τα μέσα και τις ειδοποιήσεις από το τηλέφωνό σας στις άλλες συσκευές σας."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Τρέχουσα προεπιλογή"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Να μην ερωτηθώ ξανά"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Προεπιλογή"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Να επιτρέπεται στην εφαρμογή &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; η πρόσβαση σε &lt;b&gt;φωτογραφίες, βίντεο, μουσική, ήχο και άλλα αρχεία&lt;/b&gt; της συσκευής;"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Να επιτρέπεται στην εφαρμογή &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; η πρόσβαση στη μουσική και στα αρχεία ήχου αυτής της συσκευής;"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Να επιτρέπεται στην εφαρμογή &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; η πρόσβαση στις φωτογραφίες και τα βίντεο αυτής της συσκευής;"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Εκχώρηση πρόσβασης στο &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; σε περισσότερες φωτογραφίες;"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Να επιτρέπεται στο &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; η εγγραφή ήχου;"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Αυτή η εφαρμογή θα μπορεί να εγγράφει ήχο μόνο όταν τη χρησιμοποιείτε"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Να επιτρέπεται στο &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; η εγγραφή ήχου;"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Να εμφανίζεται ένα μήνυμα όταν οι εφαρμογές αποκτούν πρόσβαση σε κείμενο, εικόνες ή άλλο περιεχόμενο που έχετε αντιγράψει"</string>
<string name="show_password_title" msgid="2877269286984684659">"Εμφάνιση κωδικών πρόσβασης"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Σύντομη εμφάνιση χαρακτήρων κατά την πληκτρολόγηση"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Αυτή η εφαρμογή δηλώνει ότι ενδέχεται να κοινοποιεί δεδομένα που αφορούν την άδεια \"<xliff:g id="PERMISSION_NAME">%s</xliff:g>\" σε τρίτα μέρη"</string>
</resources>
diff --git a/PermissionController/res/values-en-rAU/strings.xml b/PermissionController/res/values-en-rAU/strings.xml
index 77ddf5f41..af34438e7 100644
--- a/PermissionController/res/values-en-rAU/strings.xml
+++ b/PermissionController/res/values-en-rAU/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Keep \'While the app is in use\'"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Keep \'Only this time\'"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"More info"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Allow access to all photos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Select photos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Select more photos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Don’t select more photos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Don’t allow anyway"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Dismiss"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> of <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 day}other{# days}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hour}other{# hours}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# mins}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sec}other{# secs}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# day}other{# days}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hour}other{# hours}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# mins}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sec}other{# secs}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Any permission"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Any time"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Last 7 days"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Last 24 hours"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Last 1 hour"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Last 15 minutes"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Last 1 minute"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Last # day}other{Last # days}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Last # hour}other{Last # hours}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Last # minute}other{Last # minutes}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"No permission usages"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Most recent access at any time"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Most recent access in last 7 days"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Permission usage in last 1 hour"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Permission usage in last 15 minutes"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Permission usage in last 1 minute"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Not used in past 24 hours"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Not used in past 7 days"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Not used in past # day}other{Not used in past # days}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Not used in past # hour}other{Not used in past # hours}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Used by 1 app}other{Used by # apps}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"See all in Dashboard"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtered by: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Allow access to media only"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Allow all the time"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Allow only while using the app"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Allow all photos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Allow selected photos"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Ask every time"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Don\'t allow"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Precise location"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Not allowed"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"See more apps that can access all files"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 day}other{# days}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hour}other{# hours}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minute}other{# minutes}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 second}other{# seconds}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hour}other{# hours}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minute}other{# minutes}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# second}other{# seconds}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Permission reminders"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 unused app"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> unused apps"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Set <xliff:g id="APP_NAME">%1$s</xliff:g> as your default navigation app?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"No permissions needed"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, microphone and Nearby devices permissions."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and stream your apps to the connected device."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to stream content to nearby devices."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"This service shares your photos, media and notifications from your phone to other devices."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Current default"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Don\'t ask again"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Set as default"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access &lt;b&gt;photos, videos, music, audio and other files&lt;/b&gt; on this device?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access music and audio on this device?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access photos and videos on this device?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Grant &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; access to more photos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to record audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"The app will only be able to record audio while you’re using the app"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to record audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Show a message when apps access text, images or other content that you’ve copied"</string>
<string name="show_password_title" msgid="2877269286984684659">"Show passwords"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Display characters briefly as you type"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"This app stated that it may share <xliff:g id="PERMISSION_NAME">%s</xliff:g> data with third parties"</string>
</resources>
diff --git a/PermissionController/res/values-en-rCA/strings.xml b/PermissionController/res/values-en-rCA/strings.xml
index eb723454e..58fa71b32 100644
--- a/PermissionController/res/values-en-rCA/strings.xml
+++ b/PermissionController/res/values-en-rCA/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Keep “While the app is in use”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Keep “Only this time”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"More info"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Allow access to all photos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Select photos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Select more photos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Don’t select more photos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Don’t allow anyway"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Dismiss"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> of <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 day}other{# days}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hour}other{# hours}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# mins}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sec}other{# secs}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# day}other{# days}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hour}other{# hours}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# mins}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sec}other{# secs}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Any permission"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Any time"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Last 7 days"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Last 24 hours"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Last 1 hour"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Last 15 minutes"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Last 1 minute"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Last # day}other{Last # days}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Last # hour}other{Last # hours}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Last # minute}other{Last # minutes}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"No permission usages"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Most recent access at any time"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Most recent access in last 7 days"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Permission usage in last 1 hour"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Permission usage in last 15 minutes"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Permission usage in last 1 minute"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Not used in past 24 hours"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Not used in past 7 days"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Not used in past # day}other{Not used in past # days}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Not used in past # hour}other{Not used in past # hours}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Used by 1 app}other{Used by # apps}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"See all in Dashboard"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtered by: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Allow access to media only"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Allow all the time"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Allow only while using the app"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Allow all photos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Allow selected photos"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Ask every time"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Don’t allow"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Precise location"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Not allowed"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"See more apps that can access all files"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 day}other{# days}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hour}other{# hours}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minute}other{# minutes}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 second}other{# seconds}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hour}other{# hours}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minute}other{# minutes}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# second}other{# seconds}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Permission reminders"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 unused app"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> unused apps"</string>
@@ -394,8 +398,14 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Set <xliff:g id="APP_NAME">%1$s</xliff:g> as your default navigation app?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"No permissions needed"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and stream your apps to the connected device."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to stream content to nearby devices."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"This service shares your photos, media, and notifications from your phone to other devices."</string>
+ <string name="role_notes_label" msgid="7694668779088299905">"Default note taking app"</string>
+ <string name="role_notes_short_label" msgid="6617887820096092689">"Note taking app"</string>
+ <string name="role_notes_description" msgid="8486216423668803751">"Apps that allow you to take notes"</string>
+ <string name="role_notes_search_keywords" msgid="7710756695666744631">"notes"</string>
<string name="request_role_current_default" msgid="738722892438247184">"Current default"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Don’t ask again"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Set as default"</string>
@@ -470,6 +480,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access &lt;b&gt;photos, videos, music, audio, and other files&lt;/b&gt; on this device?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access music and audio on this device?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access photos and videos on this device?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Grant &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; access to more photos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to record audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"The app will only be able to record audio while you’re using the app"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to record audio?"</string>
@@ -580,4 +591,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Show a message when apps access text, images, or other content you’ve copied"</string>
<string name="show_password_title" msgid="2877269286984684659">"Show passwords"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Display characters briefly as you type"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"This app stated it may share <xliff:g id="PERMISSION_NAME">%s</xliff:g> data with third parties"</string>
</resources>
diff --git a/PermissionController/res/values-en-rGB/strings.xml b/PermissionController/res/values-en-rGB/strings.xml
index ebd914609..0c4ba02a0 100644
--- a/PermissionController/res/values-en-rGB/strings.xml
+++ b/PermissionController/res/values-en-rGB/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Keep \'While the app is in use\'"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Keep \'Only this time\'"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"More info"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Allow access to all photos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Select photos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Select more photos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Don’t select more photos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Don’t allow anyway"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Dismiss"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> of <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 day}other{# days}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hour}other{# hours}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# mins}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sec}other{# secs}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# day}other{# days}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hour}other{# hours}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# mins}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sec}other{# secs}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Any permission"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Any time"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Last 7 days"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Last 24 hours"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Last 1 hour"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Last 15 minutes"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Last 1 minute"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Last # day}other{Last # days}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Last # hour}other{Last # hours}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Last # minute}other{Last # minutes}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"No permission usages"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Most recent access at any time"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Most recent access in last 7 days"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Permission usage in last 1 hour"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Permission usage in last 15 minutes"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Permission usage in last 1 minute"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Not used in past 24 hours"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Not used in past 7 days"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Not used in past # day}other{Not used in past # days}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Not used in past # hour}other{Not used in past # hours}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Used by 1 app}other{Used by # apps}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"See all in Dashboard"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtered by: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Allow access to media only"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Allow all the time"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Allow only while using the app"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Allow all photos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Allow selected photos"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Ask every time"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Don\'t allow"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Precise location"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Not allowed"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"See more apps that can access all files"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 day}other{# days}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hour}other{# hours}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minute}other{# minutes}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 second}other{# seconds}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hour}other{# hours}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minute}other{# minutes}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# second}other{# seconds}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Permission reminders"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 unused app"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> unused apps"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Set <xliff:g id="APP_NAME">%1$s</xliff:g> as your default navigation app?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"No permissions needed"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, microphone and Nearby devices permissions."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and stream your apps to the connected device."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to stream content to nearby devices."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"This service shares your photos, media and notifications from your phone to other devices."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Current default"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Don\'t ask again"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Set as default"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access &lt;b&gt;photos, videos, music, audio and other files&lt;/b&gt; on this device?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access music and audio on this device?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access photos and videos on this device?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Grant &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; access to more photos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to record audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"The app will only be able to record audio while you’re using the app"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to record audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Show a message when apps access text, images or other content that you’ve copied"</string>
<string name="show_password_title" msgid="2877269286984684659">"Show passwords"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Display characters briefly as you type"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"This app stated that it may share <xliff:g id="PERMISSION_NAME">%s</xliff:g> data with third parties"</string>
</resources>
diff --git a/PermissionController/res/values-en-rIN/strings.xml b/PermissionController/res/values-en-rIN/strings.xml
index ebd914609..0c4ba02a0 100644
--- a/PermissionController/res/values-en-rIN/strings.xml
+++ b/PermissionController/res/values-en-rIN/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Keep \'While the app is in use\'"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Keep \'Only this time\'"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"More info"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Allow access to all photos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Select photos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Select more photos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Don’t select more photos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Don’t allow anyway"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Dismiss"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> of <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 day}other{# days}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hour}other{# hours}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# mins}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sec}other{# secs}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# day}other{# days}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hour}other{# hours}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# mins}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sec}other{# secs}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Any permission"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Any time"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Last 7 days"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Last 24 hours"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Last 1 hour"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Last 15 minutes"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Last 1 minute"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Last # day}other{Last # days}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Last # hour}other{Last # hours}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Last # minute}other{Last # minutes}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"No permission usages"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Most recent access at any time"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Most recent access in last 7 days"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Permission usage in last 1 hour"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Permission usage in last 15 minutes"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Permission usage in last 1 minute"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Not used in past 24 hours"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Not used in past 7 days"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Not used in past # day}other{Not used in past # days}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Not used in past # hour}other{Not used in past # hours}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Used by 1 app}other{Used by # apps}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"See all in Dashboard"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtered by: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Allow access to media only"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Allow all the time"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Allow only while using the app"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Allow all photos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Allow selected photos"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Ask every time"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Don\'t allow"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Precise location"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Not allowed"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"See more apps that can access all files"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 day}other{# days}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hour}other{# hours}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minute}other{# minutes}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 second}other{# seconds}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hour}other{# hours}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minute}other{# minutes}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# second}other{# seconds}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Permission reminders"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 unused app"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> unused apps"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Set <xliff:g id="APP_NAME">%1$s</xliff:g> as your default navigation app?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"No permissions needed"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and access your phone, SMS, contacts, microphone and Nearby devices permissions."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to interact with your notifications and stream your apps to the connected device."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> will be allowed to stream content to nearby devices."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"This service shares your photos, media and notifications from your phone to other devices."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Current default"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Don\'t ask again"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Set as default"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access &lt;b&gt;photos, videos, music, audio and other files&lt;/b&gt; on this device?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access music and audio on this device?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to access photos and videos on this device?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Grant &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; access to more photos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to record audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"The app will only be able to record audio while you’re using the app"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Allow &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; to record audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Show a message when apps access text, images or other content that you’ve copied"</string>
<string name="show_password_title" msgid="2877269286984684659">"Show passwords"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Display characters briefly as you type"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"This app stated that it may share <xliff:g id="PERMISSION_NAME">%s</xliff:g> data with third parties"</string>
</resources>
diff --git a/PermissionController/res/values-en-rXC/strings.xml b/PermissionController/res/values-en-rXC/strings.xml
index b78542f94..88fd49ab8 100644
--- a/PermissionController/res/values-en-rXC/strings.xml
+++ b/PermissionController/res/values-en-rXC/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‏‏‏‎‏‏‏‎‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‏‎Keep “While the app is in use”‎‏‎‎‏‎"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‎‏‏‎‏‏‏‎‎‎‏‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‎‎Keep “Only this time”‎‏‎‎‏‎"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‎‏‎‎‏‎More info‎‏‎‎‏‎"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‎‎‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎Allow access to all photos‎‏‎‎‏‎"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‎‏‎‎‏‏‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎Select photos‎‏‎‎‏‎"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‏‏‎‎‎‏‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‎Select more photos‎‏‎‎‏‎"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‎Don’t select more photos‎‏‎‎‏‎"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‎‎‎‎‏‏‏‎‎‏‏‏‏‎Don’t allow anyway‎‏‎‎‏‎"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‏‏‎‎‏‎Dismiss‎‏‎‎‏‎"</string>
<string name="current_permission_template" msgid="7452035392573329375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‎‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>‎‏‎‎‏‏‏‎ of ‎‏‎‎‏‏‎<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="ACCESS_TIME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ • ‎‏‎‎‏‏‎<xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‏‏‏‎‎‏‏‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‏‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎‏‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ • ‎‏‎‎‏‏‎<xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎‏‏‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ • ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎ • ‎‏‎‎‏‏‎<xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎1 day‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‎‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎# days‎‏‎‎‏‎}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‎‎‎1 hour‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‎‏‎‎‏‎‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‏‏‎‎‏‏‎‎‎‎# hours‎‏‎‎‏‎}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎1 min‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‎‏‎‏‎‎‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎# mins‎‏‎‎‏‎}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎1 sec‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‏‏‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‎‎‎# secs‎‏‎‎‏‎}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎‏‏‏‎‏‎# day‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎‏‏‏‎‏‎# days‎‏‎‎‏‎}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‎# hour‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‎# hours‎‏‎‎‏‎}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎# min‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‎‏‎‏‏‎‏‎‏‎‎‏‏‎‏‎‎‏‏‎‏‏‎‎‎‎‎‎‏‎‏‎‏‏‎# mins‎‏‎‎‏‎}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‏‎‎‎‏‏‎# sec‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‏‎‏‎‎‎‏‏‎# secs‎‏‎‎‏‎}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‎‏‎‏‎Any permission‎‏‎‎‏‎"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‏‏‏‏‎‏‎‎‎‏‏‏‎‏‎‏‎‎‏‏‎Any time‎‏‎‎‏‎"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‎‎‏‎Last 7 days‎‏‎‎‏‎"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‏‎Last 24 hours‎‏‎‎‏‎"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‎Last 1 hour‎‏‎‎‏‎"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‎Last 15 minutes‎‏‎‎‏‎"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‎‏‎‎‏‏‏‎‎‏‎‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‎‏‏‏‎‎Last 1 minute‎‏‎‎‏‎"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‎‏‎Last # day‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‎‏‎‏‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‎‏‏‎‎‎‏‏‏‏‎‏‏‎‎‎‎‏‎Last # days‎‏‎‎‏‎}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‎‎‏‎‏‎‎‏‎‎Last # hour‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‏‎‎‏‎‎‎‎‎‎‎‎‎‎‎‏‎‎‏‎‏‎‎‏‎‎Last # hours‎‏‎‎‏‎}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎Last # minute‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‎‎‎‏‎‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎Last # minutes‎‏‎‎‏‎}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‏‏‎‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‏‎‎‏‏‎No permission usages‎‏‎‎‏‎"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‏‎‎‎‏‎‎‎‎‎‎‎‎‏‎‏‎‏‎‏‏‏‎Most recent access at any time‎‏‎‎‏‎"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎Most recent access in last 7 days‎‏‎‎‏‎"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎‏‎‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎Permission usage in last 1 hour‎‏‎‎‏‎"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‏‎‏‎‏‏‏‎‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎Permission usage in last 15 minutes‎‏‎‎‏‎"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‎‎‎‏‎‏‎‎‏‏‏‎‏‏‏‎Permission usage in last 1 minute‎‏‎‎‏‎"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‎‎‎‎‎‏‎‎‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‏‏‎‏‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‎‎‎‏‎Not used in past 24 hours‎‏‎‎‏‎"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‏‎‏‏‏‎‎‎‎‏‎‏‏‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‎‎Not used in past 7 days‎‏‎‎‏‎"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎Not used in past # day‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎Not used in past # days‎‏‎‎‏‎}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎Not used in past # hour‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‎‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‏‏‏‎‏‏‎‏‎‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎Not used in past # hours‎‏‎‎‏‎}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎Used by 1 app‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎‏‏‎‏‎‎Used by # apps‎‏‎‎‏‎}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎See all in Dashboard‎‏‎‎‏‎"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‏‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‏‏‏‏‏‎‎Filtered by: ‎‏‎‎‏‏‎<xliff:g id="PERM">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‏‎‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‎‏‎‏‎‎Allow access to media only‎‏‎‎‏‎"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‎‏‎‎‎‏‏‎Allow all the time‎‏‎‎‏‎"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‎‎‏‏‎‏‏‏‏‎‏‎‏‏‎‎‎‏‏‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‎Allow only while using the app‎‏‎‎‏‎"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‏‏‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‎‎‎‏‎‎‎‏‎‏‏‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‎‎‏‎‏‏‎‎‎Allow all photos‎‏‎‎‏‎"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‏‎‎‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‎Allow selected photos‎‏‎‎‏‎"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‏‏‏‏‏‎‎‎‎‎‎‏‏‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‎‎‎‏‎‎‏‏‎‏‎‎‎‎‎‎‏‏‎‎‏‏‎Ask every time‎‏‎‎‏‎"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‎‏‏‎‎‏‏‏‏‏‎‎‎Don’t allow‎‏‎‎‏‎"</string>
<string name="precise_image_description" msgid="6349638632303619872">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‎‎‎‎‎‎Precise location‎‏‎‎‏‎"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‏‏‎‏‏‏‎‏‏‎‎Not allowed‎‏‎‎‏‎"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎‎‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‎‏‎‎‏‎‎‎‎‏‏‏‎‏‎‎See more apps that can access all files‎‏‎‎‏‎"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎1 day‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‏‎‏‎‏‏‎‏‎‎‎‎‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎# days‎‏‎‎‏‎}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎1 hour‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‎‎‏‏‎‎‎‏‎‎‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎# hours‎‏‎‎‏‎}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‎‏‎1 minute‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‏‏‎‏‎‏‏‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‎‏‎# minutes‎‏‎‎‏‎}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎1 second‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎# seconds‎‏‎‎‏‎}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‎# hour‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‎‎# hours‎‏‎‎‏‎}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‏‎# minute‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‏‎‏‏‎‎‎‎‏‎‎‏‎# minutes‎‏‎‎‏‎}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎# second‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎# seconds‎‏‎‎‏‎}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‎‎‎‎‎‎‎‏‏‎‎‎‏‏‏‎‎‎‏‎‏‏‏‎‏‎‏‎‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎Permission reminders‎‏‎‎‏‎"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‏‎‎‏‏‏‎‎‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎1 unused app‎‏‎‎‏‎"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‎‎‎‎‏‏‏‏‎‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="NUMBER_OF_APPS">%s</xliff:g>‎‏‎‎‏‏‏‎ unused apps‎‏‎‎‏‎"</string>
@@ -394,8 +398,14 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‎‏‏‎‎‏‏‏‎‎‎‏‏‏‎‏‎‏‎‏‏‏‏‏‎‏‏‎‏‏‎‎‎Set ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ as your default navigation app?‎‏‎‎‏‎"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‏‎‏‏‎‏‎‏‏‏‏‎‎‎‎‎‏‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‎‎‏‎‏‏‎‎‏‎‎‎No permissions needed‎‏‎‎‏‎"</string>
<string name="role_watch_description" msgid="267003778693177779">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‏‏‎‏‎‎‏‏‎‏‎‏‏‎‎‎‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions.‎‏‎‎‏‎"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‎‎‏‎‏‏‏‎‎‏‎‏‏‏‏‎‎‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.‎‏‎‎‏‎"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to interact with your notifications and stream your apps to the connected device.‎‏‎‎‏‎"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‎‎‎‏‎‏‏‎‏‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ will be allowed to stream content to nearby devices.‎‏‎‎‏‎"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‏‏‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‎‏‎This service shares your photos, media, and notifications from your phone to other devices.‎‏‎‎‏‎"</string>
+ <string name="role_notes_label" msgid="7694668779088299905">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‎‏‎Default note taking app‎‏‎‎‏‎"</string>
+ <string name="role_notes_short_label" msgid="6617887820096092689">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‎‏‎‎‎‎‏‎‎‎‏‎Note taking app‎‏‎‎‏‎"</string>
+ <string name="role_notes_description" msgid="8486216423668803751">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‏‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‏‏‏‎Apps that allow you to take notes‎‏‎‎‏‎"</string>
+ <string name="role_notes_search_keywords" msgid="7710756695666744631">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‏‎‎‏‎‏‏‏‎‏‎‎‏‏‎‏‏‏‎notes‎‏‎‎‏‎"</string>
<string name="request_role_current_default" msgid="738722892438247184">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‎‎‎‎‎‎‏‏‏‏‎‎‎‏‎‎‎‏‏‏‎‏‎‎‎‎‏‏‏‎‎‏‏‏‎‏‏‎‎‎‏‎‏‏‏‎‎‎‏‎‎‎‎‎Current default‎‏‎‎‏‎"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‎‎‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‎‏‏‏‎‏‏‎‏‏‎‎‏‎‎Don’t ask again‎‏‎‎‏‎"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‎‎‎‎‏‎‏‎‏‎‎‏‏‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‏‎‎‎‎‎Set as default‎‏‎‎‏‎"</string>
@@ -470,6 +480,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‏‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‏‎‏‎‎‎Allow &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt; to access &lt;b&gt;photos, videos, music, audio, and other files&lt;/b&gt; on this device?‎‏‎‎‏‎"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‏‏‎‎‎‏‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‎‎Allow &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt; to access music and audio on this device?‎‏‎‎‏‎"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‎‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‎‏‏‎‏‎‎‎‎‏‏‎‏‏‏‎Allow &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt; to access photos and videos on this device?‎‏‎‎‏‎"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‏‎‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎‏‎‏‎‏‎Grant &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt; access to more photos?‎‏‎‎‏‎"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‏‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‎‏‏‏‏‎‎‏‎‎‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‎‎‎‏‏‎Allow &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt; to record audio?‎‏‎‎‏‎"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‎‏‏‎‎‏‏‏‏‎‎‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎The app will only be able to record audio while you’re using the app‎‏‎‎‏‎"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‎‏‏‏‎Allow &lt;b&gt;‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎&lt;/b&gt; to record audio?‎‏‎‎‏‎"</string>
@@ -580,4 +591,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‏‏‏‎‏‎‏‏‏‏‎Show a message when apps access text, images, or other content you’ve copied‎‏‎‎‏‎"</string>
<string name="show_password_title" msgid="2877269286984684659">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‎‎‎‏‏‏‎‎‏‏‎Show passwords‎‏‎‎‏‎"</string>
<string name="show_password_summary" msgid="1110166488865981610">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‎‎Display characters briefly as you type‎‏‎‎‏‎"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎This app stated it may share ‎‏‎‎‏‏‎<xliff:g id="PERMISSION_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ data with third parties‎‏‎‎‏‎"</string>
</resources>
diff --git a/PermissionController/res/values-es-rUS/strings.xml b/PermissionController/res/values-es-rUS/strings.xml
index ead99b5eb..e65fd4082 100644
--- a/PermissionController/res/values-es-rUS/strings.xml
+++ b/PermissionController/res/values-es-rUS/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Mantener en \"Mientras la app está en uso\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Mantener \"Solo esta vez\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Más información"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Permitir el acceso a todas las fotos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Seleccionar fotos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Seleccionar más fotos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"No seleccionar más fotos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"No permitir igualmente"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Ignorar"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> de <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 día}many{# días}other{# días}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hora}many{# horas}other{# horas}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}many{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}many{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# día}many{# días}other{# días}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hora}many{# horas}other{# horas}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# minuto}many{# minutos}other{# minutos}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# segundo}many{# segundos}other{# segundos}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Cualquier permiso"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Cualquier momento"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Últimos 7 días"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Últimas 24 horas"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Última hora"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Últimos 15 minutos"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Último minuto"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Último día (#)}many{Últimos # días}other{Últimos # días}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Última hora (#)}many{Últimas # horas}other{Últimas # horas}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Último minuto (#)}many{Últimos # minutos}other{Últimos # minutos}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Ningún uso de permisos"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Acceso más reciente en cualquier momento"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Acceso más reciente en los últimos 7 días"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Uso de permisos en la última hora"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Uso de permisos en los últimos 15 minutos"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Uso de permisos en el último minuto"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Sin uso en las últimas 24 horas"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"No se usó en los últimos 7 días"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{No se usó en el último día (#)}many{No se usó en los últimos # días}other{No se usó en los últimos # días}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{No se usó en la última hora (#)}many{No se usó en las últimas # horas}other{No se usó en las últimas # horas}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{En uso por parte de 1 app}many{En uso por parte de # apps}other{En uso por parte de # apps}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Ver todo en el panel"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrado por: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Solo permitir acceso al contenido multimedia"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Permitir todo el tiempo"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Permitir solo con la app en uso"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Permitir todas las fotos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Permitir las fotos seleccionadas"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Preguntar siempre"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"No permitir"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Ubicación precisa"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Sin permiso"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Ver más apps que pueden acceder a todos los archivos"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 día}many{# días}other{# días}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hora}many{# horas}other{# horas}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuto}many{# minutos}other{# minutos}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 segundo}many{# segundos}other{# segundos}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hora}many{# horas}other{# horas}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuto}many{# minutos}other{# minutos}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# segundo}many{# segundos}other{# segundos}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Recordatorios de permisos"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 app en desuso"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> apps que no usas"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"¿Quieres establecer <xliff:g id="APP_NAME">%1$s</xliff:g> como app de navegación predeterminada?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"No se requieren permisos"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> podrá interactuar con tus notificaciones y acceder a los permisos de Teléfono, SMS, Contactos y Calendario."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> podrá interactuar con tus notificaciones y acceder a los permisos de Teléfono, SMS, Contactos, Micrófono y Dispositivos cercanos."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> podrá interactuar con tus notificaciones y transmitir tus apps al dispositivo conectado."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> podrá reproducir contenido en dispositivos cercanos."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Este servicio comparte las fotos, el contenido multimedia y las notificaciones de tu teléfono con otros dispositivos."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"App predeterminada actualmente"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"No volver a preguntar"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Hacer predeterminada"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"¿Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda a &lt;b&gt;fotos, videos, música, audio y otros archivos&lt;/b&gt; del dispositivo?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"¿Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda a la música y los archivos de audio de este dispositivo?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"¿Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda a las fotos y los videos de este dispositivo?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"¿Quieres que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pueda acceder a más fotos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"¿Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grabe audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"La app solo podrá grabar audio cuando esté en uso"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"¿Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grabe audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Mostrar un mensaje cuando las apps accedan a textos, imágenes y otro contenido que hayas copiado"</string>
<string name="show_password_title" msgid="2877269286984684659">"Mostrar contraseñas"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Mostrar caracteres brevemente mientras escribes"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Esta app indicó que podría compartir datos de <xliff:g id="PERMISSION_NAME">%s</xliff:g> con terceros"</string>
</resources>
diff --git a/PermissionController/res/values-es/strings.xml b/PermissionController/res/values-es/strings.xml
index 9cf2c1787..90b3c0917 100644
--- a/PermissionController/res/values-es/strings.xml
+++ b/PermissionController/res/values-es/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Mantener \"Mientras la aplicación se esté usando\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Mantener \"Solo esta vez\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Más información"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Permitir acceso a todas las fotos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Seleccionar fotos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Seleccionar más fotos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"No seleccionar más fotos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"No permitir de todas formas"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Cerrar"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> de <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 día}many{# días}other{# días}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hora}many{# horas}other{# horas}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}many{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}many{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# día}many{# días}other{# días}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hora}many{# horas}other{# horas}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}many{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}many{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Cualquier permiso"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Cualquier fecha"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Últimos 7 días"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Últimas 24 horas"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Última hora"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Últimos 15 minutos"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Último minuto"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Último día}many{Últimos # días}other{Últimos # días}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Última hora}many{Últimas # horas}other{Últimas # horas}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Último minuto}many{Últimos # minutos}other{Últimos # minutos}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"No se han usado los permisos"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Acceso más reciente en cualquier momento"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Acceso más reciente en los últimos 7 días"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Uso de permisos en la última hora"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Uso de permisos en los últimos 15 minutos"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Uso de permisos en el último minuto"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"No se ha usado en las últimas 24 horas"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"No se ha usado en los últimos 7 días"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{No se ha usado en el último día}many{No se ha usado en los últimos # días}other{No se ha usado en los últimos # días}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{No se ha usado en la última hora}many{No se ha usado en las últimas # horas}other{No se ha usado en las últimas # horas}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Usado por 1 aplicación}many{Usado por # aplicaciones}other{Usado por # aplicaciones}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Ver todo en el panel de control"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrados por: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Permitir acceso solo al contenido multimedia"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Permitir siempre"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Permitir solo mientras se usa la aplicación"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Permitir todas la fotos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Permitir fotos seleccionadas"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Preguntar siempre"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"No permitir"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Ubicación precisa"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"No permitido"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Ver más aplicaciones que pueden acceder a todos los archivos"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 día}many{# días}other{# días}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hora}many{# horas}other{# horas}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuto}many{# minutos}other{# minutos}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 segundo}many{# segundos}other{# segundos}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hora}many{# horas}other{# horas}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuto}many{# minutos}other{# minutos}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# segundo}many{# segundos}other{# segundos}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Recordatorios de permisos"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 aplicación no usada"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> aplicaciones no usadas"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"¿Definir <xliff:g id="APP_NAME">%1$s</xliff:g> como tu aplicación de navegación predeterminada?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"No se necesita ningún permiso"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> podrá interactuar con tus notificaciones y acceder a tu teléfono, SMS, contactos y calendario."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> podrá interactuar con tus notificaciones y acceder a tus permisos de teléfono, SMS, contactos, micrófono y dispositivos cercanos."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> podrá interactuar con tus notificaciones y reproducir tus aplicaciones en el dispositivo conectado."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> podrá emitir contenido a dispositivos cercanos."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Este servicio comparte las fotos, el contenido multimedia y las notificaciones de tu teléfono con otros dispositivos."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Predeterminada"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"No volver a preguntar"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Establecer como predeterminado"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"¿Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda a &lt;b&gt;fotos, vídeos, música, audio y otros archivos&lt;/b&gt; del dispositivo?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"¿Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda a música y audio de este dispositivo?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"¿Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda a fotos y vídeos de este dispositivo?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"¿Quieres permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda a más fotos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"¿Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grabe audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"La aplicación solo podrá grabar audio mientras la estés usando."</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"¿Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grabe audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Muestra un mensaje cuando las aplicaciones acceden a texto, imágenes u otro contenido que has copiado"</string>
<string name="show_password_title" msgid="2877269286984684659">"Mostrar contraseñas"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Muestra los caracteres brevemente mientras escribes"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Esta aplicación ha indicado que es posible que comparta datos de <xliff:g id="PERMISSION_NAME">%s</xliff:g> con terceros"</string>
</resources>
diff --git a/PermissionController/res/values-et/strings.xml b/PermissionController/res/values-et/strings.xml
index e46ce49ca..e3f2bef83 100644
--- a/PermissionController/res/values-et/strings.xml
+++ b/PermissionController/res/values-et/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Säilita valik „Rakenduse kasutamise ajal”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Säilita ainult sel korral"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Lisateave"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Luba juurdepääs kõikidele fotodele"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Vali fotod"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Valige veel fotosid"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Ära vali rohkem fotosid"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ära luba ikkagi"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Loobu"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>/<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 päev}other{# päeva}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 tund}other{# tundi}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# päev}other{# päeva}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# tund}other{# tundi}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Mis tahes luba"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Mis tahes ajal"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Viimased seitse päeva"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Viimased 24 tundi"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Viimane tund"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Viimased 15 minutit"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Viimane 1 minut"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Viimane päev}other{Viimased # päeva}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Viimane tund}other{Viimased # tundi}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Viimane minut}other{Viimased # minutit}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Lube pole kasutatud"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Hiljutisim juurdepääs mis tahes ajal"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Hiljutisim juurdepääs viimase 7 päeva jooksul"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Lubade kasutus viimase 1 tunni jooksul"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Lubade kasutus viimase 15 minuti jooksul"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Lubade kasutus viimase 1 minuti jooksul"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Pole viimase 24 tunni jooksul kasutatud"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Pole viimase 7 päeva jooksul kasutatud"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Pole viimase # päeva jooksul kasutatud}other{Pole viimase # päeva jooksul kasutatud}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Pole viimase # tunni jooksul kasutatud}other{Pole viimase # tunni jooksul kasutatud}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Kasutab 1 rakendus}other{Kasutavad # rakendust}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Kuva kõik juhtpaneelil"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtreerimisalus: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Luba juurdepääs ainult meediale"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Luba alati"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Luba rakenduse kasutamise ajal"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Luba kõik fotod"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Luba valitud fotod"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Küsi iga kord"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Ära luba"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Täpne asukoht"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Pole lubatud"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Kuva rohkem rakendusi, mis kõigile failidele juurde pääsevad"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 päev}other{# päeva}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 tund}other{# tundi}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minut}other{# minutit}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekund}other{# sekundit}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# tund}other{# tundi}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minut}other{# minutit}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekund}other{# sekundit}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Loa meeldetuletused"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 kasutamata rakendus"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> kasutamata rakendust"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Kas määrata <xliff:g id="APP_NAME">%1$s</xliff:g> navigeerimise vaikerakenduseks?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Lube ei ole vaja"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> saab kasutada teie märguandeid ning hankida teie telefoni, SMS-ide, kontaktide ja kalendri load."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Rakendusel <xliff:g id="APP_NAME">%1$s</xliff:g> lubatakse kasutada teie märguandeid ning pääseda juurde teie telefoni, SMS-ide, kontaktide, mikrofoni ja läheduses olevate seadmete lubadele."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Rakendusel <xliff:g id="APP_NAME">%1$s</xliff:g> lubatakse kasutada teie märguandeid ja voogesitada teie rakenduste kuva ühendatud seadmesse."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> lubatakse läheduses olevatesse seadmetesse sisu voogesitada."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"See teenus jagab teie fotosid, meediat ja märguandeid teie telefonist muudesse seadmetesse."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Praegune vaikeseade"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ära enam küsi"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Määra vaikeseadeks"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Anda rakendusele &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; juurdep. &lt;b&gt;foto-, video-, muusika-, heli- ja muudele failidele&lt;/b&gt;?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Kas anda rakendusele &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; selles seadmes juurdepääs muusikale ja helifailidele?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Kas anda rakendusele &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; selles seadmes juurdepääs fotodele ja videotele?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Kas anda rakendusele &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; juurdepääs rohkematele fotodele?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Kas lubada rakendusel &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; salvestada heli?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Rakendus saab heli salvestada vaid siis, kui rakendust kasutate"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Kas lubada rakendusel &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; heli salvestada?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Kui rakendused pääsevad juurde kopeeritud tekstile, piltidele või muule sisule, kuvatakse teade"</string>
<string name="show_password_title" msgid="2877269286984684659">"Kuva paroolid"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Sisestamisel kuvatakse hetkeks tähemärgid"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"See rakendus andis teada, et võib loa <xliff:g id="PERMISSION_NAME">%s</xliff:g> andmeid jagada kolmandate osapooltega"</string>
</resources>
diff --git a/PermissionController/res/values-eu/strings.xml b/PermissionController/res/values-eu/strings.xml
index 8eb01a0b0..b706e3044 100644
--- a/PermissionController/res/values-eu/strings.xml
+++ b/PermissionController/res/values-eu/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Mantendu “Aplikazioa abian denean” aukera"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Mantendu \"Oraingo honetan soilik\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Datu gehiago"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Eman argazki guztiak atzitzeko baimena"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Hautatu argazkiak"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Hautatu argazki gehiago"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Ez hautatu argazki gehiago"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ez eman baimenik halere"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Baztertu"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>/<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 egun}other{# egun}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ordu}other{# ordu}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# egun}other{# egun}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ordu}other{# ordu}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Edozein baimen"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Edonoiz"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Azken 7 egunetan"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Azken 24 orduetan"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Azken orduan"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Azken 15 minutuetan"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Azken minutuan"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Azken # egunean}other{Azken # egunetan}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Azken # orduan}other{Azken # orduetan}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Azken # minutuan}other{Azken # minutuetan}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Ez da eskatu baimenik"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Orain arteko azken sarbidea"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Azken zazpi egunetako azken sarbidea"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Azken ordubetean baimenei eman zaien erabilera"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Azken 15 minutuetan baimenei eman zaien erabilera"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Azken minutuan baimenei eman zaien erabilera"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Ez da erabili azken 24 orduetan"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Ez da erabili azken zazpi egunetan"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Ez da erabili azken # egunean}other{Ez da erabili azken # egunetan}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Ez da erabili azken # orduan}other{Ez da erabili azken # orduetan}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Aplikazio batek erabili du}other{# aplikaziok erabili dute}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Ikusi guztiak panelean"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Iragazteko irizpidea: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Eman multimedia-fitxategiak soilik atzitzeko baimena"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Eman baimena beti"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Aplikazioa erabiltzean soilik"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Eman argazki guztiak atzitzeko baimena"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Eman hautatutako argazkiak atzitzeko baimena"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Galdetu beti"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Ez eman baimenik"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Kokapen zehatza"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Baimendu gabe"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Ikusi fitxategi guztiak atzi ditzaketen aplikazio gehiago"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 egun}other{# egun}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ordu}other{# ordu}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minutu}other{# minutu}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 segundo}other{# segundo}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ordu}other{# ordu}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minutu}other{# minutu}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# segundo}other{# segundo}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Baimenen abisuak"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"Erabiltzen ez den 1 aplikazio"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Erabiltzen ez diren <xliff:g id="NUMBER_OF_APPS">%s</xliff:g> aplikazio"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> ezarri nahi duzu nabigatzeko aplikazio lehenetsi gisa?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Ez du behar baimenik"</string>
<string name="role_watch_description" msgid="267003778693177779">"Honako hauek erabiltzeko baimena izango du <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak: jakinarazpenak, Telefonoa aplikazioa, SMSak, kontaktuak eta egutegia."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Jakinarazpenekin interakzioan aritzeko, eta telefonoa, SMSak, kontaktuak, mikrofonoa eta inguruko gailuak erabiltzeko baimena izango du <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Jakinarazpenekin interakzioan aritzeko eta aplikazioak konektatutako gailura igortzeko baimena izango du <xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> aplikazioak edukia inguruko gailuetara igorri ahalko du."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Zerbitzu honek zure telefonoko argazkiak, multimedia-edukia eta jakinarazpenak partekatzen ditu beste gailuekin."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Aplikazio lehenetsia"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ez galdetu berriro"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Ezarri lehenetsi gisa"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Gailuko &lt;b&gt;argazkiak, bideoak, musika, audioa eta bestelako fitxategiak&lt;/b&gt; atzitzeko baimena eman nahi diozu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aplikazioari?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Gailuko musika eta audioa atzitzeko baimena eman nahi diozu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aplikazioari?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Gailuko argazkiak eta bideoak atzitzeko baimena eman nahi diozu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aplikazioari?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Argazki gehiago atzitzeko baimena eman nahi diozu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aplikazioari?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Audioa grabatzeko baimena eman nahi diozu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aplikazioari?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Aplikazioak hura erabiltzean soilik grabatuko du audioa"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Audioa grabatzeko baimena eman nahi diozu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aplikazioari?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Erakutsi mezu bat aplikazio batek kopiatu dituzun testuak, irudiak edo edukiak atzitzen dituenean"</string>
<string name="show_password_title" msgid="2877269286984684659">"Erakutsi pasahitzak"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Idatzi ahala, erakutsi karaktereak laburki"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Agian hirugarrenekin honi buruzko datuak partekatuko dituela adierazi du aplikazioaren garatzaileak: <xliff:g id="PERMISSION_NAME">%s</xliff:g>"</string>
</resources>
diff --git a/PermissionController/res/values-fa/strings.xml b/PermissionController/res/values-fa/strings.xml
index c0a896418..b3ea3ed96 100644
--- a/PermissionController/res/values-fa/strings.xml
+++ b/PermissionController/res/values-fa/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"تغییر ندادن اجازه «هنگامی که از برنامه استفاده می‌شود»"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"«فقط این بار» نگه داشته شود"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"اطلاعات بیشتر"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"مجاز کردن دسترسی به همه عکس‌ها"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"انتخاب عکس مجاز است"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"انتخاب عکس‌های بیشتر مجاز است"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"انتخاب عکس‌های بیشتر مجاز نیست"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"درهرصورت اجازه نیست"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"رد کردن"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> مجوز از <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> مجوز"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{یک روز}one{# روز}other{# روز}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{یک ساعت}one{# ساعت}other{# ساعت}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{یک دقیقه}one{# دقیقه}other{# دقیقه}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{یک ثانیه}one{# ثانیه}other{# ثانیه}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# روز}one{# روز}other{# روز}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ساعت}one{# ساعت}other{# ساعت}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# دقیقه}one{# دقیقه}other{# دقیقه}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# ثانیه}one{# ثانیه}other{# ثانیه}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"همه مجوزها"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"هر زمانی"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"۷ روز گذشته"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"۲۴ ساعت اخیر"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"۱ ساعت اخیر"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"۱۵ دقیقه اخیر"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"۱ دقیقه اخیر"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{# روز گذشته}one{# روز گذشته}other{# روز گذشته}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{# ساعت گذشته}one{# ساعت گذشته}other{# ساعت گذشته}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{# دقیقه گذشته}one{# دقیقه گذشته}other{# دقیقه گذشته}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"هیچ مجوزی استفاده نشده است"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"آخرین دسترسی در هرزمانی"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"آخرین دسترسی در ۷ روز گذشته"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"پراستفاده‌ترین مجوزها در ۱ ساعت گذشته"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"پراستفاده‌ترین مجوزها در ۱۵ دقیقه گذشته"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"پراستفاده‌ترین مجوزها در ۱ دقیقه گذشته"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"در ۲۴ ساعت گذشته استفاده نشده است"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"در ۷ روز گذشته استفاده نشده است"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{در # روز گذشته استفاده نشده است}one{در # روز گذشته استفاده نشده است}other{در # روز گذشته استفاده نشده است}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{در # ساعت گذشته استفاده نشده است}one{در # ساعت گذشته استفاده نشده است}other{در # ساعت گذشته استفاده نشده است}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{یک برنامه از آن استفاده کرده است}one{# برنامه از آن استفاده کرده است}other{# برنامه از آن استفاده کرده است}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"دیدن همه موارد در داشبورد"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"فیلترشده براساس: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"اجازه دادن فقط برای دسترسی به رسانه‌ها"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"همیشه مجاز است"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"فقط هنگام استفاده از برنامه مجاز است"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"مجاز کردن همه عکس‌ها"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"مجاز کردن عکس‌های منتخب"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"هربار پرسیده شود"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"اجازه ندادن"</string>
<string name="precise_image_description" msgid="6349638632303619872">"مکان دقیق"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"اجازه ندادن"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"دیدن برنامه‌های دیگری که به همه فایل‌ها دسترسی دارند"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{یک روز}one{# روز}other{# روز}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{یک ساعت}one{# ساعت}other{# ساعت}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{یک دقیقه}one{# دقیقه}other{# دقیقه}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{یک ثانیه}one{# ثانیه}other{# ثانیه}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ساعت}one{# ساعت}other{# ساعت}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# دقیقه}one{# دقیقه}other{# دقیقه}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# ثانیه}one{# ثانیه}other{# ثانیه}}"</string>
<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>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> به‌عنوان برنامه ناوبری پیش‌فرض تنظیم شود؟"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"اجازه لازم نیست"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> می‌تواند با اعلان‌های شما تعامل داشته باشد و به اجازه‌های تلفن، پیامک، مخاطبین، و «تقویم» دسترسی یابد."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"به <xliff:g id="APP_NAME">%1$s</xliff:g> اجازه داده می‌شود با اعلان‌های شما تعامل داشته باشد و به اجازه‌های «تلفن»، «پیامک»، «مخاطبین»، «میکروفون»، و «دستگاه‌های اطراف» دسترسی داشته باشد."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> می‌تواند با اعلان‌های شما تعامل داشته باشد و برنامه‌هایتان را در دستگاه متصل جاری‌سازی کند."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> می‌تواند محتوا را در دستگاه‌های اطراف جاری‌سازی کند."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"این سرویس عکس‌ها، رسانه‌ها، و اعلان‌های تلفنتان را با دستگاه‌های دیگر هم‌رسانی می‌کند."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"برنامه پیش‌فرض کنونی"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"دوباره سؤال نشود"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"تنظیم برای پیش‌فرض"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"‏به &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; اجازه می‌دهید به &lt;b&gt;عکس‌ها، ویدیوها، موسیقی، صوت، و فایل‌های دیگر&lt;/b&gt; این دستگاه دسترسی داشته باشد؟"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"‏به &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; اجازه می‌دهید به فایل‌های موسیقی و صوتی در این دستگاه دسترسی داشته باشد؟"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"‏به &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; اجازه می‌دهید به عکس‌ها و ویدیوهای این دستگاه دسترسی داشته باشد؟"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"‏به &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; اجازه می‌دهید به عکس‌های بیشتری دسترسی داشته باشد؟"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"‏به &lt;/b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; اجازه داده شود صدا ضبط کند؟"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"این برنامه فقط وقتی از آن استفاده می‌کنید، می‌تواند صدا ضبط کند"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"‏به &lt;/b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; اجازه داده شود صدا ضبط کند؟"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"وقتی برنامه‌ها به نوشتار، تصویر، یا محتوای دیگری که کپی کرده‌اید دسترسی پیدا می‌کنند، پیامی نشان داده می‌شود"</string>
<string name="show_password_title" msgid="2877269286984684659">"نمایش گذرواژه‌ها"</string>
<string name="show_password_summary" msgid="1110166488865981610">"همان‌طور که تایپ می‌کنید، نویسه‌ها را برای مدت کوتاهی نشان می‌دهد"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"این برنامه بیان کرده است که ممکن است داده‌های <xliff:g id="PERMISSION_NAME">%s</xliff:g> را با اشخاص ثالث هم‌رسانی کند"</string>
</resources>
diff --git a/PermissionController/res/values-fi/strings.xml b/PermissionController/res/values-fi/strings.xml
index 9dc1d12df..8ac508246 100644
--- a/PermissionController/res/values-fi/strings.xml
+++ b/PermissionController/res/values-fi/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Pidä \"Kun sovellusta käytetään\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Säilytä \"Vain tällä kertaa\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Lisätietoja"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Salli pääsy kaikkiin kuviin"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Valitse kuvat"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Valitse lisää kuvia"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Älä valitse enempää kuvia"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Älä salli silti"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Ohita"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>/<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 päivä}other{# päivää}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 tunti}other{# tuntia}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# päivä}other{# päivää}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# tunti}other{# tuntia}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Kaikki käyttöoikeudet"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Milloin tahansa"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Viimeiset 7 päivää"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Viimeiset 24 tuntia"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Viimeisin tunti"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Viimeiset 15 minuuttia"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Viimeinen minuutti"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Edellinen päivä}other{Viimeiset # päivää}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Viimeisin tunti}other{Viimeiset # tuntia}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Viimeinen minuutti}other{Viimeiset # minuuttia}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Käyttöoikeuksia ei käytetty"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Viimeksi käytetyt koska tahansa"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Viimeksi käytetyt viimeisten 7 päivän ajalta"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Lupien käyttö viimeisen tunnin ajalta"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Lupien käyttö viimeisten 15 min ajalta"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Lupien käyttö viimeisen minuutin ajalta"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Ei käytetty 24 viime tunnin aikana"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Ei käytetty 7 viime päivän aikana"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Ei käytetty viimeisen päivän aikana}other{Ei käytetty # viime päivän aikana}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Ei käytetty viimeisen tunnin aikana}other{Ei käytetty # viime tunnin aikana}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 sovelluksen käyttämä}other{# sovelluksen käyttämä}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Näytä kaikki ohjauspaneelissa"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Suodatusperuste: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -187,7 +189,9 @@
<string name="app_permission_button_allow_all_files" msgid="1792232272599018825">"Salli kaikkien tiedostojen ylläpito"</string>
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Salli pääsy vain mediaan"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Salli aina"</string>
- <string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Salli vain käytön aikana"</string>
+ <string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Salli vain, kun sovellus on käytössä"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Salli kaikki kuvat"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Salli valitut kuvat"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Kysy aina"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Älä salli"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Tarkka sijainti"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Ei sallittu"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Muut sovellukset, joilla on pääsy kaikkiin tiedostoihin"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 päivä}other{# päivää}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 tunti}other{# tuntia}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuutti}other{# minuuttia}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekunti}other{# sekuntia}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# tunti}other{# tuntia}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuutti}other{# minuuttia}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekunti}other{# sekuntia}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Käyttölupamuistutukset"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 käyttämätön sovellus"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> käyttämätöntä sovellusta"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Asetetaanko <xliff:g id="APP_NAME">%1$s</xliff:g> navigoinnin oletussovellukseksi?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Lupia ei tarvita"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> saa luvan hallinnoida ilmoituksiasi sekä pääsyn puhelimeesi, tekstiviesteihisi, kontakteihisi ja kalenteriisi."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> saa luvan hallinnoida ilmoituksiasi sekä pääsyn puhelimeen, tekstiviesteihin, yhteystietoihin, mikrofoniin ja lähellä olevat laitteet ‑lupiin."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> saa reagoida ilmoituksiin ja striimata sovelluksiasi yhdistetylle laitteelle."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> saa luvan striimata sisältöä lähellä oleviin laitteisiin."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Tämä palvelu jakaa kuvat, median ja ilmoitukset puhelimeltasi muille laitteille."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Nykyinen oletus"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Älä kysy uudelleen"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Aseta oletukseksi"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pääsyn &lt;b&gt;kuviin, videoihin, musiikkiin, audioon ja muihin tiedostoihin&lt;/b&gt;?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pääsyn tällä laitteella oleviin musiikki- ja audiotiedostoihin?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pääsyn laitteella oleviin kuviin ja mediaan?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pääsyn useampiin kuviin?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; nauhoittaa audiota?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Sovellus voi tallentaa audiota vain silloin, kun käytät sitä"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Saako &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tallentaa audiota?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Näytä viesti, kun sovellukset käyttävät kopioimaasi tekstiä, kuvia tai muuta sisältöä"</string>
<string name="show_password_title" msgid="2877269286984684659">"Näytä salasanat"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Näytä kirjaimet hetkellisesti, kun kirjoitat"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Sovellus on ilmoittanut, että se saattaa jakaa <xliff:g id="PERMISSION_NAME">%s</xliff:g>dataa kolmansille osapuolille"</string>
</resources>
diff --git a/PermissionController/res/values-fr-rCA/strings.xml b/PermissionController/res/values-fr-rCA/strings.xml
index 76e412a1f..e70e9854a 100644
--- a/PermissionController/res/values-fr-rCA/strings.xml
+++ b/PermissionController/res/values-fr-rCA/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Garder « Pendant l\'utilisation de l\'appli »"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Garder « Uniquement cette fois »"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"En savoir plus"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Autoriser l\'accès à toutes les photos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Sélectionner des photos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Sélectionner plus de photos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Ne pas sélectionner plus de photos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ne pas autoriser quand même"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Fermer"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> sur <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 jour}one{# jour}many{# jours}other{# jours}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 heure}one{# heure}many{# heures}other{# heures}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}one{# min}many{# mins}other{# mins}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sec}one{# sec}many{# secs}other{# secs}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# jour}one{# jour}many{# de jours}other{# jours}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# heure}one{# heure}many{# d\'heures}other{# heures}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}one{# min}many{# de minutes}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}one{# s}many{# de secondes}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Toute autorisation"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"À tout moment"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Les 7 derniers jours"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Les 24 dernières heures"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"La dernière heure"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Les 15 dernières minutes"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Dernière minute"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Depuis # jour}one{Depuis # jour}many{Depuis # de jours}other{Depuis # jours}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Depuis # heure}one{Depuis # heure}many{Depuis # d\'heures}other{Depuis # heures}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Depuis # minute}one{Depuis # minute}many{Depuis # de minutes}other{Depuis # minutes}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Aucune autoris. d\'utilisation"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"L\'accès le plus récent en tout temps"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Accès le plus récent au cours des 7 derniers jours"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Utilisation des autorisat. dans la dernière heure"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Utilisation des autor. dans les 15 dern. minutes"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Utilisation des autorisat. dans la dernière minute"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Aucune utilisation au cours des dernières 24 heures"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Inutilisée au cours des 7 derniers jours"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Aucune utilisation depuis # jour}one{Aucune utilisation depuis # jour}many{Aucune utilisation depuis # de jours}other{Aucune utilisation depuis # jours}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Aucune utilisation depuis # heure}one{Aucune utilisation depuis # heure}many{Aucune utilisation depuis # d\'heures}other{Aucune utilisation depuis # heures}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Utilisation : 1 application}one{Utilisation : # application}many{Utilisation : # applications}other{Utilisation : # applications}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Tout afficher dans le tableau de bord"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtré par : <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Autoriser à accéder aux éléments multimédias seulement"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Toujours autoriser"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Autoriser uniquement lorsque l\'appli est en cours d\'utilisation"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Autoriser toutes les photos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Autoriser les photos sélectionnées"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Toujours demander"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Ne pas autoriser"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Position exacte"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Non autorisées"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Afficher d\'autres applis pouvant accéder à tous les fichiers"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 jour}one{# jour}many{# jours}other{# jours}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 heure}one{# heure}many{# heures}other{# heures}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minute}one{# minute}many{# minutes}other{# minutes}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 seconde}one{# seconde}many{# secondes}other{# secondes}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# heure}one{# heure}many{# d\'heures}other{# heures}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minute}one{# minute}many{# de minutes}other{# minutes}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# seconde}one{# seconde}many{# de secondes}other{# secondes}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Rappels d\'autorisation"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 application non utilisée"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> applications non utilisées"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Définir l\'application <xliff:g id="APP_NAME">%1$s</xliff:g> en tant qu\'application de navigation par défaut?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Aucune autorisation nécessaire"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et d\'accéder aux autorisations pour votre téléphone, vos messages texte, vos contacts et votre agenda."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> sera autorisée à interagir avec vos notifications et à accéder à vos autorisations pour le téléphone, les messages texte, les contacts, le microphone et les appareils à proximité."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et de diffuser vos applications à l\'appareil connecté."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> pourra diffuser du contenu en continu sur les appareils à proximité."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ce service partage vos photos, vos contenus multimédias et vos notifications de votre téléphone vers d\'autres appareils."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Application par défaut actuelle"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ne plus me demander"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Définir par défaut"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Autoriser &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à accéder &lt;b&gt;aux photos, aux vidéos, et aux fichiers musicaux, audio et autres&lt;/b&gt; sur cet appareil?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Autoriser &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à accéder aux fichiers musicaux et audio sur cet appareil?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Autoriser &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à accéder aux photos et aux vidéos sur cet appareil?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Autoriser l\'accès à plus de photos à &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Autoriser &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à enregistrer l\'audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"L\'application pourra uniquement enregistrer de l\'audio lorsque vous l\'utilisez"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Autoriser « <xliff:g id="APP_NAME">%1$s</xliff:g> » à enregistrer de l\'audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Afficher un message lorsque les applications accèdent à du texte, à des images ou à d\'autres contenus que vous avez copiés"</string>
<string name="show_password_title" msgid="2877269286984684659">"Afficher les mots de passe"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Afficher les caractères brièvement pendant la saisie"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Cette application a indiqué qu\'elle pourrait partager des données de <xliff:g id="PERMISSION_NAME">%s</xliff:g> avec des tiers."</string>
</resources>
diff --git a/PermissionController/res/values-fr/strings.xml b/PermissionController/res/values-fr/strings.xml
index 2f01f9786..9f0313560 100644
--- a/PermissionController/res/values-fr/strings.xml
+++ b/PermissionController/res/values-fr/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Garder \"Seulement quand l\'appli est en cours d\'utilisation\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Conserver le paramètre \"Uniquement cette fois-ci\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Plus d\'infos"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Autoriser l\'accès à toutes les photos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Sélectionner des photos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Sélectionner d\'autres photos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Ne pas sélectionner d\'autres photos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ne pas autoriser"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Fermer"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> sur <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 jour}one{# jour}many{# jours}other{# jours}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 heure}one{# heure}many{# heures}other{# heures}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}one{# min}many{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}one{# s}many{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# jour}one{# jour}many{# jours}other{# jours}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# heure}one{# heure}many{# heures}other{# heures}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}one{# min}many{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}one{# s}many{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Toute autorisation"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Indifférent"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"7 derniers jours"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Dernières 24 heures"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Dernière heure"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"15 dernières minutes"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Dernière minute"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Dernier jour (#)}one{Dernier jour (#)}many{# derniers jours}other{# derniers jours}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Dernière heure (#)}one{Dernière heure (#)}many{# dernières heures}other{# dernières heures}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Dernière minute (#)}one{Dernière minute (#)}many{# dernières minutes}other{# dernières minutes}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Aucune autorisation utilisée"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Tous les accès les plus récents"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Accès les plus récents (7 derniers jours)"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Autorisations utilisées (dernière heure)"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Autorisations utilisées (15 dernières minutes)"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Autorisations utilisées (dernière minute)"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Aucune utilisation au cours des dernières 24 heures"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Aucune utilisation au cours des 7 derniers jours"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Non utilisée au cours du dernier jour (#)}one{Non utilisée au cours du dernier jour (#)}many{Non utilisée au cours des # derniers jours}other{Non utilisée au cours des # derniers jours}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Non utilisée au cours de la dernière heure (#)}one{Non utilisée au cours de la dernière heure (#)}many{Non utilisée au cours des # dernières heures}other{Non utilisée au cours des # dernières heures}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Utilisation par 1 appli}one{Utilisation par # appli}many{Utilisation par # applis}other{Utilisation par # applis}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Tout afficher dans le tableau de bord"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Données filtrées par : <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Autoriser l\'accès aux fichiers multimédias uniquement"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Toujours autoriser"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Autoriser seulement si l\'appli est en cours d\'utilisation"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Autoriser toutes les photos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Autoriser les photos sélectionnées"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Toujours demander"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Ne pas autoriser"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Position exacte"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Non autorisées"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Voir plus d\'applis pouvant accéder à tous les fichiers"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 jour}one{# jour}many{# jours}other{# jours}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 heure}one{# heure}many{# heures}other{# heures}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minute}one{# minute}many{# minutes}other{# minutes}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 seconde}one{# seconde}many{# secondes}other{# secondes}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# heure}one{# heure}many{# heures}other{# heures}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minute}one{# minute}many{# minutes}other{# minutes}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# seconde}one{# seconde}many{# secondes}other{# secondes}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Rappels relatifs aux autorisations"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 application inutilisée"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> applications inutilisées"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Définir <xliff:g id="APP_NAME">%1$s</xliff:g> comme appli de navigation par défaut ?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Aucune autorisation nécessaire"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et d\'accéder au téléphone, aux SMS, aux contacts et à l\'agenda."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et d\'accéder aux autorisations du téléphone, des SMS, des contacts, du micro et des appareils à proximité."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> aura l\'autorisation d\'interagir avec vos notifications et de lire vos applis en streaming sur l\'appareil connecté."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> aura l\'autorisation de diffuser des contenus en streaming sur les appareils à proximité."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ce service partage vos photos, contenus multimédias et notifications avec d\'autres appareils depuis votre téléphone."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Appli par défaut actuelle"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ne plus me demander"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Définir par défaut"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Autoriser &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à accéder aux &lt;b&gt;photos, vidéos, fichiers musicaux/audio, etc.&lt;/b&gt; sur l\'appareil ?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Autoriser &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à accéder à la musique et à l\'audio sur cet appareil ?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Autoriser &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à accéder aux photos et vidéos sur cet appareil ?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Autoriser &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à accéder à plus de photos ?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Autoriser &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; à enregistrer de l\'audio ?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Cette application ne pourra réaliser des enregistrements audio que lorsque vous l\'utiliserez"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Permettre à &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; de réaliser des enregistrements audio ?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Afficher un message lorsque les applis accèdent à du texte, à des images ou à d\'autres contenus que vous avez copiés"</string>
<string name="show_password_title" msgid="2877269286984684659">"Afficher les mots de passe"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Afficher brièvement les caractères pendant la saisie"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Cette appli a indiqué qu\'elle peut partager des données de <xliff:g id="PERMISSION_NAME">%s</xliff:g> avec des tiers"</string>
</resources>
diff --git a/PermissionController/res/values-gl/strings.xml b/PermissionController/res/values-gl/strings.xml
index 5692c2710..a20e6dbb3 100644
--- a/PermissionController/res/values-gl/strings.xml
+++ b/PermissionController/res/values-gl/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Manter mentres se estea utilizando a aplicación"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Manter Só esta vez"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Máis datos"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Permitir acceso a todas as fotos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Seleccionar fotos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Seleccionar máis fotos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Non seleccionar máis fotos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Non permitir aínda así"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Pechar"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> de <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 día}other{# días}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hora}other{# horas}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# día}other{# días}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hora}other{# horas}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Calquera permiso"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"En calquera momento"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Últimos 7 días"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Últimas 24 horas"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Última hora"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Últimos 15 minutos"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Último minuto"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Últimos día}other{Últimos # días}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Última hora}other{Últimas # horas}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Último minuto}other{Últimos # minutos}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Non se utilizaron os permisos"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Acceso máis recente en todo o tempo"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Acceso máis recente nos últimos 7 días"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Uso dos permisos durante a última hora"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Uso dos permisos durante os últimos 15 minutos"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Uso dos permisos durante o último minuto"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Non se utilizou nas últimas 24 horas"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Non se utilizou nos últimos 7 días"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Non se utilizou no último día}other{Non se utilizou nos últimos # días}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Non se utilizou na última hora}other{Non se utilizou nas últimas # horas}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Permiso usado por 1 aplicación}other{Permiso usado por # aplicacións}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Ver todo no panel de control"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Vista filtrada por: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Permitir acceso só a ficheiros multimedia"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Permitir sempre"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Permitir só mentres se use a aplicación"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Permitir todas as fotos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Permitir fotos seleccionadas"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Preguntar sempre"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Non permitir"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Localización precisa"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Permiso non concedido"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Ver máis aplicacións con acceso a todos os ficheiros"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 día}other{# días}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hora}other{# horas}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuto}other{# minutos}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 segundo}other{# segundos}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hora}other{# horas}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuto}other{# minutos}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# segundo}other{# segundos}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Recordatorios de permisos"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 aplicación que non se usa"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> aplicacións que non se usan"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Queres definir <xliff:g id="APP_NAME">%1$s</xliff:g> como a túa aplicación predeterminada de navegación?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Non se necesita ningún permiso"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> poderá interactuar coas túas notificacións e acceder aos permisos do teu teléfono, das mensaxes, dos contactos e do calendario."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> poderá interactuar coas túas notificacións e acceder aos permisos do teléfono, das SMS, dos contactos, do micrófono e dos dispositivos próximos."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Permitirase que <xliff:g id="APP_NAME">%1$s</xliff:g> interactúe coas túas notificacións e emita as túas aplicacións ao dispositivo conectado."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> poderá transmitir contido a dispositivos próximos."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Este servizo comparte con outros dispositivos as fotos, o contido multimedia e as notificacións desde o teu teléfono."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"App predeterminada actual"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Non preguntar de novo"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"App predeterminada"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Queres permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda ás &lt;b&gt;fotos, vídeos, música, audio e outros ficheiros&lt;/b&gt; do dispositivo?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Queres permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda á música e aos ficheiros de audio deste dispositivo?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Queres permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda ás fotos e aos vídeos deste dispositivo?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Queres permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acceda a máis fotos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Queres permitir que a aplicación &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grave audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Esta aplicación só poderá gravar audio cando a esteas utilizando"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Queres permitir que a aplicación &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grave audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Mostra unha mensaxe cando as aplicacións acceden ao texto, ás imaxes ou ao contido que copiaches"</string>
<string name="show_password_title" msgid="2877269286984684659">"Mostrar contrasinais"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Mostra os caracteres brevemente mentres escribes"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"A aplicación indicou que é posible que comparta con terceiros os seguintes datos: <xliff:g id="PERMISSION_NAME">%s</xliff:g>"</string>
</resources>
diff --git a/PermissionController/res/values-gu/strings.xml b/PermissionController/res/values-gu/strings.xml
index 76fd4affc..8053f4df1 100644
--- a/PermissionController/res/values-gu/strings.xml
+++ b/PermissionController/res/values-gu/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“ઍપ ઉપયોગમાં હોય ત્યારે” આ રાખો"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“ફક્ત આ વખતે” રાખો"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"વધુ માહિતી"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"બધા ફોટાના ઍક્સેસની મંજૂરી આપો"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ફોટા પસંદ કરો"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"વધુ ફોટા પસંદ કરો"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"વધુ ફોટા પસંદ કરશો નહીં"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"કોઈપણ રીતે મંજૂરી આપશો નહીં"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"છોડી દો"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> માંથી <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 દિવસ}one{# દિવસ}other{# દિવસ}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 કલાક}one{# કલાક}other{# કલાક}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 મિનિટ}one{# મિનિટ}other{# મિનિટ}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 સેકન્ડ}one{# સેકન્ડ}other{# સેકન્ડ}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# દિવસ}one{# દિવસ}other{# દિવસ}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# કલાક}one{# કલાક}other{# કલાક}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# મિનિટ}one{# મિનિટ}other{# મિનિટ}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# સેકન્ડ}one{# સેકન્ડ}other{# સેકન્ડ}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"કોઈપણ પરવાનગી"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ગમે ત્યારે"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"છેલ્લા 7 દિવસ"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"છેલ્લા 24 કલાક"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"છેલ્લો 1 કલાક"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"છેલ્લી 15 મિનિટ"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"છેલ્લી 1 મિનિટ"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{છેલ્લો # દિવસ}one{છેલ્લો # દિવસ}other{છેલ્લા # દિવસ}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{છેલ્લો # કલાક}one{છેલ્લો # કલાક}other{છેલ્લા # કલાક}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{છેલ્લી # મિનિટ}one{છેલ્લી # મિનિટ}other{છેલ્લી # મિનિટ}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"પરવાનગીનો ઉપયોગ થયો નથી"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"કોઈ પણ સમયે સૌથી તાજેતરનો ઍક્સેસ"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"છેલ્લા 7 દિવસમાં સૌથી તાજેતરનો ઍક્સેસ"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"છેલ્લા 1 કલાકમાં પરવાનગીનો વપરાશ"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"છેલ્લી 15 મિનિટમાં પરવાનગીનો ઉપયોગ"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"છેલ્લી 1 મિનિટમાં પરવાનગીનો ઉપયોગ"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"છેલ્લા 24 કલાકમાં કોઈ ઉપયોગ કરવામાં આવ્યો નથી"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"છેલ્લા 7 દિવસમાં કોઈ ઉપયોગ કરવામાં આવ્યો નથી"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{છેલ્લા # દિવસમાં કોઈ ઉપયોગ કરવામાં આવ્યો નથી}one{છેલ્લા # દિવસમાં કોઈ ઉપયોગ કરવામાં આવ્યો નથી}other{છેલ્લા # દિવસમાં કોઈ ઉપયોગ કરવામાં આવ્યો નથી}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{છેલ્લા # કલાકમાં કોઈ ઉપયોગ કરવામાં આવ્યો નથી}one{છેલ્લા # કલાકમાં કોઈ ઉપયોગ કરવામાં આવ્યો નથી}other{છેલ્લા # કલાકમાં કોઈ ઉપયોગ કરવામાં આવ્યો નથી}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 ઍપ દ્વારા ઉપયોગ કરવામાં આવ્યો}one{# ઍપ દ્વારા ઉપયોગ કરવામાં આવ્યો}other{# ઍપ દ્વારા ઉપયોગ કરવામાં આવ્યો}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"બધી વિગતો ડૅશબોર્ડમાં જુઓ"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"આના અનુસાર ફિલ્ટર કર્યું: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"માત્ર મીડિયાના ઍક્સેસની મંજૂરી આપો"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"હંમેશાં મંજૂરી આપો"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"ઍપનો ઉપયોગ કરતી વખતે જ મંજૂરી આપો"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"બધા ફોટાના ઍક્સેસની મંજૂરી આપો"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"પસંદ કરેલા ફોટાના ઍક્સેસની મંજૂરી આપો"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"દર વખતે પૂછો"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"મંજૂરી આપશો નહીં"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ચોક્કસ સ્થાન"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"મંજૂરી નથી"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"બધી ફાઇલોને ઍક્સેસ કરી શકે તેવી વધુ ઍપ જુઓ"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 દિવસ}one{# દિવસ}other{# દિવસ}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 કલાક}one{# કલાક}other{# કલાક}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 મિનિટ}one{# મિનિટ}other{# મિનિટ}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 સેકન્ડ}one{# સેકન્ડ}other{# સેકન્ડ}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# કલાક}one{# કલાક}other{# કલાક}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# મિનિટ}one{# મિનિટ}other{# મિનિટ}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# સેકન્ડ}one{# સેકન્ડ}other{# સેકન્ડ}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"મંજૂરીના રિમાઇન્ડર"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ન વપરાયેલી ઍપ"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ન વપરાયેલી ઍપ"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"શું <xliff:g id="APP_NAME">%1$s</xliff:g>ને તમારી ડિફૉલ્ટ નૅવિગેશન ઍપ તરીકે સેટ કરીએ?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"કોઈ પરવાનગી જરૂરી નથી"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g>ને તમારા નોટિફિકેશન સાથે ક્રિયાપ્રતિક્રિયા કરવાની અને તમારા ફોન, SMS, સંપર્કો તેમજ Calendarની પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી મળશે."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g>ને તમારા નોટિફિકેશન સાથે ક્રિયાપ્રતિક્રિયા કરવાની અને તમારો ફોન, SMS, સંપર્કો, માઇક્રોફોન તથા નજીકના ડિવાઇસની પરવાનગીઓ ઍક્સેસ કરવાની મંજૂરી આપવામાં આવશે."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g>ને તમારા નોટિફિકેશન સાથે ક્રિયાપ્રતિક્રિયા કરવાની અને કનેક્ટ કરેલા ડિવાઇસ સાથે તમારી ઍપનું કન્ટેન્ટ સ્ટ્રીમ કરવાની મંજૂરી આપવામાં આવશે."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g>ને નજીકના ડિવાઇસ પર કન્ટેન્ટ સ્ટ્રીમ કરવાની મંજૂરી આપવામાં આવશે."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"આ સેવા તમારા ફોનમાંથી તમારા ફોટા, મીડિયા અને નોટિફિકેશન અન્ય ડિવાઇસમાં શેર કરે છે."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"હાલની ડિફૉલ્ટ"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"ફરીથી પૂછશો નહીં"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ડિફૉલ્ટ તરીકે સેટ"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને આ ડિવાઇસ પર &lt;b&gt;ફોટા, વીડિયો, મ્યુઝિક, ઑડિયો અને અન્ય ફાઇલો&lt;b&gt;ના ઍક્સેસની મંજૂરી આપીએ?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"શું &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને આ ડિવાઇસ પરની મ્યુઝિક અને ઑડિયો ફાઇલો ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"શું &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને આ ડિવાઇસ પરના ફોટા અને વીડિયો ઍક્સેસ કરવાની મંજૂરી આપીએ?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને વધુ ફોટાનો ઍક્સેસ આપીએ?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને ઑડિયો રેકૉર્ડ કરવાની મંજૂરી આપીએ?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"જ્યારે તમે ઍપનો ઉપયોગ કરી રહ્યા હશો, માત્ર ત્યારે જ ઍપ ઑડિયો રેકોર્ડ કરી શકશે"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ને ઑડિયો રેકોર્ડ કરવાની મંજૂરી આપીએ?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"જ્યારે ઍપ તમે કૉપિ કરેલી ટેક્સ્ટ, છબીઓ કે અન્ય કન્ટેન્ટનો ઍક્સેસ કરે, ત્યારે મેસેજ બતાવો"</string>
<string name="show_password_title" msgid="2877269286984684659">"પાસવર્ડ બતાવો"</string>
<string name="show_password_summary" msgid="1110166488865981610">"તમે ટાઇપ કરો ત્યારે થોડા સમય માટે અક્ષરો બતાવો"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"આ ઍપ દ્વારા જણાવવામાં આવ્યું છે કે તે ત્રીજા પક્ષો સાથે <xliff:g id="PERMISSION_NAME">%s</xliff:g>નો ડેટા શેર કરી શકે છે"</string>
</resources>
diff --git a/PermissionController/res/values-hi/strings.xml b/PermissionController/res/values-hi/strings.xml
index 40b8e6cb4..210b8f356 100644
--- a/PermissionController/res/values-hi/strings.xml
+++ b/PermissionController/res/values-hi/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"\"ऐप्लिकेशन इस्तेमाल करते समय\" अनुमति बनाए रखें"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“सिर्फ़ इस बार अनुमति दें” को बनाए रखें"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"ज़्यादा जानकारी"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"सभी फ़ोटो को ऐक्सेस करने की अनुमति दें"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"फ़ोटो चुनें"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"और फ़ोटो चुनें"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"शेयर करने के लिए, ज़्यादा फ़ोटो चुनने की अनुमति न दें"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"फिर भी अनुमति न दें"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"खारिज करें"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> में से <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 दिन}one{# दिन}other{# दिन}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 घंटा}one{# घंटा}other{# घंटे}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 मिनट}one{# मिनट}other{# मिनट}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 सेकंड}one{# सेकंड}other{# सेकंड}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# दिन}one{# दिन}other{# दिन}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# घंटा}one{# घंटा}other{# घंटे}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# मिनट}one{# मिनट}other{# मिनट}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# सेकंड}one{# सेकंड}other{# सेकंड}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"कोई भी अनुमति"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"किसी भी समय"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"पिछले सात दिनों में"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"पिछले 24 घंटों में"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"पिछले एक घंटे में"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"पिछले 15 मिनट में"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"आखिरी एक मिनट"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{पिछला # दिन}one{पिछला # दिन}other{पिछले # दिन}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{पिछला # घंटा}one{पिछला # घंटा}other{पिछले # घंटे}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{पिछला # मिनट}one{पिछला # मिनट}other{पिछले # मिनट}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"अनुमति का इस्तेमाल नहीं हुआ"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"किसी भी समय सबसे हाल ही में ऐक्सेस किए गए ऐप्लिकेशन"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"पिछले सात दिनों में सबसे हाल के ऐक्सेस"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"पिछले एक घंटे में अनुमति का इस्तेमाल"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"पिछले 15 मिनटों में अनुमति का इस्तेमाल"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"पिछले एक मिनट में अनुमति का इस्तेमाल"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"पिछले 24 घंटों में इस्तेमाल नहीं किया गया"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"पिछले सात दिनों में इस्तेमाल नहीं किया गया"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{पिछले # दिन में इस्तेमाल नहीं की गई}one{पिछले # दिन में इस्तेमाल नहीं की गई}other{पिछले # दिनों में इस्तेमाल नहीं की गई}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{पिछले # घंटे में इस्तेमाल नहीं की गई}one{पिछले # घंटे में इस्तेमाल नहीं की गई}other{पिछले # घंटों में इस्तेमाल नहीं की गई}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 ऐप्लिकेशन ने इस्तेमाल किया}one{# ऐप्लिकेशन ने इस्तेमाल किया}other{# ऐप्लिकेशन ने इस्तेमाल किया}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"डैशबोर्ड में सभी को देखें"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"इससे फ़िल्टर किया गया: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"सिर्फ़ मीडिया फ़ाइलें ऐक्सेस करने की अनुमति दें"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"हमेशा के लिए अनुमति दें"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"सिर्फ़ ऐप्लिकेशन इस्तेमाल करते समय अनुमति दें"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"सभी फ़ोटो को ऐक्सेस करने की अनुमति दें"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"चुनी गईं फ़ोटो को ऐक्सेस करने की अनुमति दें"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"हर बार पूछें"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"अनुमति न दें"</string>
<string name="precise_image_description" msgid="6349638632303619872">"सटीक जगह"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"इन ऐप्लिकेशन के पास अनुमति नहींं है"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"ऐसे और ऐप्लिकेशन देखें जो सभी फ़ाइलों को ऐक्सेस कर सकते हैं"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 दिन}one{# दिन}other{# दिन}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 घंटा}one{# घंटा}other{# घंटे}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 मिनट}one{# मिनट}other{# मिनट}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 सेकंड}one{# सेकंड}other{# सेकंड}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# घंटा}one{# घंटा}other{# घंटे}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# मिनट}one{# मिनट}other{# मिनट}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# सेकंड}one{# सेकंड}other{# सेकंड}}"</string>
<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>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> को डिफ़ॉल्ट नेविगेशन ऐप्लिकेशन के तौर पर सेट करें?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"अनुमति की ज़रूरत नहीं है"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपकी सूचनाओं को पढ़ सकेगा और उन पर कार्रवाई कर पाएगा. साथ ही, यह आपके फ़ोन, एसएमएस, संपर्कों, और कैलेंडर की अनुमतियों को भी ऐक्सेस कर पाएगा."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> को अनुमति होगी कि यह आपकी सूचनाओं पर कार्रवाई कर सके. साथ ही, आपके फ़ोन, मैसेज, संपर्कों, माइक्रोफ़ोन, और आस-पास मौजूद डिवाइसों को ऐक्सेस कर सके."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> आपकी सूचनाओं पर कार्रवाई कर पाएगा. साथ ही, यह आपके कनेक्ट किए गए डिवाइस पर, आपके ऐप्लिकेशन का कॉन्टेंट चला पाएगा."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> को आस-पास मौजूद डिवाइसों पर कॉन्टेंट स्ट्रीम करने की अनुमति मिल जाएगी."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"यह सेवा आपके फ़ोन की फ़ोटो, मीडिया, और सूचनाओं को दूसरे डिवाइसों पर शेयर करती है."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"मौजूदा डिफ़ॉल्ट"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"फिर से न पूछें"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"डिफ़ॉल्ट के रूप में सेट करें"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; को डिवाइस में मौजूद &lt;b&gt;फ़ोटो, वीडियो, संगीत, ऑडियो, और अन्य फ़ाइल&lt;/b&gt; का ऐक्सेस देना है?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; को इस डिवाइस में मौजूद संगीत और ऑडियो ऐक्सेस करने की अनुमति देनी है?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; को इस डिवाइस में मौजूद फ़ोटो और वीडियो ऐक्सेस करने की अनुमति देनी है?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; को और फ़ोटो ऐक्सेस करने की अनुमति देनी है?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; को ऑडियो रिकॉर्ड करने की अनुमति देनी है?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"ऐप्लिकेशन सिर्फ़ तब ही ऑडियो रिकॉर्ड कर पाएगा, जब आप ऐप्लिकेशन इस्तेमाल कर रहे हों"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"क्या आप &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; को ऑडियो रिकॉर्ड करने की अनुमति देना चाहते हैं?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"जब कोई ऐप्लिकेशन आपके कॉपी किए गए टेक्स्ट, इमेज या अन्य कॉन्टेंट को ऐक्सेस करे, तो मैसेज से इसकी सूचना पाएं"</string>
<string name="show_password_title" msgid="2877269286984684659">"पासवर्ड दिखाएं"</string>
<string name="show_password_summary" msgid="1110166488865981610">"टाइप करते समय वर्ण दिखाएं"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"इस ऐप्लिकेशन ने बताया है कि यह, <xliff:g id="PERMISSION_NAME">%s</xliff:g> का डेटा तीसरे पक्ष के साथ शेयर कर सकता है"</string>
</resources>
diff --git a/PermissionController/res/values-hr/strings.xml b/PermissionController/res/values-hr/strings.xml
index 2b2b1dea0..7f87297eb 100644
--- a/PermissionController/res/values-hr/strings.xml
+++ b/PermissionController/res/values-hr/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Zadržite \"Dok se aplikacija koristi\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Zadrži \"Samo ovaj put\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Više podataka"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Dopusti pristup svim fotografijama"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Odaberi fotografije"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Odaberi više fotografija"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Ne biraj više slika"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ipak nemoj dopustiti"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Odbaci"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> od <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dan}one{# dan}few{# dana}other{# dana}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 h}one{# h}few{# h}other{# h}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}one{# min}few{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}one{# s}few{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dan}one{# dan}few{# dana}other{# dana}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# sat}one{# sat}few{# sata}other{# sati}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}one{# min}few{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}one{# s}few{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Bilo koje dopuštenje"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Bilo kad"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Posljednjih tjedan dana"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Posljednja 24 sata"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Posljednjih sat vremena"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Posljednjih 15 minuta"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Posljednja minuta"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{posljednji # dan}one{Posljednji # dan}few{Posljednja # dana}other{Posljednjih # dana}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Posljednjih sat vremena}one{Posljednji # sat}few{Posljednja # sata}other{Posljednjih # sati}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Posljednja # minuta}one{Posljednja # minuta}few{Posljednje # minute}other{Posljednjih # minuta}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Nema upotreba dopuštenja"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Posljednji pristup u bilo koje vrijeme"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Posljednji pristup u prethodnih tjedan dana"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Upotreba dopuštenja u posljednjih sat vremena"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Upotreba dopuštenja u posljednjih 15 minuta"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Upotreba dopuštenja u posljednjoj minuti"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Nije korišteno u posljednja 24 sata"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Nije korišteno u posljednjih sedam dana"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Nije korišteno u posljednji # dan}one{Nije korišteno u posljednji # dan}few{Nije korišteno u posljednja # dana}other{Nije korišteno u posljednjih # dana}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Nije korišteno u posljednji # sat}one{Nije korišteno u posljednji # sat}few{Nije korišteno u posljednja # sata}other{Nije korišteno u posljednjih # sati}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Koristi 1 aplikacija}one{Koristi # aplikacija}few{Koriste # aplikacije}other{Koristi # aplikacija}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Pogledajte sve na nadzornoj ploči"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrirano po: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Omogući pristup samo medijima"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Dopusti cijelo vrijeme"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Dopusti samo dok se aplikacija koristi"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Dopusti sve fotografije"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Dopusti odabrane fotografije"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Pitaj svaki put"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Nemoj dopustiti"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Točna lokacija"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nemaju dopuštenje"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Pogledajte koje još aplikacije imaju pristup svim datotekama"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dan}one{# dan}few{# dana}other{# dana}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 h}one{# h}few{# h}other{# h}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 min}one{# min}few{# min}other{# min}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 s}one{# s}few{# s}other{# s}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# sat}one{# sat}few{# sata}other{# sati}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# min}one{# min}few{# min}other{# min}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# s}one{# s}few{# s}other{# s}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Podsjetnici za dopuštenja"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 nekorištena aplikacija"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Nekorištenih aplikacija: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Želite li postaviti aplikaciju <xliff:g id="APP_NAME">%1$s</xliff:g> kao zadanu aplikaciju za navigaciju?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nije potrebno nijedno dopuštenje"</string>
<string name="role_watch_description" msgid="267003778693177779">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> moći će stupati u interakciju s vašim obavijestima i pristupati dopuštenjima za telefon, SMS-ove, kontakte i kalendar."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> moći će stupati u interakciju s vašim obavijestima i pristupati vašim dopuštenjima za telefon, SMS-ove, kontakte, mikrofon i uređaje u blizini."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> moći će stupati u interakciju s vašim obavijestima i streamati aplikacije na povezanom uređaju."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Aplikacija <xliff:g id="APP_NAME">%1$s</xliff:g> moći će emitirati sadržaj na uređaje u blizini."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Usluga dijeli vaše fotografije, medije i obavijesti s telefona s drugim uređajima."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Trenutačna zadana"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Više me ne pitaj"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Postavi kao zadano"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Dopustiti apl. &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pristup &lt;b&gt;foto/video/audio i drugim datotekama te glazbi&lt;/b&gt; na uređaju?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Želite li dopustiti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pristup glazbi i audiodatotekama na ovom uređaju?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Želite li dopustiti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pristup fotografijama i videozapisima na ovom uređaju?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Želite li aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; omogućiti pristup dodatnim fotografijama?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Želite li dopustiti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; da snima audiozapise?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Aplikacija će moći snimati audiozapise samo dok je upotrebljavate"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Želite li dopustiti aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; da snima audiozapise?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Primite poruku kad aplikacije pristupe tekstu, slikama ili drugom kopiranom sadržaju"</string>
<string name="show_password_title" msgid="2877269286984684659">"Prikaži zaporke"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Nakratko prikaži znakove tijekom unosa"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Aplikacija je navela da može dijeliti <xliff:g id="PERMISSION_NAME">%s</xliff:g> podatke s trećim stranama"</string>
</resources>
diff --git a/PermissionController/res/values-hu/strings.xml b/PermissionController/res/values-hu/strings.xml
index 9b45a8143..102f15181 100644
--- a/PermissionController/res/values-hu/strings.xml
+++ b/PermissionController/res/values-hu/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Továbbra is: „Amíg az alkalmazás használatban van”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Maradjon „Csak most”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Bővebben"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Hozzáférés engedélyezése az összes fotóhoz"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Fotók kijelölése"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"További fotók kijelölése"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Ne jelöljön ki több fotót"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Semmiképpen se engedélyezze"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Elvetés"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>/<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>."</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 nap}other{# nap}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 óra}other{# óra}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 perc}other{# perc}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 mp}other{# mp}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# nap}other{# nap}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# óra}other{# óra}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# perc}other{# perc}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# mp}other{# mp}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Bármely engedély"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Bármikor"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Elmúlt 7 nap"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Elmúlt 24 óra"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Elmúlt 1 óra"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Elmúlt 15 perc"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Elmúlt 1 perc"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Elmúlt # nap}other{Elmúlt # nap}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Elmúlt # óra}other{Elmúlt # óra}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Elmúlt # perc}other{Elmúlt # perc}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Nincs engedélyhasználat"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Legutóbbi hozzáférés bármikor"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Legutóbbi hozzáférés az elmúlt hét nap során"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Engedélyhasználat az elmúlt egy órában"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Engedélyhasználat az elmúlt 15 percben"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Engedélyhasználat az elmúlt egy percben"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Semmi nem használta az elmúlt 24 órában"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Semmi nem használta az elmúlt hét napban"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Nem volt használva az elmúlt # nap során}other{Semmi nem használta az elmúlt # napban}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Nem volt használva az elmúlt # óra során}other{Semmi nem használta az elmúlt # órában}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 alkalmazás használta}other{# alkalmazás használta}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Összes megtekintése az irányítópulton"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Szűrés alapja: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Csak a médiatartalmakhoz való hozzáférés engedélyezése"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Mindig engedélyezett"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Csak az alkalmazás használatakor engedélyezett"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Összes fotó engedélyezése"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Kijelölt fotók engedélyezése"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Mindig kérdezzen rá"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Tiltás"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Pontos hely"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nem engedélyezett"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"További, minden fájlhoz hozzáférő alkalmazások megtekintése"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 nap}other{# nap}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 óra}other{# óra}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 perc}other{# perc}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 másodperc}other{# másodperc}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# óra}other{# óra}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# perc}other{# perc}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# másodperc}other{# másodperc}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Engedélyekre vonatkozó emlékeztetők"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 nem használt alkalmazás"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> nem használt alkalmazás"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Beállítja a(z) <xliff:g id="APP_NAME">%1$s</xliff:g> alkalmazást alapértelmezett navigációs alkalmazásként?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nincs szükség engedélyre"</string>
<string name="role_watch_description" msgid="267003778693177779">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> műveleteket végezhet majd az értesítésekkel, és hozzáférhet a telefonra, az SMS-ekre, a névjegyekre és a naptárra vonatkozó engedélyekhez."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> műveleteket végezhet majd az értesítésekkel, és hozzáférhet majd a Telefon, az SMS, a Névjegyek, a Mikrofon és a Közeli eszközök engedélyekhez."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> műveleteket végezhet majd az értesítésekkel, és streamelheti az alkalmazásait a csatlakoztatott eszközre."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"A(z) <xliff:g id="APP_NAME">%1$s</xliff:g> tartalmat streamelhet majd a közeli eszközökre."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ez a szolgáltatás megosztja a telefonján található fotóit, médiatartalmait és értesítéseit egyéb eszközökkel."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Aktuális alapérték"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ne jelenjen meg többé"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Alapértelmezett"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Hozzáférhet a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; az eszközön tárolt &lt;b&gt;fotókhoz, hang-, videó- és egyéb fájlokhoz&lt;/b&gt;?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Hozzáférhet a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; az eszközön tárolt zenékhez és egyéb hanganyagokhoz?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Hozzáférhet a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; az eszközön tárolt fotókhoz és videókhoz?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Engedélyezi, hogy a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; további fotókhoz férjen hozzá?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Engedélyezi a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; számára, hogy hangfelvételt készíthessen?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Az alkalmazás csak akkor tud majd hangfelvételt készíteni, amikor Ön használja az alkalmazást."</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Engedélyezi a(z) &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; számára, hogy hangfelvételt készíthessen?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Üzenet megjelenítése, amikor alkalmazások férnek hozzá a vágólapra másolt szövegekhez, képekhez vagy más tartalmakhoz"</string>
<string name="show_password_title" msgid="2877269286984684659">"Jelszavak mutatása"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Gépelés közben rövid ideig megjeleníti a karaktereket"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Az alkalmazás jelezte, hogy megoszthat a(z) <xliff:g id="PERMISSION_NAME">%s</xliff:g> jogosultsággal kapcsolatos adatokat harmadik felekkel"</string>
</resources>
diff --git a/PermissionController/res/values-hy/strings.xml b/PermissionController/res/values-hy/strings.xml
index 0c57d6f61..46fc1ea26 100644
--- a/PermissionController/res/values-hy/strings.xml
+++ b/PermissionController/res/values-hy/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Թույլատրել, միայն երբ հավելվածն ակտիվ է"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Պահպանել «Միայն այս անգամ» կարգավորումը"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Մանրամասն"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Հասանելի դարձնել բոլոր լուսանկարները"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Ընտրել լուսանկարներ"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Ընտրել այլ լուսանկարներ"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Չընտրել այլ լուսանկարներ"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Միևնույն է չթույլատրել"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Փակել"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>/<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 օր}one{# օր}other{# օր}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ժամ}one{# ժամ}other{# ժամ}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 րոպե}one{# րոպե}other{# րոպե}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 վրկ}one{# վրկ}other{# վրկ}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# օր}one{# օր}other{# օր}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ժամ}one{# ժամ}other{# ժամ}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# րոպե}one{# րոպե}other{# րոպե}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# վրկ}one{# վրկ}other{# վրկ}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Բոլոր թույլտվությունները"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Ցանկացած ժամանակ"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Վերջին 7 օրում"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Վերջին 24 ժամում"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Վերջին ժամում"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Վերջին 15 րոպեում"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Վերջին 1 րոպեում"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Վերջին # օրում}one{Վերջին # օրում}other{Վերջին # օրում}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Վերջին # ժամում}one{Վերջին # ժամում}other{Վերջին # ժամում}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Վերջին # րոպեում}one{Վերջին # րոպեում}other{Վերջին # րոպեում}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Թույլտվություններ չեն կիրառվել"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Վերջին օգտագործումը ցանկացած ժամանակ"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Վերջին օգտագործումը վերջին 7 օրում"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Թույլտվությունների օգտագործումը վերջին 1 ժամում"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Թույլտվությունների օգտագործումը վերջին 15 րոպեում"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Թույլտվությունների օգտագործումը վերջին 1 րոպեում"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Չի օգտագործվել վերջին 24 ժամվա ընթացքում"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Չի օգտագործվել վերջին 7 օրվա ընթացքում"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Չի օգտագործվել վերջին # օրվա ընթացքում}one{Չի օգտագործվել վերջին # օրվա ընթացքում}other{Չի օգտագործվել վերջին # օրվա ընթացքում}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Չի օգտագործվել վերջին # ժամվա ընթացքում}one{Չի օգտագործվել վերջին # ժամվա ընթացքում}other{Չի օգտագործվել վերջին # ժամվա ընթացքում}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Օգտագործվում է 1 հավելվածի կողմից}one{Օգտագործվում է # հավելվածի կողմից}other{Օգտագործվում է # հավելվածի կողմից}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Դիտել բոլորը կառավարման վահանակում"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Զտիչ՝ <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Հասանելի դարձնել միայն մեդիաֆայլերը"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Միշտ թույլատրել"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Թույլատրել միայն հավելվածի օգտագործման ժամանակ"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Թույլատրել բոլոր լուսանկարները"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Թույլատրել ընտրված լուսանկարները"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Ամեն անգամ հարցնել"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Չթույլատրել"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Ճշգրիտ տեղադրություն"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Արգելված"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Տեսնել այլ հավելվածներ, որոնց հասանելի են բոլոր ֆայլերը"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 օր}one{# օր}other{# օր}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ժամ}one{# ժամ}other{# ժամ}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 րոպե}one{# րոպե}other{# րոպե}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 վայրկյան}one{# վայրկյան}other{# վայրկյան}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ժամ}one{# ժամ}other{# ժամ}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# րոպե}one{# րոպե}other{# րոպե}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# վայրկյան}one{# վայրկյան}other{# վայրկյան}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Հիշեցումներ թույլտվությունների մասին"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 չօգտագործվող հավելված"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> չօգտագործվող հավելված"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Նշե՞լ <xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը որպես նավիգացիայի կանխադրված հավելված"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Թույլտվություններ հարկավոր չեն"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը կկարողանա փոխազդել ձեր ծանուցումների հետ և կստանա «Հեռախոս», «SMS», «Կոնտակտներ» և «Օրացույց» ծառայությունների թույլտվությունները։"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը կկարողանա փոխազդել ձեր ծանուցումների հետ և կստանա «Հեռախոս», «SMS», «Կոնտակտներ», «Խոսափող» և «Մոտակա սարքեր» թույլտվությունները։"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը կկարողանա փոխազդել ձեր ծանուցումների հետ և հեռարձակել ձեր հավելվածները միացված սարքին։"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածը կկարողանա բովանդակություն հեռարձակել մոտակա սարքերին։"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Այս ծառայությունը ձեր հեռախոսից այլ սարքեր է փոխանցում ձեր լուսանկարները, մեդիա բովանդակությունը և ծանուցումները։"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Օգտագործվում է ըստ կանխադրման"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Նորից չհարցնել"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Նշել կանխադրված"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Թույլ տա՞լ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; հավելվածին օգտագործել այս սարքի &lt;b&gt;նկարները, երգերը, տեսանյութերը, աուդիո ֆայլերը և մյուս ֆայլերը&lt;/b&gt;"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Թույլ տա՞լ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; հավելվածին օգտագործել այս սարքի երաժշտությունը և մյուս աուդիո ֆայլերը"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Թույլ տա՞լ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; հավելվածին օգտագործել այս սարքի լուսանկարներն ու տեսանյութերը"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Հասանելի դարձնե՞լ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; հավելվածին այլ լուսանկարներ։"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Թույլատրե՞լ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; հավելվածին ձայնագրել"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Այս հավելվածը կկարողանա ձայնագրություններ անել միայն, երբ այն օգտագործելիս լինեք"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Թույլատրե՞լ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; հավելվածին ձայնագրություններ անել։"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Ցուցադրել հաղորդագրություն, երբ հավելվածներին հասանելի են դառնում ձեր պատճենած տեքստը, պատկերները կամ այլ բովանդակություն"</string>
<string name="show_password_title" msgid="2877269286984684659">"Ցուցադրել գաղտնաբառերը"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Տեքստ մուտքագրելիս կարճ ժամանակով ցուցադրել գրանշանները"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Այս հավելվածը կարող է երրորդ կողմերի հետ կիսվել «<xliff:g id="PERMISSION_NAME">%s</xliff:g>» կատեգորիային առնչվող տվյալներով"</string>
</resources>
diff --git a/PermissionController/res/values-in/strings.xml b/PermissionController/res/values-in/strings.xml
index 11cd4dfe7..681f03129 100644
--- a/PermissionController/res/values-in/strings.xml
+++ b/PermissionController/res/values-in/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Pertahankan \"Saat aplikasi digunakan\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Pertahankan \"Hanya kali ini\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Info lengkap"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Izinkan akses ke semua foto"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Pilih foto"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Pilih foto lainnya"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Jangan pilih foto lagi"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Bagaimanapun jangan izinkan"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Tutup"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> dari <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 hari}other{# hari}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 jam}other{# jam}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 mnt}other{# mnt}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 dtk}other{# dtk}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# hari}other{# hari}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# jam}other{# jam}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# menit}other{# menit}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# detik}other{# detik}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Izin apa pun"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Kapan saja"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"7 hari terakhir"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"24 jam terakhir"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"1 jam terakhir"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"15 menit terakhir"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"1 menit terakhir"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{# hari terakhir}other{# hari terakhir}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{# jam terakhir}other{# jam terakhir}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{# menit terakhir}other{# menit terakhir}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Tidak ada penggunaan izin"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Akses terbaru kapan saja"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Akses terbaru dalam 7 hari terakhir"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Penggunaan izin dalam 1 jam terakhir"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Penggunaan izin dalam 15 menit terakhir"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Penggunaan izin dalam 1 menit terakhir"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Tidak digunakan dalam 24 jam terakhir"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Tidak digunakan dalam 7 hari terakhir"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Tidak digunakan dalam # hari terakhir}other{Tidak digunakan dalam # hari terakhir}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Tidak digunakan dalam # jam terakhir}other{Tidak digunakan dalam # jam terakhir}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Digunakan 1 aplikasi}other{Digunakan # aplikasi}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Lihat semua di Dasbor"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Difilter menurut: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Izinkan akses hanya ke media"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Izinkan sepanjang waktu"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Izinkan hanya saat aplikasi digunakan"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Izinkan semua foto"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Izinkan foto yang dipilih"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Selalu tanya"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Jangan izinkan"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Lokasi akurat"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Tidak diizinkan"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Lihat aplikasi lain yang dapat mengakses semua file"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 hari}other{# hari}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 jam}other{# jam}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 menit}other{# menit}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 detik}other{# detik}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# jam}other{# jam}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# menit}other{# menit}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# detik}other{# detik}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Pengingat izin"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 aplikasi yang tidak digunakan"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> aplikasi tidak digunakan"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Setel <xliff:g id="APP_NAME">%1$s</xliff:g> sebagai aplikasi navigasi default?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Tidak ada izin yang diperlukan"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> akan diizinkan berinteraksi dengan notifikasi dan mengakses izin Telepon, SMS, Kontak, dan Kalender."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> akan diizinkan berinteraksi dengan notifikasi dan mengakses izin Telepon, SMS, Kontak, Mikrofon, dan Perangkat di sekitar."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> akan diizinkan berinteraksi dengan notifikasi dan men-streaming aplikasi ke perangkat terhubung."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> akan diizinkan melakukan streaming konten ke perangkat di sekitar."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Layanan ini membagikan foto, media, dan notifikasi dari ponsel ke perangkat lain."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Default saat ini"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Jangan tanya lagi"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Jadikan default"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Izinkan &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; mengakses &lt;b&gt;foto, video, musik, audio, dan file lainnya&lt;/b&gt; di perangkat ini?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Izinkan &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; mengakses musik dan audio di perangkat ini?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Izinkan &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; mengakses foto dan video di perangkat ini?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Beri &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; akses ke foto lainnya?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Izinkan &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; merekam audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Aplikasi hanya dapat merekam audio saat aplikasi sedang digunakan"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Izinkan &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; merekam audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Menampilkan pesan saat aplikasi mengakses teks, gambar, atau konten lainnya yang telah Anda salin"</string>
<string name="show_password_title" msgid="2877269286984684659">"Tampilkan sandi"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Menampilkan karakter sejenak saat Anda mengetik"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Aplikasi ini menyatakan bahwa aplikasi mungkin membagikan data <xliff:g id="PERMISSION_NAME">%s</xliff:g> ke pihak ketiga"</string>
</resources>
diff --git a/PermissionController/res/values-is/strings.xml b/PermissionController/res/values-is/strings.xml
index ea631a8ce..322de09bf 100644
--- a/PermissionController/res/values-is/strings.xml
+++ b/PermissionController/res/values-is/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Halda „Þegar forritið er í notkun“"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Halda „Aðeins í þetta skipti“"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Upplýsingar"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Leyfa aðgang að öllum myndum"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Velja myndir"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Velja fleiri myndir"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Ekki velja fleiri myndir"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ekki leyfa þrátt fyrir það"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Loka"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> af <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dagur}one{# dagur}other{# dagar}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 klukkustund}one{# klukkustund}other{# klukkustundir}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 mín.}one{# mín.}other{# mín.}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sek.}one{# sek.}other{# sek.}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dagur}one{# dagur}other{# dagar}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# klukkustund}one{# klukkustund}other{# klukkustundir}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# mín.}one{# mín.}other{# mín.}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sek.}one{# sek.}other{# sek.}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Hvaða heimild sem er"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Hvenær sem er"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Síðustu sjö daga"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Síðasti sólarhringur"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Síðustu klukkustund"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Síðustu 15 mínútur"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Síðasta mínúta"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Undanfarinn # dag}one{Undanfarinn # dag}other{Undanfarna # daga}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Síðustu # klst.}one{Síðustu # klst.}other{Síðustu # klst.}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Síðasta # mín.}one{Síðustu # mín.}other{Síðustu # mín.}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Engin heimildanotkun"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Síðasti aðgangur, hvenær sem er"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Síðasti aðgangur síðustu 7 daga"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Heimildanotkun síðustu klukkustund"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Heimildanotkun síðustu 15 mínútur"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Heimildanotkun síðustu mínútu"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Ekki notað síðasta sólarhringinn"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Ekki notað síðastliðna 7 daga"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Ekki notað undanfarinn # dag}one{Ekki notað undanfarinn # dag}other{Ekki notað undanfarna # daga}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Ekki notað síðustu # klst.}one{Ekki notað síðustu # klst.}other{Ekki notað síðustu # klst.}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Notað af 1 forriti}one{Notað af # forriti}other{Notað af # forritum}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Sjá allt á stjórnborði"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Síað eftir: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Leyfa aðeins aðgang að margmiðlunarefni"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Leyfa alltaf"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Leyfa aðeins þegar forritið er í notkun"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Leyfa allar myndir"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Leyfa valdar myndir"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Spyrja alltaf"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Ekki leyfa"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Nákvæm staðsetning"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Ekki heimilað"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Sjá fleiri forrit sem geta opnað allar skrár"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dagur}one{# dagur}other{# dagar}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 klukkustund}one{# klukkustund}other{# klukkustundir}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 mínúta}one{# mínúta}other{# mínútur}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekúnda}one{# sekúnda}other{# sekúndur}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# klukkustund}one{# klukkustund}other{# klukkustundir}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# mínúta}one{# mínúta}other{# mínútur}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekúnda}one{# sekúnda}other{# sekúndur}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Áminningar um heimild"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ónotað forrit"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ónotuð forrit"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Stilla <xliff:g id="APP_NAME">%1$s</xliff:g> sem sjálfgefið leiðsagnarforrit?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Engra heimilda krafist"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> fær aðgang að tilkynningum og heimildum síma, SMS, tengiliða og dagatals."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> fær heimild til að bregðast við tilkynningum og fær aðgang að heimildum fyrir síma, SMS, tengiliði, hljóðnema og nálæg tæki."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> getur brugðist við tilkynningunum þínum og streymt forritunum þínum í tengda tækinu."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> fær heimild til að streyma efni í nálægum tækjum."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Þessi þjónusta deilir myndum, efni og tilkynningum frá þér úr símanum þínum til annarra tækja."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Núverandi sjálfgefið forrit"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ekki spyrja aftur"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Velja sem sjálfgefið"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Veita &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aðgang að &lt;b&gt;myndum, myndskeiðum, tónlist, hljóði og öðrum skrám&lt;/b&gt; í þessu tæki?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Veita &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aðgang að tónlist og hljóði í þessu tæki?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Veita &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aðgang að myndum og myndskeiðum í þessu tæki?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Veita &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aðgang að fleiri myndum?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Leyfa &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; að taka upp hljóð?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Forritið mun aðeins geta tekið upp hljóð þegar þú ert að nota forritið"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Viltu leyfa &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; að taka upp hljóð?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Birta skilaboð þegar forrit fá aðgang að texta, myndum eða öðru efni sem þú hefur afritað"</string>
<string name="show_password_title" msgid="2877269286984684659">"Sýna aðgangsorð"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Birta stafi í stutta stund þegar þú skrifar"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Þetta forrit gaf til kynna að það kunni að deila gögnum af gerðinni „<xliff:g id="PERMISSION_NAME">%s</xliff:g>“ með þriðju aðilum"</string>
</resources>
diff --git a/PermissionController/res/values-it/strings.xml b/PermissionController/res/values-it/strings.xml
index 7199e0045..6888dd800 100644
--- a/PermissionController/res/values-it/strings.xml
+++ b/PermissionController/res/values-it/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Mantieni \"Mentre l\'app è in uso\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Mantieni solo questa volta"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Altre info"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Consenti l\'accesso a tutte le foto"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Seleziona foto"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Seleziona altre foto"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Non selezionare altre foto"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Non consentire comunque"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Ignora"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> di <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 giorno}many{# giorni}other{# giorni}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ora}many{# ore}other{# ore}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}many{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sec}many{# sec}other{# sec}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# giorno}many{# giorni}other{# giorni}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ora}many{# ore}other{# ore}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}many{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sec}many{# sec}other{# sec}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Qualsiasi autorizzazione"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Qualsiasi data"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Ultimi 7 giorni"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Ultime 24 ore"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Ultima ora"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Ultimi 15 minuti"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Ultimo minuto"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Ultimo giorno}many{Ultimi # giorni}other{Ultimi # giorni}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Ultima ora}many{Ultime # ore}other{Ultime # ore}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Ultimo minuto}many{Ultimi # minuti}other{Ultimi # minuti}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Autorizzazioni non usate"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Accesso più recente in qualsiasi momento"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Accesso più recente negli ultimi 7 giorni"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Uso autorizzazioni nell\'ultima ora"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Uso autorizzazioni negli ultimi 15 minuti"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Uso autorizzazioni nell\'ultimo minuto"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Autorizzazione non utilizzata nelle ultime 24 ore"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Autorizzazione non utilizzata negli ultimi 7 giorni"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Autorizzazione non utilizzata nell\'ultimo giorno}many{Autorizzazione non utilizzata negli ultimi # giorni}other{Autorizzazione non utilizzata negli ultimi # giorni}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Autorizzazione non utilizzata nell\'ultima ora}many{Autorizzazione non utilizzata nelle ultime # ore}other{Autorizzazione non utilizzata nelle ultime # ore}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Autorizzazione usata da 1 app}many{Used by # apps}other{Autorizzazione usata da # app}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Mostra tutto nella Dashboard"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrata per: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Consenti l\'accesso solo ai file multimediali"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Consenti sempre"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Consenti solo mentre l\'app è in uso"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Consenti tutte le foto"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Consenti le foto selezionate"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Chiedi ogni volta"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Non consentire"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Posizione esatta"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Non autorizzate"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Visualizza altre app che possono accedere a tutti i file"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 giorno}many{# giorni}other{# giorni}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ora}many{# ore}other{# ore}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuto}many{# minuti}other{# minuti}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 secondo}many{# secondi}other{# secondi}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ora}many{# ore}other{# ore}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuto}many{# minuti}other{# minuti}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# secondo}many{# secondi}other{# secondi}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Promemoria autorizzazione"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 app inutilizzata"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> app inutilizzate"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Impostare <xliff:g id="APP_NAME">%1$s</xliff:g> come app di navigazione predefinita?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nessuna autorizzazione necessaria"</string>
<string name="role_watch_description" msgid="267003778693177779">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> potrà interagire con le tue notifiche e accedere a telefono, SMS, contatti e calendario."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> potrà interagire con le tue notifiche e accedere alle autorizzazioni Telefono, SMS, Contatti, Microfono e Dispositivi nelle vicinanze."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> potrà interagire con le tue notifiche e trasmettere le tue app al dispositivo connesso."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> potrà riprodurre contenuti alle app nelle vicinanze."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Questo servizio condivide le tue foto, i tuoi contenuti multimediali e le tue notifiche dal telefono ad altri dispositivi."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Valore predefinito attuale"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Non chiedermelo più"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Imposta predefinito"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Consentire all\'app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; di accedere a &lt;b&gt;foto, video, musica, audio e altri file&lt;/b&gt; sul dispositivo?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Consentire all\'app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; di accedere a musica e audio sul dispositivo?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Consentire all\'app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; di accedere a foto e video sul dispositivo?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Vuoi consentire all\'app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; di accedere ad altre foto?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Consentire all\'app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; di registrare audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"L\'app potrà registrare audio soltanto quando la usi"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Vuoi consentire all\'app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; di registrare audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Viene mostrato un messaggio quando le app accedono a testo, immagini o altri contenuti che hai copiato"</string>
<string name="show_password_title" msgid="2877269286984684659">"Mostra password"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Mostra brevemente i caratteri durante la digitazione"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Questa app ha dichiarato che potrebbe condividere dati <xliff:g id="PERMISSION_NAME">%s</xliff:g> con terze parti"</string>
</resources>
diff --git a/PermissionController/res/values-iw/strings.xml b/PermissionController/res/values-iw/strings.xml
index 24f03be0a..f78fe6f9b 100644
--- a/PermissionController/res/values-iw/strings.xml
+++ b/PermissionController/res/values-iw/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"אני רוצה להשאיר את האפשרות \"כשהאפליקציה נמצאת בשימוש\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"אני רוצה לשמור על ההגדרה “רק הפעם”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"מידע נוסף"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"אישור גישה לכל התמונות"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"בחירת תמונות"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"בחירת תמונות נוספות"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"לא לבחור תמונות נוספות"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"אין אישור בכל זאת"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"סגירה"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> מתוך <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{יום אחד}one{# ימים}two{יומיים}other{# ימים}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{שעה אחת}one{# שעות}two{שעתיים}other{# שעות}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{דקה אחת}one{# דקות}two{# דקות}other{# דקות}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{שנייה אחת}one{# שניות}two{# שניות}other{# שניות}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{יום אחד}one{# ימים}two{יומיים}other{# ימים}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{שעה}one{# שעות}two{שעתיים}other{# שעות}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{דקה אחת}one{# דקות}two{# דקות}other{# דקות}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{שנייה אחת}one{# שניות}two{# שניות}other{# שניות}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"כל הרשאה שהיא"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"בכל עת"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"7 הימים האחרונים"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"ב-24 השעות החולפות"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"בשעה האחרונה"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"15 הדקות האחרונות"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"הדקה האחרונה"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{ביום האחרון}one{ב-# הימים האחרונים}two{ביומיים האחרונים}other{ב-# הימים האחרונים}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{בשעה האחרונה}one{ב-# השעות האחרונות}two{בשעתיים האחרונות}other{ב-# השעות האחרונות}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{בדקה האחרונה}one{ב-# הדקות האחרונות}two{ב-# הדקות האחרונות}other{ב-# הדקות האחרונות}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"אין שימוש בהרשאות"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"הגישה האחרונה בכל מסגרת זמן"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"הגישה האחרונה ב-7 הימים האחרונים"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"שימוש בהרשאות בשעה האחרונה"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"שימוש בהרשאות ב-15 הדקות האחרונות"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"שימוש בהרשאות בדקה האחרונה"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"לא נעשה שימוש ב-24 השעות האחרונות"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"לא נעשה שימוש ב-7 הימים האחרונים"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{לא נעשה שימוש ביום האחרון}one{לא נעשה שימוש ב-# הימים האחרונים}two{לא נעשה שימוש ביומיים האחרונים}other{לא נעשה שימוש ב-# הימים האחרונים}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{לא נעשה שימוש בשעה האחרונה}one{לא נעשה שימוש ב-# השעות האחרונות}two{לא נעשה שימוש בשעתיים האחרונות}other{לא נעשה שימוש ב-# השעות האחרונות}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{בשימוש על ידי אפליקציה אחת}one{בשימוש על ידי # אפליקציות}two{בשימוש על ידי # אפליקציות}other{בשימוש על ידי # אפליקציות}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"הצגת כל הפרטים במרכז השליטה"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"סינון לפי: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"אישור גישה למדיה בלבד"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"כן, כל הזמן"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"רק כשהאפליקציה בשימוש"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"גישה לכל התמונות"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"גישה לתמונות נבחרות"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"יש לשאול בכל פעם"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"אין אישור"</string>
<string name="precise_image_description" msgid="6349638632303619872">"מיקום מדויק"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"אין הרשאה"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"לצפייה באפליקציות נוספות שיכולות לגשת לכל הקבצים"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{יום אחד}one{# ימים}two{יומיים}other{# ימים}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{שעה אחת}one{# שעות}two{שעתיים}other{# שעות}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{דקה אחת}one{# דקות}two{# דקות}other{# דקות}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{שנייה אחת}one{# שניות}two{# שניות}other{# שניות}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{שעה}one{# שעות}two{שעתיים}other{# שעות}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{דקה אחת}one{# דקות}two{# דקות}other{# דקות}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{שנייה אחת}one{# שניות}two{# שניות}other{# שניות}}"</string>
<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>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"להגדיר את <xliff:g id="APP_NAME">%1$s</xliff:g> כאפליקציית ברירת המחדל לניווט?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"אין צורך בהרשאות"</string>
<string name="role_watch_description" msgid="267003778693177779">"‏האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> תוכל לבצע פעולות בהתראות ותקבל הרשאות גישה לטלפון, ל-SMS לאנשי הקשר וליומן."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"‏האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> תוכל לבצע פעולות בהתראות ותקבל הרשאות גישה לטלפון, ל-SMS לאנשי הקשר, למיקרופון ולמכשירים בקרבת מקום."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"לאפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> יהיו הרשאות לביצוע פעולות בהתראות שלך ולביצוע סטרימינג של האפליקציות למכשיר המחובר."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"האפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> תוכל להעביר תוכן בסטרימינג למכשירים בקרבת מקום."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"התמונות, המדיה וההתראות מהטלפון שלך ישותפו למכשירים אחרים על ידי השירות הזה."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"ברירת המחדל הנוכחית"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"לא לשאול שוב"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"הגדרה כברירת מחדל"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"‏לתת לאפליקציה ‎&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;‎‏ הרשאת גישה ‎&lt;b&gt;‎‏לתמונות, לסרטונים, למוזיקה, לאודיו ולקבצים אחרים‎&lt;/b&gt;‎‏ במכשיר?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"‏לתת לאפליקציה ‎&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;‎‏ הרשאת גישה למוזיקה ולקובצי אודיו במכשיר?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"‏לתת לאפליקציה ‎&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;‎‏ הרשאת גישה לתמונות ולסרטונים במכשיר?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"‏לתת לאפליקציה &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; גישה לתמונות נוספות?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"‏לאשר לאפליקציה &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; להקליט אודיו?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"האפליקציה תוכל להקליט אודיו רק כאשר היא בשימוש"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"‏לתת לאפליקציה &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; הרשאה להקליט אודיו?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"הצגת הודעה בזמן גישה של אפליקציות לטקסט, לתמונות או לכל תוכן אחר שהעתקת"</string>
<string name="show_password_title" msgid="2877269286984684659">"הצגת סיסמאות"</string>
<string name="show_password_summary" msgid="1110166488865981610">"התווים יופיעו לפרקי זמן קצרים בזמן ההקלדה"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"האפליקציה הזו הצהירה שהיא עשויה לשתף נתונים של <xliff:g id="PERMISSION_NAME">%s</xliff:g> עם צדדים שלישיים"</string>
</resources>
diff --git a/PermissionController/res/values-ja/strings.xml b/PermissionController/res/values-ja/strings.xml
index 200e704fd..d5a3ffdbf 100644
--- a/PermissionController/res/values-ja/strings.xml
+++ b/PermissionController/res/values-ja/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"[アプリが使用中の場合] を保持"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"「今回のみ」の設定を維持"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"詳細"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"すべての写真へのアクセスを許可"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"写真を選択"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"他の写真を選択"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"他の写真を選択しない"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"許可しない"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"閉じる"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>/<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 日}other{# 日}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 時間}other{# 時間}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 分}other{# 分}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 秒}other{# 秒}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# 日}other{# 日}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# 時間}other{# 時間}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# 分}other{# 分}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# 秒}other{# 秒}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"すべての権限"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"全期間"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"過去 7 日間"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"過去 24 時間"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"過去 1 時間"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"過去 15 分間"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"過去 1 分間"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{過去 # 日}other{過去 # 日間}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{過去 # 時間}other{過去 # 時間}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{過去 # 分間}other{過去 # 分間}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"権限の使用はなし"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"最近のアクセス状況(常時)"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"最近のアクセス状況(過去 7 日間)"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"権限の使用状況(過去 1 時間)"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"権限の使用状況(過去 15 分間)"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"権限の使用状況(過去 1 分間)"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"過去 24 時間では使用されていません"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"過去 7 日間では使用されていません"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{過去 # 日は使用されていません}other{過去 # 日間は使用されていません}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{過去 # 時間は使用されていません}other{過去 # 時間は使用されていません}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 個のアプリで使用}other{# 個のアプリで使用}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ダッシュボードにすべて表示"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"フィルタ: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"メディアへのアクセスのみを許可"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"常に許可"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"アプリの使用中のみ許可"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"すべての写真を許可"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"一部の写真を許可"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"毎回確認する"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"許可しない"</string>
<string name="precise_image_description" msgid="6349638632303619872">"正確な現在地"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"許可しない"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"すべてのファイルにアクセスできるアプリをもっと見る"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 日}other{# 日}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 時間}other{# 時間}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 分}other{# 分}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 秒}other{# 秒}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# 時間}other{# 時間}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# 分}other{# 分}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# 秒}other{# 秒}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"権限のリマインダー"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"使用されていないアプリ: 1 個"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"使用されていないアプリ: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g> 個"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> をデフォルトのナビゲーション アプリとして設定しますか?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"必要な権限がありません"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> は通知を使用でき、スマートフォン、SMS、連絡先、カレンダーの権限にもアクセスできるようになります。"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> は、通知を操作し、電話、SMS、連絡先、マイク、付近のデバイスの権限を利用することができます。"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> は通知を使用して、接続済みのデバイスにアプリをストリーミングできるようになります。"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> は付近のデバイスにコンテンツをストリーミングできるようになります。"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"このサービスは、スマートフォンから他のデバイスに写真、メディア、通知を共有します。"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"現在のデフォルト"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"次回から表示しない"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"デフォルトに設定"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"このデバイス内の&lt;b&gt;写真、動画、音楽、音声など&lt;/b&gt;へのアクセスを &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; に許可しますか?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"このデバイス内の音楽と音声へのアクセスを &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; に許可しますか?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"このデバイス内の写真と動画へのアクセスを &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; に許可しますか?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; に他の写真へのアクセスを許可しますか?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"音声の録音を「&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;」に許可しますか?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"アプリは、ユーザーがアプリを使用している場合のみ音声を録音できます"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"音声の録音を &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; に許可しますか?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"クリップボードにコピーしたテキストや画像などにアプリがアクセスすると、メッセージで通知する"</string>
<string name="show_password_title" msgid="2877269286984684659">"パスワードの表示"</string>
<string name="show_password_summary" msgid="1110166488865981610">"入力した文字を短い間表示する"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"このアプリは、<xliff:g id="PERMISSION_NAME">%s</xliff:g>データをサードパーティと共有する可能性があります"</string>
</resources>
diff --git a/PermissionController/res/values-ka/strings.xml b/PermissionController/res/values-ka/strings.xml
index d8cce80cb..0552703db 100644
--- a/PermissionController/res/values-ka/strings.xml
+++ b/PermissionController/res/values-ka/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"დარჩეს „აპის გამოყენებისას“"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"დაშვება „მხოლოდ ამ ერთხელ“"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"მეტი ინფორმაცია"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"ყველა ფოტოზე წვდომის დაშვება"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ფოტოების არჩევა"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"მეტი ფოტოს არჩევა"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"არ აირჩიო მეტი ფოტო"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"მაინც არ დაიშვას"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"დახურვა"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> / <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>-დან"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 დღე}other{# დღე}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 საათი}other{# საათი}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 წთ}other{# წთ}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 წმ}other{# წმ}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# დღე}other{# დღე}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# საათი}other{# საათი}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# წთ}other{# წთ}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# წმ}other{# წმ}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ნებისმიერი ნებართვა"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ნებისმიერი დრო"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"ბოლო 7 დღე"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"ბოლო 24 საათი"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"ბოლო 1 საათი"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"ბოლო 15 წუთი"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"ბოლო 1 წუთი"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{ბოლო # დღე}other{ბოლო # დღე}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{ბოლო # საათი}other{ბოლო # საათი}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{ბოლო # წუთი}other{ბოლო # წუთი}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"ამ ნებართვებს აპები არ იყენებს"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"უახლესი წვდომა ნებისმიერი დროისთვის"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"უახლესი წვდომა ბოლო 7 დღეში"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"ნებართვების გამოყენება ბოლო 1 საათში"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"ნებართვების გამოყენება ბოლო 15 წუთში"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"ნებართვების გამოყენება ბოლო 1 წუთში"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"არ გამოყენებულა ბოლო 24 საათის განმავლობაში"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"არ გამოყენებულა ბოლო 7 დღის განმავლობაში"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{არ გამოყენებულა ბოლო # დღეში}other{არ გამოყენებულა ბოლო # დღეში}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{არ გამოყენებულა ბოლო # საათში}other{არ გამოყენებულა ბოლო # საათში}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{იყენებს 1 აპი}other{იყენებს # აპი}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ყველაფრის ნახვა საინფორმაციო დაფაზე"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"გაფილტვრის კრიტერიუმი: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"მხოლოდ მედიაზე წვდომის დაშვება"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"ყოველთვის დაშვება"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"მხოლოდ აპის გამოყენებისას დაშვება"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"ყველა ფოტოს დაშვება"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"არჩეული ფოტოების დაშვება"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"ყოველთვის მკითხეთ"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"არ დაიშვას"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ზუსტი მდებარეობა"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"არ არის დაშვებული"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"სხვა აპების ნახვა, რომლებსაც ყველა ფაილზე წვდომა შეუძლია"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 დღე}other{# დღე}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 საათი}other{# საათი}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 წუთი}other{# წუთი}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 წამი}other{# წამი}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# საათი}other{# საათი}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# წუთი}other{# წუთი}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# წამი}other{# წამი}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"შეხსენებები ნებართვის შესახებ"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 გამოუყენებელი აპი"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> გამოუყენებელი აპი"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"გსურთ <xliff:g id="APP_NAME">%1$s</xliff:g> დააყენოთ, როგორც ნაგულისხმევი ნავიგაციის აპი?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"ნებართვები არ არის საჭირო"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> შეძლებს თქვენს შეტყობინებებთან ინტერაქციას და თქვენი ტელეფონის, SMS-ების, კონტაქტებისა და კალენდრის ნებართვებზე წვდომას."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> შეძლებს თქვენს შეტყობინებებთან ინტერაქციას და თქვენს ტელეფონზე, SMS-ებზე, კონტაქტებზე, მიკროფონსა და ახლომახლო მოწყობილობების ნებართვებზე წვდომას."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> შეძლებს თქვენს შეტყობინებებთან ინტერაქციას და თქვენი აპების სტრიმინგს დაკავშირებულ მოწყობილობაზე."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> შეძლებს კონტენტის ნაკადის გაშვებას ახლომახლო მოწყობილობებისკენ."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"ეს სერვისი თქვენს ფოტოებს, მედიას და შეტყობინებებს თქვენი ტელეფონიდან სხვა მოწყობილობებს გაუზიარებს."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"ამჟამინდელი ნაგულისხმევი"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"აღარ მკითხოთ"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ნაგულისხმ. დაყენება"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"მიანიჭებთ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-ის წვდომას თქვენი მოწყ. &lt;b&gt;ფოტოებზე, ვიდეოებზე, მუსიკაზე, აუდიო და სხვა &lt;/b&gt;?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"მიანიჭებთ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-ს წვდომას თქვენი მოწყობილობის მუსიკასა და აუდიოფაილებზე?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"მიანიჭებთ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-ის წვდომას თქვენი მოწყობილობის ფოტოებსა და ვიდეოებზე?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"მეტ ფოტოზე წვდომის ნებას დართავთ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; აპს?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"გსურთ, მიანიჭოთ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>-ს&lt;/b&gt; აუდიოს ჩაწერის ნებართვა?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"ეს აპი აუდიოს ჩაწერას მხოლოდ მაშინ შეძლებს, როცა მას იყენებთ"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"გსურთ, მიანიჭოთ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>-ს&lt;/b&gt; აუდიოს ჩაწერის ნებართვა?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"როდესაც აპებს თქვენ მიერ კოპირებულ ტექსტზე, სურათებზე ან სხვა კონტენტზე აქვთ წვდომა, გამოჩნდება შეტყობინება"</string>
<string name="show_password_title" msgid="2877269286984684659">"პაროლების ჩვენება"</string>
<string name="show_password_summary" msgid="1110166488865981610">"აკრეფისას სიმბოლოების ხანმოკლედ გამოჩენა"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"ამ აპის თანახმად, მან შესაძლოა გაუზიაროს <xliff:g id="PERMISSION_NAME">%s</xliff:g>-ის მონაცემები მესამე მხარეს"</string>
</resources>
diff --git a/PermissionController/res/values-kk/strings.xml b/PermissionController/res/values-kk/strings.xml
index b01ee9be3..a38f9a385 100644
--- a/PermissionController/res/values-kk/strings.xml
+++ b/PermissionController/res/values-kk/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"\"Қолданба пайдаланылатын кезде\" түймесін басып тұрыңыз"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"\"Тек осы жолы\" күйінде қалдыру"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Толығырақ"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Барлық фотосуретті пайдалануға рұқсат беру"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Фотосурет таңдау"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Көбірек фотосурет таңдау"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Басқа фотосурет таңдамау"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Бәрібір рұқсат бермеу"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Жабу"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>/<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 күн}other{# күн}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 сағат}other{# сағат}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 мин}other{# мин}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 с}other{# с}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# күн}other{# күн}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# сағат}other{# сағат}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# мин}other{# мин}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# с}other{# с}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Кез келген рұқсат"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Кез келген уақытта"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Соңғы 7 күн"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Соңғы 24 сағат"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Соңғы 1 сағат"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Соңғы 15 минут"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Соңғы 1 минут"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Соңғы # күн}other{Соңғы # күн}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Соңғы # сағат}other{Соңғы # сағат}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Соңғы # минут}other{Соңғы # минут}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Рұқсаттар пайдаланылмаған"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Бүкіл уақыт бойғы соңғы пайдаланылуы"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Соңғы 7 күндегі пайдаланылуы"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Рұқсаттың соңғы 1 сағатта пайдаланылуы"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Рұқсаттың соңғы 15 минутта пайдаланылуы"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Рұқсаттың соңғы 1 минутта пайдаланылуы"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Соңғы 24 сағатта пайдаланылмады."</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Соңғы 7 күнде пайдаланылмады."</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Соңғы # күнде пайдаланылмады.}other{Соңғы # күнде пайдаланылмады.}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Соңғы # сағатта пайдаланылмады.}other{Соңғы # сағатта пайдаланылмады.}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 қолданба пайдаланды}other{# қолданба пайдаланды}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Барлығын бақылау тақтасынан көру"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Сүзгі шарты: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Тек медиафайлдарды пайдалануға рұқсат беру"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Біржола рұқсат беру"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Қолданбаны пайдаланғанда ғана рұқсат беру"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Барлық фотосуретке рұқсат беру"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Таңдалған фотосуреттерге рұқсат беру"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Әрдайым сұрау"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Рұқсат бермеу"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Нақты орын"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Рұқсат берілмегендер"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Барлық файлды пайдалана алатын тағы басқа қолданбаларды көру"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 күн}other{# күн}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 сағат}other{# сағат}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 минут}other{# минут}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 секунд}other{# секунд}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# сағат}other{# сағат}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# минут}other{# минут}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# секунд}other{# секунд}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Рұқсат туралы еске салғыштар"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 пайдаланылмайтын қолданба"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> пайдаланылмайтын қолданба бар"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы әдепкі навигация қолданбасы етіп орнатылсын ба?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Ешқандай рұқсат қажет емес."</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы хабарландыруларды, телефонды, SMS, контактілерді және күнтізбе рұқсаттарын пайдалана алады."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасына хабарландыруларды оқуға, телефонды, хабарларды, контактілерді, микрофон мен маңайдағы құрылғыларды пайдалануға рұқсат беріледі."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасы хабарландыруларды пайдаланып, қолданбаларыңызды жалғанған құрылғыға трансляциялай алатын болады."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасына контентті маңайдағы құрылғыларға трансляциялауға рұқсат беріледі."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Бұл қызмет телефоныңыздағы фотосуреттерді, медиафайлдар мен хабарландыруларды басқа құрылғылармен бөліседі."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Ағымдағы әдепкі қолданба"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Қайта сұралмасын"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Әдепкі етіп орнату"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; қолданбасына осы құрылғыдағы &lt;b&gt;фотосурет, бейне, музыка, аудио мен басқа файлдарды&lt;/b&gt; пайдалану рұқсаты берілсін бе?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; қолданбасына осы құрылғыдағы музыка мен аудионы пайдалану рұқсаты берілсін бе?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; қолданбасына осы құрылғыдағы фотосурет пен бейнені пайдалану рұқсаты берілсін бе?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; қолданбасына көбірек фотосурет пайдалануға рұқсат беру керек пе?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; қолданбасына дыбыс жазуға рұқсат берілсін бе?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Қолданба тек жұмыс кезінде ғана аудиомазмұн жаза алады."</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; қолданбасына аудиомазмұн жазуға рұқсат берілсін бе?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Қолданбалар көшірілген мәтінді, суреттерді немесе басқа контентті пайдаланған кезде хабар көрсету"</string>
<string name="show_password_title" msgid="2877269286984684659">"Құпия сөздерді көрсету"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Таңбалар терілген кезде аз уақыт көрсетіледі."</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Бұл қолданба <xliff:g id="PERMISSION_NAME">%s</xliff:g> деректерін үшінші тараптармен бөлісуі мүмкін екенін мәлімдеді."</string>
</resources>
diff --git a/PermissionController/res/values-km/strings.xml b/PermissionController/res/values-km/strings.xml
index 1396915f9..9c30a4f9d 100644
--- a/PermissionController/res/values-km/strings.xml
+++ b/PermissionController/res/values-km/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"រក្សាទុក “ខណៈពេល​កំពុងប្រើ​កម្មវិធី”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"រក្សាទុក “តែពេលនេះ​ប៉ុណ្ណោះ”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"ព័ត៌មាន​បន្ថែម"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"អនុញ្ញាតឱ្យចូលប្រើរូបថតទាំងអស់"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ជ្រើសរើសរូបថត"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"ជ្រើសរើស​រូបថតច្រើន​ទៀត"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"កុំជ្រើសរើសរូបថតបន្ថែមទៀត"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"មិនអីទេ មិនអនុញ្ញាត"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"ច្រានចោល"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> ក្នុងចំណោម <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ថ្ងៃ}other{# ថ្ងៃ}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ម៉ោង}other{# ម៉ោង}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 ន}other{# ន}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 វិ}other{# វិ}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ថ្ងៃ}other{# ថ្ងៃ}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ម៉ោង}other{# ម៉ោង}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# នាទីមុន}other{# ន}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# វិ}other{# វិ}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ការអនុញ្ញាត​ណាមួយ"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ពេល​ណា​ក៏​បាន"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"7 ថ្ងៃ​ចុងក្រោយ"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"24 ម៉ោង​ចុងក្រោយ"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"1 ម៉ោង​ចុងក្រោយ"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"15 នាទី​ចុងក្រោយ"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"1 នាទី​ចុងក្រោយ"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{# ថ្ងៃចុងក្រោយ}other{# ថ្ងៃចុងក្រោយ}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{# ម៉ោង​ចុងក្រោយ}other{# ម៉ោង​ចុងក្រោយ}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{# នាទី​ចុងក្រោយ}other{# នាទី​ចុងក្រោយ}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"គ្មាន​ការប្រើប្រាស់​ការអនុញ្ញាត​ទេ"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"ការចូលប្រើ​ថ្មី​បំផុតគ្រប់ពេល"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"ការចូលប្រើចុងក្រោយបំផុត​ក្នុងរយៈពេល 7 ថ្ងៃចុងក្រោយ"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"ការប្រើប្រាស់​ការអនុញ្ញាត​ក្នុងរយៈពេល 1 ម៉ោងចុងក្រោយ"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"ការប្រើប្រាស់​ការអនុញ្ញាត​ក្នុងរយៈពេល​ 15 នាទីចុងក្រោយ"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"ការប្រើប្រាស់​ការអនុញ្ញាត​ក្នុងរយៈពេល 1 នាទីចុងក្រោយ"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"មិនបានប្រើក្នុងរយៈពេល 24 ម៉ោងចុងក្រោយ"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"មិនបានប្រើក្នុងរយៈពេល 7 ថ្ងៃចុងក្រោយ"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{មិន​បាន​ប្រើ​ក្នុងរយៈពេល # ថ្ងៃ​ចុងក្រោយ}other{មិនបានប្រើក្នុងរយៈពេល # ថ្ងៃចុងក្រោយ}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{មិនបានប្រើក្នុងរយៈពេល # ម៉ោងចុងក្រោយ}other{មិនបានប្រើក្នុងរយៈពេល # ម៉ោងចុងក្រោយ}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{បាន​ប្រើដោយ​កម្មវិធី 1}other{បាន​ប្រើដោយ​កម្មវិធី #}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"មើល​ទាំងអស់នៅក្នុងផ្ទាំង​គ្រប់គ្រង"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"ត្រង​តាម៖ <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"អនុញ្ញាតឱ្យចូលប្រើ​មេឌៀតែប៉ុណ្ណោះ"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"អនុញ្ញាតគ្រប់ពេល"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"អនុញ្ញាត​ពេល​កំពុង​ប្រើប្រាស់​កម្មវិធីតែ​ប៉ុណ្ណោះ"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"អនុញ្ញាតរូបថតទាំងអស់"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"អនុញ្ញាតរូបថតដែលបានជ្រើសរើស"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"សួរគ្រប់ពេល"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"មិនអនុញ្ញាត"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ទីតាំងជាក់លាក់"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"មិន​បានអនុញ្ញាត​"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"មើលកម្មវិធីច្រើនទៀតដែលអាចចូលប្រើឯកសារទាំងអស់បាន"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ថ្ងៃ}other{# ថ្ងៃ}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ម៉ោង}other{# ម៉ោង}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 នាទី}other{# នាទី}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 វិនាទី}other{# វិនាទី}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ម៉ោង}other{# ម៉ោង}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# នាទី}other{# នាទី}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# វិនាទី}other{# វិនាទី}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"ការរំលឹក​អំពី​ការអនុញ្ញាត"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"កម្មវិធី​ដែលមិនប្រើ​ចំនួន 1"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"កម្មវិធី​ដែលមិនប្រើ​ចំនួន <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"កំណត់ <xliff:g id="APP_NAME">%1$s</xliff:g> ជាកម្មវិធីរុករក​លំនាំដើមរបស់អ្នក​ឬ?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"មិនត្រូវការ​ការអនុញ្ញាត​ទេ"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> នឹងត្រូវបានអនុញ្ញាតឱ្យ​ធ្វើអន្តរកម្មជាមួយ​ការជូនដំណឹងរបស់អ្នក និងចូលប្រើទូរសព្ទ, SMS, ទំនាក់ទំនង និងការអនុញ្ញាត​ប្រតិទិនរបស់អ្នក។"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> នឹងត្រូវបានអនុញ្ញាតឱ្យ​ធ្វើអន្តរកម្មជាមួយ​ការជូនដំណឹងរបស់អ្នក និងចូលប្រើការអនុញ្ញាត​របស់ទូរសព្ទ, SMS, ទំនាក់ទំនង, មីក្រូហ្វូន និងឧបករណ៍នៅជិត​របស់អ្នក។"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> នឹងត្រូវបាន​អនុញ្ញាតឱ្យ​ធ្វើអន្តរកម្មជាមួយ​ការជូនដំណឹងរបស់អ្នក និងចាក់កម្មវិធី​របស់អ្នកទៅឧបករណ៍​ដែលបានភ្ជាប់។"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> នឹងត្រូវបានអនុញ្ញាតឱ្យផ្សាយខ្លឹមសារទៅកាន់ឧបករណ៍នៅជិត។"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"សេវាកម្មនេះចែករំលែករូបថត មេឌៀ និងការជូនដំណឹងរបស់អ្នក ពីទូរសព្ទរបស់អ្នកទៅឧបករណ៍ផ្សេងទៀត។"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"លំនាំដើម​បច្ចុប្បន្ន"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"កុំសួរទៀត"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"កំណត់ជាលំនាំដើម"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"អនុញ្ញាតឱ្យ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ចូលប្រើ &lt;b&gt;រូបថត វីដេអូ តន្ត្រី សំឡេង និងឯកសារផ្សេងទៀត&lt;/b&gt;នៅលើឧបករណ៍នេះទេ?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"អនុញ្ញាតឱ្យ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ចូលប្រើប្រាស់តន្ត្រី និងសំឡេងនៅលើឧបករណ៍នេះទេ?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"អនុញ្ញាតឱ្យ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ចូលប្រើប្រាស់រូបថត និងវីដេអូនៅលើឧបករណ៍នេះទេ?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"ផ្ដល់សិទ្ធិឱ្យ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ចូលប្រើរូបថតបន្ថែមទៀតឬ?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"អនុញ្ញាតឱ្យ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ថតសំឡេង?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"កម្មវិធីនេះនឹងអាចថតសំឡេង នៅពេលអ្នកកំពុងប្រើប្រាស់កម្មវិធីតែប៉ុណ្ណោះ"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"អនុញ្ញាតឱ្យ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ថតសំឡេងឬ?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"បង្ហាញសារ នៅពេលកម្មវិធីចូលប្រើអត្ថបទ រូបភាព ឬខ្លឹមសារផ្សេងទៀតដែលអ្នកបានចម្លង"</string>
<string name="show_password_title" msgid="2877269286984684659">"បង្ហាញ​ពាក្យ​សម្ងាត់"</string>
<string name="show_password_summary" msgid="1110166488865981610">"បង្ហាញ​តួអក្សរ​មួយភ្លែត​ខណៈ​ពេល​អ្នក​វាយ​បញ្ចូល"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"កម្មវិធីនេះបានបញ្ជាក់ថា វាអាចចែករំលែកទិន្នន័យ <xliff:g id="PERMISSION_NAME">%s</xliff:g> ជាមួយភាគីទីបី"</string>
</resources>
diff --git a/PermissionController/res/values-kn/strings.xml b/PermissionController/res/values-kn/strings.xml
index 83cc355b3..8f3b6fc9f 100644
--- a/PermissionController/res/values-kn/strings.xml
+++ b/PermissionController/res/values-kn/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“ಆ್ಯಪ್ ಬಳಕೆಯಲ್ಲಿರುವಾಗ” ಹಾಗೆಯೇ ಇರಿಸಿ"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“ಈ ಬಾರಿ ಮಾತ್ರ” ಇರಿಸಿ"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"ಹೆಚ್ಚಿನ ಮಾಹಿತಿ"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"ಎಲ್ಲಾ ಫೋಟೋಗಳಿಗೆ ಆ್ಯಕ್ಸೆಸ್ ಅನ್ನು ಅನುಮತಿಸಿ"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ಫೋಟೋಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"ಇನ್ನಷ್ಟು ಫೋಟೋಗಳನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"ಇನ್ನಷ್ಟು ಫೋಟೋಗಳನ್ನು ಆಯ್ಕೆ ಮಾಡಬೇಡಿ"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"ಯಾವುದೇ ರೀತಿಯಲ್ಲೂ ಅನುಮತಿಸಬೇಡಿ"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"ವಜಾಗೊಳಿಸಿ"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> ರಲ್ಲಿ <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ದಿನ}one{# ದಿನಗಳು}other{# ದಿನಗಳು}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ಗಂಟೆ}one{# ಗಂಟೆಗಳು}other{# ಗಂಟೆಗಳು}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 ನಿಮಿಷ}one{# ನಿಮಿಷಗಳು}other{# ನಿಮಿಷಗಳು}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 ಸೆಕೆಂಡ್}one{# ಸೆಕೆಂಡ್‌ಗಳು}other{# ಸೆಕೆಂಡ್‌ಗಳು}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ದಿನ}one{# ದಿನಗಳು}other{# ದಿನಗಳು}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ಗಂಟೆ}one{# ಗಂಟೆಗಳು}other{# ಗಂಟೆಗಳು}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# ನಿಮಿಷ}one{# ನಿಮಿಷಗಳು}other{# ನಿಮಿಷಗಳು}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# ಸೆಕೆಂಡ್}one{# ಸೆಕೆಂಡ್‌ಗಳು}other{# ಸೆಕೆಂಡ್‌ಗಳು}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ಯಾವುದೇ ಅನುಮತಿ"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ಯಾವುದಾದರೂ ಸಮಯದಲ್ಲಿ"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"ಕಳೆದ 7 ದಿನಗಳು"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"ಕೊನೆಯ 24 ಗಂಟೆಗಳು"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"1 ಗಂಟೆಯ ಹಿಂದೆ"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"ಹಿಂದಿನ 15 ನಿಮಿಷಗಳು"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"ಕಳೆದ 1 ನಿಮಿಷ"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{ಕಳೆದ # ದಿನ}one{ಕಳೆದ # ದಿನಗಳು}other{ಕಳೆದ # ದಿನಗಳು}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{# ಗಂಟೆಯ ಹಿಂದೆ}one{ಕೊನೆಯ # ಗಂಟೆಗಳು}other{ಕೊನೆಯ # ಗಂಟೆಗಳು}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{ಕಳೆದ # ನಿಮಿಷ}one{ಹಿಂದಿನ # ನಿಮಿಷಗಳು}other{ಹಿಂದಿನ # ನಿಮಿಷಗಳು}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"ಅನುಮತಿಯ ಬಳಕೆಗಳು ಇಲ್ಲ"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"ಯಾವುದೇ ಸಮಯದಲ್ಲಿನ ತೀರಾ ಇತ್ತೀಚಿನ ಪ್ರವೇಶ"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"ಕಳೆದ 7 ದಿನಗಳಲ್ಲಿನ ತೀರಾ ಇತ್ತೀಚಿನ ಪ್ರವೇಶ"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"ಕಳೆದ 1 ಗಂಟೆಯಲ್ಲಿನ ಅನುಮತಿಯ ಬಳಕೆ"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"ಕಳೆದ 15 ನಿಮಿಷಗಳಲ್ಲಿನ ಅನುಮತಿಯ ಬಳಕೆ"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"ಕಳೆದ 1 ನಿಮಿಷದಲ್ಲಿನ ಅನುಮತಿಯ ಬಳಕೆ"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"ಕಳೆದ 24 ಗಂಟೆಗಳಲ್ಲಿ ಬಳಸಿಲ್ಲ"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"ಕಳೆದ 7 ದಿನಗಳಲ್ಲಿ ಬಳಸಿಲ್ಲ"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{ಕಳೆದ # ದಿನದಲ್ಲಿ ಬಳಸಿಲ್ಲ}one{ಕಳೆದ # ದಿನಗಳಲ್ಲಿ ಬಳಸಿಲ್ಲ}other{ಕಳೆದ # ದಿನಗಳಲ್ಲಿ ಬಳಸಿಲ್ಲ}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{ಕಳೆದ # ಗಂಟೆಯಲ್ಲಿ ಬಳಸಿಲ್ಲ}one{ಕಳೆದ # ಗಂಟೆಗಳಲ್ಲಿ ಬಳಸಿಲ್ಲ}other{ಕಳೆದ # ಗಂಟೆಗಳಲ್ಲಿ ಬಳಸಿಲ್ಲ}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 ಆ್ಯಪ್ ಬಳಸಿದೆ}one{# ಆ್ಯಪ್‌ಗಳು ಬಳಸಿವೆ}other{# ಆ್ಯಪ್‌ಗಳು ಬಳಸಿವೆ}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ಎಲ್ಲವನ್ನೂ ಡ್ಯಾಶ್‌ಬೋರ್ಡ್‌ನಲ್ಲಿ ವೀಕ್ಷಿಸಿ"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"ಈ ಆಧಾರದ ಮೇಲೆ ಫಿಲ್ಟರ್ ಮಾಡಲಾಗಿದೆ: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"ಮಾಧ್ಯಮಕ್ಕೆ ಮಾತ್ರ ಪ್ರವೇಶಿಸಲು ಅನುಮತಿಸಿ"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"ಎಲ್ಲಾ ಸಮಯದಲ್ಲೂ ಅನುಮತಿಸಿ"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"ಆ್ಯಪ್ ಬಳಸುವಾಗ ಮಾತ್ರ ಅನುಮತಿಸಿ"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"ಎಲ್ಲಾ ಫೋಟೋಗಳನ್ನು ಅನುಮತಿಸಿ"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"ಆಯ್ಕೆಮಾಡಿದ ಫೋಟೋಗಳನ್ನು ಅನುಮತಿಸಿ"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"ಪ್ರತಿ ಬಾರಿ ಕೇಳಿ"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"ಅನುಮತಿಸಬೇಡಿ"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ನಿಖರವಾದ ಸ್ಥಾನ"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"ಅನುಮತಿಸಿಲ್ಲದಿರುವುದು"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"ಎಲ್ಲಾ ಫೈಲ್‌ಗಳನ್ನು ಪ್ರವೇಶಿಸಬಹುದಾದ ಇನ್ನಷ್ಟು ಆ್ಯಪ್‌ಗಳನ್ನು ನೋಡಿ"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ದಿನ}one{# ದಿನಗಳು}other{# ದಿನಗಳು}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ಗಂಟೆ}one{# ಗಂಟೆಗಳು}other{# ಗಂಟೆಗಳು}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 ನಿಮಿಷ}one{# ನಿಮಿಷಗಳು}other{# ನಿಮಿಷಗಳು}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 ಸೆಕೆಂಡ್}one{# ಸೆಕೆಂಡ್‌ಗಳು}other{# ಸೆಕೆಂಡ್‌ಗಳು}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ಗಂಟೆ}one{# ಗಂಟೆಗಳು}other{# ಗಂಟೆಗಳು}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# ನಿಮಿಷ}one{# ನಿಮಿಷಗಳು}other{# ನಿಮಿಷಗಳು}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# ಸೆಕೆಂಡ್}one{# ಸೆಕೆಂಡ್‌ಗಳು}other{# ಸೆಕೆಂಡ್‌ಗಳು}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"ಅನುಮತಿ ಜ್ಞಾಪನೆಗಳು"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ಬಳಕೆಯಾಗದ ಆ್ಯಪ್"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ಬಳಕೆಯಾಗದ ಆ್ಯಪ್‌ಗಳು"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> ಅನ್ನು ಡೀಫಾಲ್ಟ್ ನ್ಯಾವಿಗೇಷನ್ ಆ್ಯಪ್ ಆಗಿ ಸೆಟ್ ಮಾಡಬೇಕೇ?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"ಯಾವುದೇ ಅನುಮತಿಗಳ ಅಗತ್ಯವಿಲ್ಲ"</string>
<string name="role_watch_description" msgid="267003778693177779">"ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು ಮತ್ತು ನಿಮ್ಮ ಫೋನ್, ಎಸ್ಎಂಎಸ್, ಸಂಪರ್ಕಗಳು ಮತ್ತು Calendar ಅನುಮತಿಗಳನ್ನು ಪ್ರವೇಶಿಸಲು <xliff:g id="APP_NAME">%1$s</xliff:g> ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"ನಿಮ್ಮ ಫೋನ್, SMS, ಸಂಪರ್ಕಗಳು, ಮೈಕ್ರೊಫೋನ್ ಮತ್ತು ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳ ಅನುಮತಿಗಳನ್ನು ಆ್ಯಕ್ಸೆಸ್ ಮಾಡಲು ಹಾಗೂ ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು <xliff:g id="APP_NAME">%1$s</xliff:g> ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"ನಿಮ್ಮ ಅಧಿಸೂಚನೆಗಳೊಂದಿಗೆ ಸಂವಹನ ನಡೆಸಲು ಮತ್ತು ನಿಮ್ಮ ಆ್ಯಪ್‌ಗಳನ್ನು ಸಂಪರ್ಕಿತ ಸಾಧನಕ್ಕೆ ಸ್ಟ್ರೀಮ್ ಮಾಡಲು <xliff:g id="APP_NAME">%1$s</xliff:g> ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"ಸಮೀಪದಲ್ಲಿರುವ ಸಾಧನಗಳಲ್ಲಿ ವಿಷಯವನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಲು <xliff:g id="APP_NAME">%1$s</xliff:g> ಗೆ ಅನುಮತಿಸಲಾಗುತ್ತದೆ."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"ಈ ಸೇವೆಯು ನಿಮ್ಮ ಫೋಟೋಗಳು, ಮಾಧ್ಯಮ ಹಾಗೂ ಅಧಿಸೂಚನೆಗಳನ್ನು ನಿಮ್ಮ ಫೋನ್‌ನಿಂದ ಇತರ ಸಾಧನಗಳ ಜೊತೆ ಹಂಚಿಕೊಳ್ಳುತ್ತದೆ."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"ಪ್ರಸ್ತುತ ಡೀಫಾಲ್ಟ್"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"ಮತ್ತೆ ಕೇಳಬೇಡ"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ಡೀಫಾಲ್ಟ್ ಆಗಿ ಹೊಂದಿಸಿ"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"ಈ ಸಾಧನದಲ್ಲಿರುವ &lt;b&gt;ಫೋಟೋಗಳು, ವೀಡಿಯೊಗಳು, ಸಂಗೀತ, ಆಡಿಯೋ, ಇತರ ಫೈಲ್‌ಗಳನ್ನು&lt;/b&gt; ಪ್ರವೇಶಿಸಲು &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ಗೆ ಅನುಮತಿಸಬೇಕೆ?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"ಈ ಸಾಧನದಲ್ಲಿರುವ ಸಂಗೀತ ಮತ್ತು ಆಡಿಯೊವನ್ನು ಪ್ರವೇಶಿಸಲು &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ಗೆ ಅನುಮತಿಸಬೇಕೆ?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"ಈ ಸಾಧನದಲ್ಲಿರುವ ಫೋಟೋಗಳು ಮತ್ತು ವೀಡಿಯೊಗಳನ್ನು ಪ್ರವೇಶಿಸಲು &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ಗೆ ಅನುಮತಿಸಬೇಕೆ?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"ಇನ್ನಷ್ಟು ಫೋಟೋಗಳಿಗೆ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ಆ್ಯಕ್ಸೆಸ್ ನೀಡಬೇಕೇ?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್‌ ಮಾಡಲು &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ಗೆ ಅನುಮತಿಸಬೇಕೇ?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"ನೀವು ಆ್ಯಪ್ ಬಳಸುತ್ತಿರುವಾಗ ಮಾತ್ರ ಆ್ಯಪ್‌ಗೆ ಆಡಿಯೋ ರೆಕಾರ್ಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"ಆಡಿಯೋ ರೆಕಾರ್ಡ್‌ ಮಾಡಲು &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ಆ್ಯಪ್‌ಗೆ ಅನುಮತಿಸಬೇಕೇ?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"ನೀವು ನಕಲಿಸಿರುವ ಪಠ್ಯ, ಚಿತ್ರಗಳು ಅಥವಾ ಇತರ ವಿಷಯವನ್ನು ಆ್ಯಪ್‌ಗಳು ಪ್ರವೇಶಿಸಿದಾಗ ಸಂದೇಶವೊಂದನ್ನು ತೋರಿಸಿ"</string>
<string name="show_password_title" msgid="2877269286984684659">"ಪಾಸ್‌ವರ್ಡ್‌ಗಳನ್ನು ತೋರಿಸಿ"</string>
<string name="show_password_summary" msgid="1110166488865981610">"ನೀವು ಟೈಪ್ ಮಾಡಿದಂತೆ ಅಕ್ಷರಗಳನ್ನು ಸಂಕ್ಷಿಪ್ತವಾಗಿ ಪ್ರದರ್ಶಿಸಿ"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"ಈ ಆ್ಯಪ್ ಥರ್ಡ್ ಪಾರ್ಟಿಗಳೊಂದಿಗೆ <xliff:g id="PERMISSION_NAME">%s</xliff:g> ಡೇಟಾವನ್ನು ಹಂಚಿಕೊಳ್ಳಬಹುದು ಎಂದು ಉಲ್ಲೇಖಿಸಿದೆ"</string>
</resources>
diff --git a/PermissionController/res/values-ko/strings.xml b/PermissionController/res/values-ko/strings.xml
index 35a9e1899..03801785a 100644
--- a/PermissionController/res/values-ko/strings.xml
+++ b/PermissionController/res/values-ko/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"\'앱 사용 중에만 허용\' 유지"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"\'이번만 허용\' 유지"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"추가 정보"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"모든 사진에 대한 액세스 허용"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"사진 선택"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"추가 사진 선택"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"추가 사진 선택 안함"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"무시하고 허용 안함"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"닫기"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>/<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1일}other{#일}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1시간}other{#시간}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1분}other{#분}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1초}other{#초}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{#일}other{#일}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{#시간}other{#시간}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{#분}other{#분}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{#초}other{#초}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"모든 권한"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"전체 기간"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"최근 7일"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"최근 24시간"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"최근 1시간"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"최근 15분"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"최근 1분"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{지난 #일}other{지난 #일}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{최근 #시간}other{최근 #시간}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{최근 #분}other{최근 #분}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"사용 권한 없음"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"가장 최근에 액세스한 앱"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"지난 7일 동안 가장 최근에 액세스한 앱"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"지난 1시간 동안 사용된 권한"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"지난 15분 동안 사용된 권한"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"지난 1분 동안 사용된 권한"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"지난 24시간 이내에 사용하지 않음"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"지난 7일 이내에 사용하지 않음"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{지난 #일 동안 사용하지 않음}other{지난 #일 동안 사용하지 않음}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{지난 #시간 동안 사용하지 않음}other{지난 #시간 동안 사용하지 않음}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{앱 1개에서 사용}other{앱 #개에서 사용}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"대시보드에서 모두 보기"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"필터링 기준: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"미디어 액세스만 허용"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"항상 허용"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"앱 사용 중에만 허용"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"모든 사진 허용"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"선택한 사진 허용"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"항상 확인"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"허용 안함"</string>
<string name="precise_image_description" msgid="6349638632303619872">"정확한 위치"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"허용되지 않음"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"모든 파일에 액세스할 수 있는 앱 더보기"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1일}other{#일}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1시간}other{#시간}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1분}other{#분}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1초}other{#초}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{#시간}other{#시간}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{#분}other{#분}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{#초}other{#초}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"권한 알림"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"사용하지 않는 앱 1개"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"사용하지 않는 앱 <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>개"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> 앱을 기본 내비게이션 앱으로 설정하시겠습니까?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"권한 필요 없음"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 알림과 상호작용하고 전화, SMS, 연락처, 캘린더에 대한 권한을 갖게 됩니다."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 알림과 상호작용하고 내 전화, SMS, 연락처, 마이크, 근처 기기에 대한 권한을 갖게 됩니다."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 알림과 상호작용하고 앱의 콘텐츠를 연결된 기기로 스트리밍할 수 있게 됩니다."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g>에서 콘텐츠를 근처 기기에 스트리밍할 수 있게 됩니다."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"서비스는 휴대전화의 사진, 미디어, 알림을 다른 기기로 공유합니다."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"현재 기본 앱"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"다시 묻지 않음"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"기본 앱으로 설정"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;에서 기기의 &lt;b&gt;사진, 동영상, 음악, 오디오, 기타 파일&lt;/b&gt;에 액세스하도록 허용하시겠습니까?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;에서 기기의 음악과 오디오에 액세스하도록 허용하시겠습니까?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;에서 기기의 사진과 동영상에 액세스하도록 허용하시겠습니까?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;에 추가 사진에 대한 액세스를 허용하시겠습니까?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;에서 오디오를 녹음하도록 허용하시겠습니까?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"앱을 사용하고 있는 동안에만 앱에서 오디오를 녹음할 수 있습니다."</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;에서 오디오를 녹음하도록 허용하시겠습니까?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"앱이 복사된 텍스트, 이미지 또는 기타 콘텐츠에 액세스할 때 메시지 표시"</string>
<string name="show_password_title" msgid="2877269286984684659">"비밀번호 표시"</string>
<string name="show_password_summary" msgid="1110166488865981610">"입력할 때 잠깐 표시"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"앱에서 <xliff:g id="PERMISSION_NAME">%s</xliff:g> 데이터를 서드 파티와 공유할 수 있다고 명시했습니다."</string>
</resources>
diff --git a/PermissionController/res/values-ky/strings.xml b/PermissionController/res/values-ky/strings.xml
index af8fc9017..afdebec5c 100644
--- a/PermissionController/res/values-ky/strings.xml
+++ b/PermissionController/res/values-ky/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"\"Колдонмо колдонулуп жатканда\" режими кала берсин"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"\"Ушул жолу гана\" жөндөөсү калсын"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Дагы маалымат"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Бардык сүрөттөргө кирүүгө уруксат берүү"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Cүрөттөрдү тандаңыз"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Дагы сүрөт тандоо"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Дагы сүрөттөр тандалбасын"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Баары бир тыюу салуу"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Жабуу"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> уруксаттын ичинен <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 күн}other{# күн}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 саат}other{# саат}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 мүн.}other{# мүн.}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 сек.}other{# сек.}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# күн}other{# күн}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# саат}other{# саат}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# мүн.}other{# мүн.}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# сек.}other{# сек.}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Бардык уруксаттар"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Каалаган убакта"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Акыркы 7 күндө"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Акыркы 24 саатта"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Акыркы 1 саатта"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Акыркы 15 мүнөттө"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Акыркы 1 мүнөт"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Акыркы # күндө}other{Акыркы # күндө}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Акыркы # саатта}other{Акыркы # саатта}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Акыркы # мүнөттө}other{Акыркы # мүнөттө}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Уруксаттар колдонулган жок"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Бардык убакыттагы эң акыркы колдонулушу"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Өткөн 7 күндүн ичиндеги эң акыркы колдонулушу"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Акыркы 1 саатта колдонулган уруксаттар"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Акыркы 15 мүнөттө уруксаттардын колдонулушу"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Акыркы 1 мүнөттө уруксаттардын колдонулушу"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Акыркы 24 сааттын ичинде колдонулган жок"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Акыркы 7 күндө колдонулган жок"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Акыркы # күндө колдонулган жок}other{Акыркы # күндө колдонулган жок}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Акыркы # саатта колдонулган жок}other{Акыркы # саатта колдонулган жок}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 колдонмо пайдаланды}other{# колдонмо пайдаланды}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Баарын Куралдар тактасында көрүү"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Чыпка: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Медиа файлдарга гана уруксат берүү"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Бардык учурда уруксат берилет"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Колдонмо колдонулуп жатканда гана уруксат берилет"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Бардык сүрөттөргө уруксат берүү"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Тандалган сүрөттөргө уруксат берүү"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Ар дайым суралсын"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Жок"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Так жайгашкан жери"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Тыюу салынган"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Дагы кайсы колдонмолорго бардык файлдар жеткиликтүү?"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 күн}other{# күн}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 саат}other{# саат}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 мүнөт}other{# мүнөт}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 секунд}other{# секунд}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# саат}other{# саат}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# мүнөт}other{# мүнөт}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# секунд}other{# секунд}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Уруксат жөнүндө эстеткичтер"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 колдонулбаган колдонмо"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> колдонулбаган колдонмо"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> демейки навигация колдонмоңуз катары коюлсунбу?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Уруксаттардын кереги жок"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> билдирмелериңизди көрүп, телефонуңуздун, SMS билдирүүлөрүңүздүн, байланыштырыңыздын жана жылнаамаңыздын уруксаттарын пайдалана алат."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> билдирмелериңизди көрүп, телефонуңуз, SMS билдирүүлөр, Байланыштар, Микрофон жана Жакын жердеги түзмөктөргө болгон уруксаттарды пайдалана алат."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> билдирмелериңизди көрүп, колдонмолоруңузду туташкан түзмөктө иштете алат."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> ар кандай нерселерди жакын жердеги түзмөктөрдө көрсөтө алат."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Бул кызмат телефонуңуздагы сүрөттөрдү, медиа файлдарды жана билдирмелерди башка түзмөктөр менен бөлүшөт."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Учурдагы демейки колдонмо"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Экинчи суралбасын"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Демейки катары коюу"</string>
@@ -450,7 +464,7 @@
<string name="assistant_record_audio_user_sensitive_title" msgid="5532123360322362378">"Үн жардамчысынын иштегенин чагылдырган сүрөтчөнү көрсөтүү"</string>
<string name="assistant_record_audio_user_sensitive_summary" msgid="6482937591816401619">"Үн жардамчысын иштетүү үчүн микрофон колдонулганда, абал тилкесинде сүрөтчө көрүнөт"</string>
<string name="permgrouprequest_storage_isolated" msgid="4892154224026852295">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосу үчүн түзмөгүңүздөгү сүрөттөр менен мультимедиа файлдарын иштетесизби?"</string>
- <string name="permgrouprequest_contacts" msgid="8391550064551053695">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосуна байланыштарыңызды жеткиликтүү кыласызбы?"</string>
+ <string name="permgrouprequest_contacts" msgid="8391550064551053695">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосуна байланыштарыңызды пайдаланууга уруксат бересизби?"</string>
<string name="permgrouprequest_location" msgid="6990232580121067883">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосуна бул түзмөктүн жайгашкан жерин көрүүгө уруксат бересизби?"</string>
<string name="permgrouprequestdetail_location" msgid="2635935335778429894">"Колдонмону колдонуп жаткан маалда гана, ал сиздин кайда жүргөнүңүздү билип турат."</string>
<string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосуна бул түзмөктүн жайгашкан жерин көрүүгө уруксат бересизби?"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосуна ушул түзмөктөгү &lt;b&gt;сүрөттөрдү, видеолорду, ырларды, аудио файлдарды жана башка нерселерди&lt;/b&gt; жеткиликтүү кыласызбы?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосуна ушул түзмөктөгү ырлар менен аудио файлдарды жеткиликтүү кыласызбы?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосуна ушул түзмөктөгү сүрөттөр менен видеолорду жеткиликтүү кыласызбы?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосуна көбүрөөк сүрөттөргө кирүүгө уруксат берилсинби?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосуна аудио файлдарды жаздырганга уруксат бересизби?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Колдонмону колдонуп жатканда гана, ал аудио жаздыра алат"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; колдонмосуна аудио файлдарды жаздырууга уруксат бересизби?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Колдонмолор көчүрүлгөн текстти, сүрөттөрдү же башка нерселерди пайдаланганда билдирүүлөр көрүнөт"</string>
<string name="show_password_title" msgid="2877269286984684659">"Сырсөз көрүнсүн"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Сырсөз терилип жатканда символдор бир саамга көрүнөт"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Бул колдонмо <xliff:g id="PERMISSION_NAME">%s</xliff:g> тууралуу маалыматты үчүнчү тараптар менен бөлүшүүгө уруксат сурап жатат"</string>
</resources>
diff --git a/PermissionController/res/values-lo/strings.xml b/PermissionController/res/values-lo/strings.xml
index 22609be0a..7394085e2 100644
--- a/PermissionController/res/values-lo/strings.xml
+++ b/PermissionController/res/values-lo/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"ໃຊ້ “ໃນຂະນະທີ່ມີການໃຊ້ແອັບຢູ່”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"ໃຊ້ແບບ “ສະເພາະເທື່ອນີ້” ຕໍ່ໄປ"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"ຂໍ້ມູນເພີ່ມເຕີມ"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"ອະນຸຍາດສິດເຂົ້າເຖິງຮູບພາບທັງໝົດ"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ເລືອກຮູບພາບ"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"ເລືອກຮູບພາບເພີ່ມເຕີມ"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"ຢ່າເລືອກຮູບພາບເພີ່ມເຕີມ"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"ຢືນຢັນບໍ່ອະນຸຍາດ"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"ປິດໄວ້"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> ຈາກທັງໝົດ <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ມື້}other{# ມື້}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ຊົ່ວໂມງ}other{# ຊົ່ວໂມງ}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 ນທ}other{# ນທ}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 ວິ}other{# ວິ}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ມື້}other{# ມື້}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ຊົ່ວໂມງ}other{# ຊົ່ວໂມງ}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# ນທ}other{# ນທ}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# ວິ}other{# ວິ}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ສິດອະນຸຍາດໃດກໍໄດ້"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ເວລາໃດກໍໄດ້"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"7 ມື້ທີ່ຜ່ານມາ"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"24 ຊົ່ວໂມງທີ່ຜ່ານມາ"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"1 ຊົ່ວໂມງທີ່ຜ່ານມາ"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"15 ນາທີຜ່ານມາ"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"1 ນາທີທີ່ຜ່ານມາ"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{# ມື້ທີ່ຜ່ານມາ}other{# ມື້ທີ່ຜ່ານມາ}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{# ຊົ່ວໂມງທີ່ຜ່ານມາ}other{# ຊົ່ວໂມງທີ່ຜ່ານມາ}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{# ນາທີທີ່ຜ່ານມາ}other{# ນາທີທີ່ຜ່ານມາ}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"ບໍ່ມີການນຳໃຊ້ສິດອະນຸຍາດ"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"ການເຂົ້າເຖິງຫຼ້າສຸດຕອນໃດກໍໄດ້"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"ເຂົ້າເຖິງຫຼ້າສຸດໃນ 7 ມື້ທີ່ຜ່ານມາ"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"ການນຳໃຊ້ສິດອະນຸຍາດໃນ 1 ຊົ່ວໂມງທີ່ຜ່ານມາ"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"ການນຳໃຊ້ສິດອະນຸຍາດໃນ 15 ນາທີທີ່ຜ່ານມາ"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"ການນຳໃຊ້ສິດອະນຸຍາດໃນ 1 ນາທີທີ່ຜ່ານມາ"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"ບໍ່ໄດ້ໃຊ້ໃນ 24 ຊົ່ວໂມງທີ່ຜ່ານມາ"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"ບໍ່ໄດ້ໃຊ້ໃນ 7 ມື້ທີ່ຜ່ານມາ"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{ບໍ່ໄດ້ໃຊ້ໃນ # ມື້ທີ່ຜ່ານມາ}other{ບໍ່ໄດ້ໃຊ້ໃນ # ມື້ທີ່ຜ່ານມາ}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{ບໍ່ໄດ້ໃຊ້ໃນ # ຊົ່ວໂມງທີ່ຜ່ານມາ}other{ບໍ່ໄດ້ໃຊ້ໃນ # ຊົ່ວໂມງທີ່ຜ່ານມາ}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{ໃຊ້ໂດຍ 1 ແອັບ}other{ໃຊ້ໂດຍ # ແອັບ}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ເບິ່ງທັງໝົດໃນແຜງໜ້າປັດ"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"ກັ່ນຕອງແລ້ວໂດຍ: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"ອະນຸຍາດການເຂົ້າເຖິງມີເດຍເທົ່ານັ້ນ"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"ອະນຸຍາດຕະຫຼອດເວລາ"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"ອະນຸຍາດໃນເວລາທີ່ກຳລັງໃຊ້ແອັບເທົ່ານັ້ນ"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"ອະນຸຍາດຮູບພາບທັງໝົດ"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"ອະນຸຍາດຮູບພາບທີ່ເລືອກໄວ້"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"ຖາມທຸກເທື່ອ"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"ບໍ່ອະນຸຍາດ"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ສະຖານທີ່ແບບລະອຽດ"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"ບໍ່ອະນຸຍາດ"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"ເບິ່ງແອັບເພີ່ມເຕີມທີ່ສາມາດເຂົ້າເຖິງໄຟລ໌ທັງໝົດໄດ້"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ມື້}other{# ມື້}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ຊົ່ວໂມງ}other{# ຊົ່ວໂມງ}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 ນາທີ}other{# ນາທີ}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 ວິນາທີ}other{# ວິນາທີ}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ຊົ່ວໂມງ}other{# ຊົ່ວໂມງ}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# ນາທີ}other{# ນາທີ}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# ວິນາທີ}other{# ວິນາທີ}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"ການແຈ້ງເຕືອນການອະນຸຍາດ"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ແອັບທີ່ບໍ່ໄດ້ໃຊ້"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ແອັບທີ່ບໍ່ໄດ້ໃຊ້"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"ຕັ້ງ <xliff:g id="APP_NAME">%1$s</xliff:g> ເປັນແອັບການນຳທາງເລີ່ມຕົ້ນຂອງທ່ານບໍ?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"ບໍ່ຈຳເປັນຕ້ອງມີການອະນຸຍາດ"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການແຈ້ງເຕືອນຂອງທ່ານ ແລະ ເຂົ້າເຖິງການອະນຸຍາດໂທລະສັບ, SMS, ລາຍຊື່ຜູ້ຕິດຕໍ່ ແລະ ປະຕິທິນຂອງທ່ານໄດ້."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການແຈ້ງເຕືອນຂອງທ່ານ ແລະ ການອະນຸຍາດສິດເຂົ້າເຖິງໂທລະສັບ, SMS, ລາຍຊື່ຜູ້ຕິດຕໍ່, ໄມໂຄຣໂຟນ ແລະ ອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງຂອງທ່ານ."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ໂຕ້ຕອບກັບການແຈ້ງເຕືອນຂອງທ່ານ ແລະ ສະຕຣີມແອັບຂອງທ່ານຫາອຸປະກອນທີ່ເຊື່ອມແລ້ວໄດ້."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> ຈະໄດ້ຮັບອະນຸຍາດໃຫ້ສະຕຣີມເນື້ອຫາໄປຫາອຸປະກອນທີ່ຢູ່ໃກ້ຄຽງ."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"ບໍລິການນີ້ຈະແບ່ງປັນຮູບພາບ, ມີເດຍ ແລະ ການແຈ້ງເຕືອນຂອງທ່ານຈາກໂທລະສັບທ່ານໄປຫາອຸປະກອນອື່ນ."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"ຄ່າເລີ່ມຕົ້ນປັດຈຸບັນ"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"ບໍ່ຕ້ອງຖາມອີກ"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນ"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"ອະນຸຍາດໃຫ້ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ເຂົ້າເຖິງ &lt;b&gt;ຮູບພາບ, ວິດີໂອ, ເພງ, ສຽງ ແລະ ໄຟລ໌ອື່ນໆ&lt;/b&gt; ຢູ່ອຸປະກອນນີ້ບໍ?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"ອະນຸຍາດໃຫ້ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ເຂົ້າເຖິງເພງ ແລະ ສຽງຢູ່ອຸປະກອນນີ້ບໍ?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"ອະນຸຍາດໃຫ້ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ເຂົ້າເຖິງຮູບພາບ ແລະ ວິດີໂອຢູ່ອຸປະກອນນີ້ບໍ?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"ໃຫ້ສິດ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ສິດເຂົ້າເຖິງຮູບພາບເພີ່ມເຕີມບໍ?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"ອະນຸຍາດ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ໃຫ້ບັນທຶກສຽງບໍ?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"ແອັບນີ້ສາມາດບັນທຶກສຽງໃນຂະນະທີ່ທ່ານກຳລັງໃຊ້ແອັບເທົ່ານັ້ນ"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"ອະນຸຍາດ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ໃຫ້ບັນທຶກສຽງບໍ?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"ສະແດງຂໍ້ຄວາມເມື່ອແອັບເຂົ້າເຖິງຂໍ້ຄວາມ, ຮູບພາບ ຫຼື ເນື້ອຫາອື່ນທີ່ທ່ານສຳເນົາໄວ້"</string>
<string name="show_password_title" msgid="2877269286984684659">"ສະແດງລະຫັດຜ່ານ"</string>
<string name="show_password_summary" msgid="1110166488865981610">"ສະແດງຕົວອັກສອນເປັນເວລາສັ້ນໆໃນເວລາພິມ"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"ແອັບນີ້ລະບຸວ່າແອັບອາດແບ່ງປັນຂໍ້ມູນ <xliff:g id="PERMISSION_NAME">%s</xliff:g> ກັບພາກສ່ວນທີສາມ"</string>
</resources>
diff --git a/PermissionController/res/values-lt/strings.xml b/PermissionController/res/values-lt/strings.xml
index 0e6f08683..141cf817f 100644
--- a/PermissionController/res/values-lt/strings.xml
+++ b/PermissionController/res/values-lt/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Išlaikyti režimą „Kai programa naudojama“ įjungtą"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Palikti „Tik šį kartą“"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Daugiau inform."</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Leisti pasiekti visas nuotraukas"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Pasirinkti nuotraukas"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Pasirinkti daugiau nuotraukų"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Nesirinkti daugiau nuotraukų"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Vis tiek neleisti"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Atmesti"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> iš <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • „<xliff:g id="APP_NAME">%2$s</xliff:g>“ • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 diena}one{# diena}few{# dienos}many{# dienos}other{# dienų}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 valanda}one{# valanda}few{# valandos}many{# valandos}other{# valandų}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 minutė}one{# minutė}few{# minutės}many{# minutės}other{# minučių}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sekundė}one{# sekundė}few{# sekundės}many{# sekundės}other{# sekundžių}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# diena}one{# diena}few{# dienos}many{# dienos}other{# dienų}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# valanda}one{# valanda}few{# valandos}many{# valandos}other{# valandų}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# minutė}one{# minutė}few{# minutės}many{# minutės}other{# minučių}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sekundė}one{# sekundė}few{# sekundės}many{# sekundės}other{# sekundžių}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Bet koks leidimas"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Bet koks laikas"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Pastarosios 7 dienos"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Pastarosios 24 valandos"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Pastaroji 1 valanda"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Pastarosios 15 minučių"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Pastaroji minutė"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Pastaroji # diena}one{Pastaroji # diena}few{Pastarosios # dienos}many{Pastarosios # dienos}other{Pastarųjų # dienų}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Pastaroji # valanda}one{Pastaroji # valanda}few{Pastarosios # valandos}many{Pastarosios # valandos}other{Pastarųjų # valandų}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Pastaroji # minutė}one{Pastaroji # minutė}few{Pastarosios # minutės}many{Pastarosios # minutės}other{Pastarųjų # minučių}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Leidimai nenaudoti"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Paskutinį kartą pasiekta bet kuriuo metu"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Paskutinį kartą pasiekta per pastarąsias 7 dienas"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Leidimo naudojimas per pastarąją 1 valandą"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Leidimo naudojimas per pastarąsias 15 minučių"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Leidimo naudojimas per pastarąją 1 minutę"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Nenaudota per pastarąsias 24 val."</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Nenaudota per pastarąsias septynias dienas"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Nenaudota per pastarąją # dieną}one{Nenaudota per pastarąją # dieną}few{Nenaudota per pastarąsias # dienas}many{Nenaudota per pastarosios # dienos}other{Nenaudota per pastarąsias # dienų}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Nenaudota per pastarąją # valandą}one{Nenaudota per pastarąją # valandą}few{Nenaudota per pastarąsias # valandas}many{Nenaudota per pastarosios # valandos}other{Nenaudota per pastarąsias # valandų}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Naudojama 1 programoje}one{Naudojama # programoje}few{Naudojama # programose}many{Naudojama # programos}other{Naudojama # programų}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Žr. viską informacijos suvestinėje"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtruota pagal: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Leisti pasiekti tik mediją"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Leisti visą laiką"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Leisti tik naudojant programą"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Leisti visas nuotraukas"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Leisti pasirinktas nuotraukas"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Klausti kaskart"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Neleisti"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Tiksli vietovė"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Neleidžiama"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Peržiūrėti daugiau programų, galinčių pasiekti visus failus"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 diena}one{# diena}few{# dienos}many{# dienos}other{# dienų}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 valanda}one{# valanda}few{# valandos}many{# valandos}other{# valandų}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minutė}one{# minutė}few{# minutės}many{# minutės}other{# minučių}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekundė}one{# sekundė}few{# sekundės}many{# sekundės}other{# sekundžių}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# valanda}one{# valanda}few{# valandos}many{# valandos}other{# valandų}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minutė}one{# minutė}few{# minutės}many{# minutės}other{# minučių}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekundė}one{# sekundė}few{# sekundės}many{# sekundės}other{# sekundžių}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Leidimų priminimai"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 nenaudojama programa"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Nenaudojamų programų: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Nustatyti „<xliff:g id="APP_NAME">%1$s</xliff:g>“ kaip numatytąją navigacijos programą?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nereikia jokių leidimų"</string>
<string name="role_watch_description" msgid="267003778693177779">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ galės sąveikauti su pranešimų funkcija ir pasiekti telefoną, SMS, kontaktus ir kalendorių."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“ bus leidžiama sąveikauti su jūsų pranešimais ir pasiekti jūsų leidimus „Telefonas“, „SMS“, „Kontaktai“, „Mikrofonas“ ir „Įrenginiai netoliese“."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"„<xliff:g id="APP_NAME">%1$s</xliff:g>“ galės sąveikauti su pranešimų funkcija ir srautu perduoti programas į prijungtą įrenginį."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Programai „<xliff:g id="APP_NAME">%1$s</xliff:g>“ bus leidžiama srautu perduoti turinį netoliese esantiems įrenginiams."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ši paslauga bendrina jūsų nuotraukas, mediją ir pranešimus iš telefono ir kitų įrenginių."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Dabartinė numatytoji"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Daugiau neklausti"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Nustatyti numatytąja"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Leisti &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pasiekti &lt;b&gt;nuotrauk., vaizdo, garso įrašus, muziką, kitus failus&lt;/b&gt; įrenginyje?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Leisti &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pasiekti muziką ir garso failus šiame įrenginyje?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Leisti &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pasiekti nuotraukas ir vaizdo įrašus šiame įrenginyje?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Leisti &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; pasiekti daugiau nuotraukų?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Leisti &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; įrašyti garsą?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Programa galės įrašyti garsą, tik kai ją naudosite"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Leisti &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; įrašyti garsą?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Rodyti pranešimą, kai programos pasiekia nukopijuotą tekstą, vaizdus ar kitą turinį"</string>
<string name="show_password_title" msgid="2877269286984684659">"Rodyti slaptažodžius"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Trumpai rodyti simbolius vedant tekstą"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Ši programa nurodė, kad gali bendrinti <xliff:g id="PERMISSION_NAME">%s</xliff:g> duomenis su trečiosiomis šalimis"</string>
</resources>
diff --git a/PermissionController/res/values-lv/strings.xml b/PermissionController/res/values-lv/strings.xml
index 9cfb9c087..c616b10eb 100644
--- a/PermissionController/res/values-lv/strings.xml
+++ b/PermissionController/res/values-lv/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Paturēt atļauju “Kamēr lietotne tiek izmantota”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Paturēt iestatījumu “Tikai šoreiz”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Informācija"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Atļaut piekļuvi visiem fotoattēliem"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Atlasīt fotoattēlus"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Atlasīt vairāk fotoattēlu"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Neatlasīt citus fotoattēlus"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Tomēr neatļaut"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Nerādīt"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>. no <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 diena}zero{# dienu}one{# diena}other{# dienas}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 stunda}zero{# stundu}one{# stunda}other{# stundas}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}zero{# min}one{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}zero{# s}one{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# diena}zero{# dienu}one{# diena}other{# dienas}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# stunda}zero{# stundu}one{# stunda}other{# stundas}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}zero{# min}one{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}zero{# s}one{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Jebkura atļauja"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Jebkurā laikā"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Pēdējās 7 dienās"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Pēdējās 24 stundās"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Pēdējā stundā"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Pēdējās 15 minūtēs"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Pēdējā minūtē"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Pēdējā # dienā}zero{Pēdējās # dienās}one{Pēdējā # dienā}other{Pēdējās # dienās}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Pēdējā # stundā}zero{Pēdējās # stundās}one{Pēdējā # stundā}other{Pēdējās # stundās}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Pēdējā # minūtē}zero{Pēdējās # minūtēs}one{Pēdējā # minūtē}other{Pēdējās # minūtēs}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Nav lietota neviena atļauja"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Nesenākā piekļuve jebkurā laikā"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Nesenākā piekļuve pēdējo 7 dienu laikā"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Atļauju lietojums pēdējās stundas laikā"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Atļauju lietojums pēdējās 15 minūtēs"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Atļauju lietojums pēdējā minūtē"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Pēdējo 24 stundu laikā nav izmantota"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Pēdējo 7 dienu laikā nav izmantota"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Pēdējās # dienas laikā nav izmantota}zero{Pēdējo # dienu laikā nav izmantota}one{Pēdējās # dienas laikā nav izmantota}other{Pēdējo # dienu laikā nav izmantota}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Pēdējās # stundas laikā nav izmantota}zero{Pēdējo # stundu laikā nav izmantota}one{Pēdējās # stundas laikā nav izmantota}other{Pēdējo # stundu laikā nav izmantota}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Izmanto 1 lietotne}zero{Izmanto # lietotnes}one{Izmanto # lietotne}other{Izmanto # lietotnes}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Skatīt visu rīkā Permission Dashboard"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrēts pēc: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Atļaut piekļūt tikai multivides failiem"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Vienmēr atļaut"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Atļaut tikai lietotnes izmantošanas laikā"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Atļaut visus fotoattēlus"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Atļaut atlasītos fotoattēlus"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Vaicāt katru reizi"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Neatļaut"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Precīza atrašanās vieta"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nav atļauts"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Skatīt citas lietotnes, kas drīkst piekļūt visiem failiem"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 diena}zero{# dienu}one{# diena}other{# dienas}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 stunda}zero{# stundu}one{# stunda}other{# stundas}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minūte}zero{# minūšu}one{# minūte}other{# minūtes}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekunde}zero{# sekunžu}one{# sekunde}other{# sekundes}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# stunda}zero{# stundu}one{# stunda}other{# stundas}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minūte}zero{# minūšu}one{# minūte}other{# minūtes}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekunde}zero{# sekunžu}one{# sekunde}other{# sekundes}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Atgādinājumi par atļauju"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 neizmantota lietotne"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> neizmantotas lietotnes"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Vai vēlaties iestatīt lietotni <xliff:g id="APP_NAME">%1$s</xliff:g> kā noklusējuma navigācijas lietotni?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Atļaujas nav nepieciešamas"</string>
<string name="role_watch_description" msgid="267003778693177779">"Lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g> tiks atļauts mijiedarboties ar jūsu paziņojumiem un piekļūt šādām atļaujām: Tālrunis, Īsziņas, Kontaktpersonas un Kalendārs."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> drīkstēs mijiedarboties ar jūsu paziņojumiem un piekļūt atļaujām Tālrunis, Īsziņas, Kontaktpersonas, Mikrofons un Tuvumā esošas ierīces."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g> tiks atļauts mijiedarboties ar jūsu paziņojumiem un straumēt jūsu lietotņu saturu uz pievienoto ierīci."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Lietotnei <xliff:g id="APP_NAME">%1$s</xliff:g> tika atļauta satura straumēšana uz tuvumā esošām ierīcēm."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Izmantojot šo pakalpojumu, jūsu tālruņa fotoattēli, multivides saturs un paziņojumi tiek kopīgoti ar citām ierīcēm."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Pašreizējais noklusējums"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Nejautāt atkārtoti"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Iest. kā noklusējumu"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Vai atļaut lietotnei &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; piekļūt &lt;b&gt;foto, video, mūzikai, audio u.c. failiem&lt;/b&gt; ierīcē?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Vai atļaut lietotnei &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; piekļūt mūzikai un audio failiem šajā ierīcē?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Vai atļaut lietotnei &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; piekļūt fotoattēliem un video šajā ierīcē?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Vai piešķirt lietotnei &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; piekļuvi citiem fotoattēliem?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Vai atļaut lietotnei &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ierakstīt audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Lietotne varēs ierakstīt audio tikai tad, kad izmantosiet lietotni."</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Vai atļaut lietotnei &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ierakstīt audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Rādīt ziņojumu, kad lietotnes piekļūst jūsu nokopētajam tekstam, attēliem vai citam saturam"</string>
<string name="show_password_title" msgid="2877269286984684659">"Rādīt paroles"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Rakstot tiek īslaicīgi rādītas rakstzīmes"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Lietotne norādīja, ka tā var kopīgot ar trešajām pusēm šādus datus: <xliff:g id="PERMISSION_NAME">%s</xliff:g>"</string>
</resources>
diff --git a/PermissionController/res/values-mk/strings.xml b/PermissionController/res/values-mk/strings.xml
index 514ae451b..a08bbacba 100644
--- a/PermissionController/res/values-mk/strings.xml
+++ b/PermissionController/res/values-mk/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Задржи ја „Додека се користи апликацијата“"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Задржи „Само овој пат“"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Уште информации"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Дозволете пристап до сите фотографии"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Изберете фотографии"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Изберете повеќе фотографии"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Не избирајте повеќе фотографии"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Сепак не дозволувај"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Отфрли"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> од <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ден}one{# ден}other{# дена}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 час}one{# час}other{# часа}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 мин.}one{# мин.}other{# мин.}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 сек.}one{# сек.}other{# сек.}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ден}one{# ден}other{# дена}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# час}one{# час}other{# часа}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# мин.}one{# мин.}other{# мин.}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# сек.}one{# сек.}other{# сек.}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Која било дозвола"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Кога било"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Минатите 7 дена"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Минатите 24 часа"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Минатиот час"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Минатите 15 минути"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Последна 1 минута"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Минатиот # ден}one{Минатите # ден}other{Минатите # дена}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Минатиот # час}one{Минатите # час}other{Минатите # часа}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Минатата # минута}one{Минатите # минута}other{Минатите # минути}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Не се користени дозволи"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Најнов пристап во кое било време"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Најнов пристап во минатите 7 дена"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Користење дозволи во последниот 1 час"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Користење дозволи во минатите 15 минути"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Користење дозволи во последната 1 минута"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Не е користена во минатите 24 часа"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Не е користена во минатите 7 дена"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Не е користена во минатиот # ден}one{Не е користена во минатите # ден}other{Не е користена во минатите # дена}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Не е користена во минатиот # час}one{Не е користена во минатите # час}other{Не е користена во минатите # часа}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Искористена од 1 апликација}one{Искористена од # апликација}other{Искористена од # апликации}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Прикажи ги сите на контролната табла"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Филтрирано според: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Дозволи пристап само до аудиовизуелните содржини"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Дозволи цело време"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Дозволи само додека се користи апликацијата"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Дозволете ги сите фотографии"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Дозволете избрани фотографии"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Прашувај секогаш"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Не дозволувај"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Прецизна локација"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Без дозвола"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Видете уште апликации што може да пристапат до сите датотеки"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ден}one{# ден}other{# дена}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 час}one{# час}other{# часа}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 минута}one{# минута}other{# минути}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 секунда}one{# секунда}other{# секунди}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# час}one{# час}other{# часа}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# минута}one{# минута}other{# минути}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# секунда}one{# секунда}other{# секунди}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Потсетници за дозволата"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 некористена апликација"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> некористени апликации"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Да се постави <xliff:g id="APP_NAME">%1$s</xliff:g> како ваша стандардна апликација за навигација?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Не се потребни дозволи"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> ќе може да остварува интеракција со известувањата и да пристапува до дозволите за телефонот, SMS, контактите и календарот."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> ќе може да остварува интеракција со известувањата и да пристапува до дозволите за „Телефон“, SMS, „Контакти“, „Микрофон“ и „Уреди во близина“."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> ќе може да остварува интеракција со известувањата и да пренесува од вашите апликации на поврзаниот уред."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> ќе добие дозвола да стримува содржини на уреди во близина."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Услугава споделува ваши фотографии, содржини и известувања од телефонот на други уреди."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Стандардна апликација сега"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Не прашувај повторно"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Нека биде стандардна"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Да се дозволи &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; да пристапува до &lt;b&gt;фотографии, видеа, музика, аудио и други датотеки&lt;/b&gt; на уредов?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Да се дозволи &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; да пристапува до музика и аудиодатотеки на уредов?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Да се дозволи &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; да пристапува до фотографии и видеа на уредов?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Да се дозволи пристап на &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; до повеќе фотографии?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Да се дозволи &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; да снима аудио?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Апликацијава ќе може да снима аудио само додека ја користите"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Да се дозволи &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; да снима аудио?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Нека се прикажува известување кога апликациите пристапуваат до текст, слики или други содржини што сте ги копирале"</string>
<string name="show_password_title" msgid="2877269286984684659">"Прикажувај ги лозинките"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Прикажувај ги знаците накратко додека пишувам"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Апликацијава изјави дека можеби ќе споделува податоци за <xliff:g id="PERMISSION_NAME">%s</xliff:g> со трети страни"</string>
</resources>
diff --git a/PermissionController/res/values-ml/strings.xml b/PermissionController/res/values-ml/strings.xml
index 8a328b4f6..10c47285c 100644
--- a/PermissionController/res/values-ml/strings.xml
+++ b/PermissionController/res/values-ml/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“ആപ്പ് ഉപയോഗത്തിലിരിക്കുമ്പോൾ” തുടരുക"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“ഇപ്പോഴത്തേക്ക് മാത്രം” നിലനിർത്തുക"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"കൂടുതൽ വിവരങ്ങൾ"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"എല്ലാ ഫോട്ടോകളിലേക്കും ആക്‌സസ് അനുവദിക്കുക"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ഫോട്ടോകൾ തിരഞ്ഞെടുക്കുക"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"കൂടുതൽ ഫോട്ടോകൾ തിരഞ്ഞെടുക്കുക"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"കൂടുതൽ ഫോട്ടോകൾ തിരഞ്ഞെടുക്കരുത്"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"എന്തായാലും അനുവദിക്കരുത്"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>-ൽ <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> എണ്ണം"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{ഒരു ദിവസം}other{# ദിവസം}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{ഒരു മണിക്കൂർ}other{# മണിക്കൂർ}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{ഒരു മിനിറ്റ്}other{# മിനിറ്റ്}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{ഒരു സെക്കൻഡ്}other{# സെക്കൻഡ്}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ദിവസം}other{# ദിവസം}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# മണിക്കൂർ}other{# മണിക്കൂർ}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# മിനിറ്റ്}other{# മിനിറ്റ്}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# സെക്കൻഡ്}other{# സെക്കൻഡ്}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ഏതെങ്കിലും അനുമതി"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ഏത് സമയത്തും"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"കഴിഞ്ഞ 7 ദിവസം"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"അവസാന 24 മണിക്കൂർ"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"കഴിഞ്ഞ ഒരു മണിക്കൂര്‍‌"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"കഴിഞ്ഞ 15 മിനിറ്റ്"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"അവസാന ഒരു മിനിറ്റ്"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{അവസാന # ദിവസം}other{അവസാന # ദിവസം}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{അവസാന # മണിക്കൂർ}other{അവസാന # മണിക്കൂർ}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{അവസാന # മിനിറ്റ്}other{അവസാന # മിനിറ്റ്}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"അനുമതി ഉപയോഗങ്ങളൊന്നുമില്ല"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"ഏതുസമയത്തുമുള്ള ഏറ്റവും സമീപകാല ആക്‌സസ്"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"കഴിഞ്ഞ 7 ദിവസത്തിലെ ഏറ്റവും സമീപകാല ആക്‌സസ്"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"കഴിഞ്ഞ 1 മണിക്കൂറിലെ അനുമതിയുടെ ഉപയോഗം"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"കഴിഞ്ഞ 15 മിനിറ്റിലെ അനുമതിയുടെ ഉപയോഗം"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"കഴിഞ്ഞ 1 മിനിറ്റിലെ അനുമതിയുടെ ഉപയോഗം"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"കഴിഞ്ഞ 24 മണിക്കൂറിനിടെ ഉപയോഗിച്ചിട്ടില്ല"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"കഴിഞ്ഞ 7 ദിവസത്തിനിടെ ഉപയോഗിച്ചിട്ടില്ല"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{കഴിഞ്ഞ # ദിവസത്തിനിടെ ഉപയോഗിച്ചിട്ടില്ല}other{കഴിഞ്ഞ # ദിവസത്തിനിടെ ഉപയോഗിച്ചിട്ടില്ല}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{കഴിഞ്ഞ # മണിക്കൂറിനിടെ ഉപയോഗിച്ചിട്ടില്ല}other{കഴിഞ്ഞ # മണിക്കൂറിനിടെ ഉപയോഗിച്ചിട്ടില്ല}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{ഒരു ആപ്പ് ഉപയോഗിച്ചു}other{# ആപ്പുകൾ ഉപയോഗിച്ചു}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"എല്ലാം ഡാഷ്ബോർഡിൽ കാണുക"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"ഇതുപ്രകാരം ഫിൽട്ടർ ചെയ്‌‌തു: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"മീഡിയ ഫയലുകളിലേക്ക് മാത്രം ആക്‌സസ് അനുവദിക്കുക"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"ഏതുസമയത്തും അനുവദിക്കുക"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"ആപ്പ് ഉപയോഗിക്കുമ്പോൾ മാത്രം"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"എല്ലാ ഫോട്ടോകളും ആക്‌സസ് ചെയ്യാൻ അനുവദിക്കുക"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"തിരഞ്ഞെടുത്ത ഫോട്ടോകൾ ആക്‌സസ് ചെയ്യാൻ അനുവദിക്കുക"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"എപ്പോഴും ചോദിക്കുക"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"അനുവദിക്കരുത്"</string>
<string name="precise_image_description" msgid="6349638632303619872">"കൃത്യമായ ലൊക്കേഷൻ"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"അനുവദിച്ചിട്ടില്ല"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"എല്ലാ ഫയലുകളും ആക്‌സസ് ചെയ്യാൻ കഴിയുന്ന കൂടുതൽ ആപ്പുകൾ കാണുക"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{ഒരു ദിവസം}other{# ദിവസം}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{ഒരു മണിക്കൂർ}other{# മണിക്കൂർ}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{ഒരു മിനിറ്റ്}other{# മിനിറ്റ്}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{ഒരു സെക്കൻഡ്}other{# സെക്കൻഡ്}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# മണിക്കൂർ}other{# മണിക്കൂർ}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# മിനിറ്റ്}other{# മിനിറ്റ്}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# സെക്കൻഡ്}other{# സെക്കൻഡ്}}"</string>
<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>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനെ നിങ്ങളുടെ ഡിഫോൾട്ട് നാവിഗേഷൻ ആപ്പ് ആയി സജ്ജീകരിക്കണോ?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"അനുമതികളൊന്നും ആവശ്യമില്ല"</string>
<string name="role_watch_description" msgid="267003778693177779">"നിങ്ങളുടെ അറിയിപ്പുകളുമായി സംവദിക്കാനും നിങ്ങളുടെ ഫോൺ, SMS, കോൺടാക്റ്റുകൾ, കലണ്ടർ അനുമതികൾ എന്നിവ ആക്‌സസ് ചെയ്യാനും <xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനെ അനുവദിക്കും."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"നിങ്ങളുടെ അറിയിപ്പുകളുമായി സംവദിക്കാനും ഫോൺ, SMS, കോൺടാക്റ്റുകൾ, മൈക്രോഫോൺ, സമീപമുള്ള ഉപകരണങ്ങളുടെ അനുമതികൾ എന്നിവ ആക്‌സസ് ചെയ്യാനും <xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനെ അനുവദിക്കും."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"നിങ്ങളുടെ അറിയിപ്പുകളുമായി സംവദിക്കാനും കണക്‌റ്റ് ചെയ്‌ത ഉപകരണത്തിലേക്ക് നിങ്ങളുടെ ആപ്പുകൾ സ്ട്രീം ചെയ്യാനും <xliff:g id="APP_NAME">%1$s</xliff:g> ആപ്പിനെ അനുവദിക്കും."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"സമീപമുള്ള ഉപകരണങ്ങളിലേക്ക് ഉള്ളടക്കം സ്ട്രീം ചെയ്യാൻ <xliff:g id="APP_NAME">%1$s</xliff:g> എന്നതിനെ അനുവദിക്കും."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"ഈ സേവനം, നിങ്ങളുടെ ഫോണിൽ നിന്നുള്ള ഫോട്ടോകളും മീഡിയയും അറിയിപ്പുകളും മറ്റ് ഉപകരണങ്ങളിലേക്ക് പങ്കിടുന്നു."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"നിലവിലെ ഡിഫോൾട്ട്"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"വീണ്ടും ആവശ്യപ്പെടരുത്"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ഡിഫോൾട്ടായി സജ്ജമാക്കൂ"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;ഫോട്ടോ, വീഡിയോ, സംഗീതം, ഓഡിയോ, മറ്റ് ഫയലുകൾ&lt;/b&gt; എന്നിവയിലേക്ക് &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; എന്നതിന് ആക്സസ് നൽകണോ?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"ഈ ഉപകരണത്തിലെ സംഗീതവും ഓഡിയോയും ആക്സസ് ചെയ്യാൻ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; എന്നതിനെ അനുവദിക്കണോ?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"ഈ ഉപകരണത്തിലെ ഫോട്ടോകളും വീഡിയോകളും ആക്‌സസ് ചെയ്യാൻ &lt;b&gt; <xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; എന്നതിനെ അനുവദിക്കണോ?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"കൂടുതൽ ഫോട്ടോകളിലേക്ക് &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ആപ്പിന് ആക്‌സസ് നൽകണോ?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"ഓഡിയോ റെക്കോർഡ് ചെയ്യാൻ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ആപ്പിനെ അനുവദിക്കണോ?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"നിങ്ങൾ ആപ്പ് ഉപയോഗിക്കുമ്പോൾ മാത്രമേ അതിന് ഓഡിയോ റെക്കോർഡ് ചെയ്യാൻ കഴിയൂ"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"ഓഡിയോ റെക്കോർഡ് ചെയ്യാൻ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ആപ്പിനെ അനുവദിക്കണോ?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"നിങ്ങൾ പകർത്തിയ ടെക്‌സ്‌റ്റോ ചിത്രങ്ങളോ മറ്റ് ഉള്ളടക്കമോ ആപ്പുകൾ ആക്‌സസ് ചെയ്യുമ്പോൾ ഒരു സന്ദേശം കാണിക്കുക"</string>
<string name="show_password_title" msgid="2877269286984684659">"പാസ്‌വേ‌ഡുകൾ കാണിക്കുക"</string>
<string name="show_password_summary" msgid="1110166488865981610">"ടൈപ്പ് ചെയ്യുന്ന അക്ഷരങ്ങൾ പ്രദർശിപ്പിക്കുക"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"<xliff:g id="PERMISSION_NAME">%s</xliff:g> ഡാറ്റ മൂന്നാം കക്ഷികളുമായി പങ്കിട്ടേക്കാമെന്ന് ഈ ആപ്പ് പ്രസ്താവിക്കുന്നു"</string>
</resources>
diff --git a/PermissionController/res/values-mn/strings.xml b/PermissionController/res/values-mn/strings.xml
index f16b8bb83..b423976b5 100644
--- a/PermissionController/res/values-mn/strings.xml
+++ b/PermissionController/res/values-mn/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"\"Аппыг ашиглаж байх үед\" хэвээр үлдээх"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“Зөвхөн энэ удаад зөвшөөрөх”-г хэвээр хадгалах"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Дэлгэрэнгүй мэдээлэл"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Бүх зурагт хандахыг зөвшөөрөх"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Зураг сонгох"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Илүү олон зураг сонгох"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Илүү олон зураг сонгохгүй"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ямартай ч бүү зөвшөөр"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Үл хэрэгсэх"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>-н <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 өдөр}other{# өдөр}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 цаг}other{# цаг}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 мин}other{# мин}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 сек}other{# сек}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# хоног}other{# хоног}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# цаг}other{# цаг}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# мин}other{# мин}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# сек}other{# сек}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Дурын зөвшөөрөл"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Дурын хугацаа"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Сүүлийн 7 хоног"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Сүүлийн 24 цаг"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Сүүлийн 1 цаг"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Сүүлийн 15 минут"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Сүүлийн 1 минут"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Сүүлийн # хоног}other{Сүүлийн # хоног}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Сүүлийн # цаг}other{Сүүлийн # цаг}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Сүүлийн # минут}other{Сүүлийн # минут}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Зөвшөөрлийн хэрэглээ алга"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Бүх цаг үеийн хамгийн сүүлийн хандалт"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Сүүлийн 7 хоногт хамгийн сүүлд хийсэн хандалт"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Сүүлийн 1 цагийн зөвшөөрлийн ашиглалт"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Сүүлийн 15 минутын зөвшөөрлийн ашиглалт"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Сүүлийн 1 минутын зөвшөөрлийн ашиглалт"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Өнгөрсөн 24 цагт ашиглаагүй"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Өнгөрсөн 7 хоногт ашиглаагүй"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Сүүлийн # хоногт ашиглаагүй}other{Сүүлийн # хоногт ашиглаагүй}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Сүүлийн # цагт ашиглаагүй}other{Сүүлийн # цагт ашиглаагүй}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 апп ашигласан}other{# апп ашигласан}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Хяналтын самбараас бүгдийг нь харах"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Шүүсэн: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Зөвхөн медиад хандахыг зөвшөөрөх"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Ямар ч үед зөвшөөрөх"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Зөвхөн аппыг ашиглаж байх үед зөвшөөрөх"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Бүх зургийг зөвшөөрөх"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Сонгосон зургуудыг зөвшөөрөх"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Тухай бүрд асуух"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Бүү зөвшөөр"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Нарийвчилсан байршил"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Зөвшөөрөөгүй"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Бүх файлд хандах боломжтой илүү их аппыг харах"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 өдөр}other{# өдөр}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 цаг}other{# цаг}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 минут}other{# минут}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 секунд}other{# секунд}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# цаг}other{# цаг}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# минут}other{# минут}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# секунд}other{# секунд}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Зөвшөөрлийн сануулагч"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"Ашиглаагүй 1 апп"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Ашиглаагүй <xliff:g id="NUMBER_OF_APPS">%s</xliff:g> апп"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г өгөгдмөл зам заагч аппаарaa тохируулах уу?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Ямар ч зөвшөөрөл шаардлагагүй"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г таны мэдэгдлүүдтэй харилцаж, таны Утас, SMS, Харилцагчид болон Календарийн зөвшөөрөлд хандахыг зөвшөөрнө."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g>-г таны мэдэгдлүүдтэй харилцаж, мөн таны утас, SMS, харилцагчид, микрофон болон ойролцоох төхөөрөмжүүдийн зөвшөөрөлд хандахыг зөвшөөрнө."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д таны мэдэгдэлтэй харилцах болон таны аппуудыг холбогдсон төхөөрөмжид дамжуулахыг зөвшөөрөх болно."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g>-д ойролцоох төхөөрөмжүүдэд контент дамжуулахыг зөвшөөрнө."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Энэ үйлчилгээ таны утаснаас зураг, медиа болон мэдэгдлийг тань бусад төхөөрөмжтэй хуваалцана."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Одоогийн өгөгдмөл апп"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Дахиж бүү асуу"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Өгөгдмөлөөр тохируулах"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-д энэ төхөөрөмжийн &lt;b&gt;зураг, видео, хөгжим, аудио, бусад файлд&lt;/b&gt; хандахыг зөвшөөрөх үү?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-д энэ төхөөрөмж дээрх хөгжим болон аудионд хандахыг зөвшөөрөх үү?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-д энэ төхөөрөмж дээрх зураг болон видеонд хандахыг зөвшөөрөх үү?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-д илүү олон зурагт хандах эрх өгөх үү?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-д аудио бичихийг зөвшөөрөх үү?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Энэ апп зөвхөн таныг ашиглаж байх үед л аудио бичих боломжтой болно"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;-д аудио бичихийг зөвшөөрөх үү?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Аппууд таны хуулсан текст, зураг эсвэл бусад контентод хандах үед мессеж харуулах"</string>
<string name="show_password_title" msgid="2877269286984684659">"Нууц үгнүүдийг харуулах"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Таныг бичиж явцад тэмдэгтүүдийг түр үзүүлэх"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Энэ апп <xliff:g id="PERMISSION_NAME">%s</xliff:g>-н өгөгдлийг гуравдагч талуудтай хуваалцаж болохыг мэдэгдсэн"</string>
</resources>
diff --git a/PermissionController/res/values-mr/strings.xml b/PermissionController/res/values-mr/strings.xml
index 242d32857..9a2d99ad8 100644
--- a/PermissionController/res/values-mr/strings.xml
+++ b/PermissionController/res/values-mr/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“ॲप वापरत असताना” ठेवा"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“फक्त यावेळेपुरते” ठेवा"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"अधिक माहिती"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"सर्व फोटो ॲक्सेस करण्याची अनुमती द्या"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"फोटो निवडा"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"आणखी फोटो निवडा"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"आणखी फोटो निवडू नका"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"तरीही अनुमती देऊ नका"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"डिसमिस करा"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> पैकी <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{एक दिवस}other{# दिवस}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{एक तास}other{# तास}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{एक मिनिट}other{# मिनिटे}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{एक सेकंद}other{# सेकंद}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# दिवस}other{# दिवस}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# तास}other{# तास}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# मिनिट}other{# मिनिटे}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# सेकंद}other{# सेकंद}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"कोणतीही परवानगी"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"कधीही"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"शेवटचे सात दिवस"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"गेल्या २४ तासात"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"शेवटचा एक तास"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"शेवटची १५ मिनिटे"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"शेवटचा एक मिनिट"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{शेवटचा # दिवस}other{शेवटचे # दिवस}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{मागील # तासामधील}other{मागील # तासांमधील}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{मागील # मिनिटामधील}other{मागील # मिनिटांमधील}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"वापराची परवानगी नाही"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"कोणत्याही वेळी सर्वात अलीकडील अ‍ॅक्सेस"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"मागील सात दिवसांतील सर्वात अलीकडील अ‍ॅक्सेस"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"मागील एका तासातील परवानगी वापर"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"मागील १५ मिनिटांतील परवानगी वापर"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"मागील एका मिनिटातील परवानगी वापर"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"मागील २४ तासांमध्ये न वापरलेली"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"मागील सात दिवसांमध्ये न वापरलेली"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{मागील # दिवसामध्ये न वापरलेली}other{मागील # दिवसांमध्ये न वापरलेली}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{मागील # तासामध्ये न वापरलेली}other{मागील # तासांमध्ये न वापरलेली}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{एका अ‍ॅपने वापरल्या}other{# अ‍ॅप्सनी वापरल्या}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"डॅशबोर्डमध्ये सर्व पहा"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"यानुसार फिल्टर केले: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"फक्त मीडिया ॲक्सेस करण्यासाठी अनुमती द्या"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"सर्व वेळी अनुमती द्या"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"फक्त अ‍ॅप वापरत असताना अनुमती द्या"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"सर्व फोटोना अनुमती द्या"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"निवडलेल्या फोटोना अनुमती द्या"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"प्रत्येक वेळी विचारा"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"अनुमती देऊ नका"</string>
<string name="precise_image_description" msgid="6349638632303619872">"अचूक स्थान"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"अनुमती नाही"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"सर्व फाइलचा ॲक्सेस असलेली आणखी ॲप्स पहा"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{एक दिवस}other{# दिवस}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{एक तास}other{# तास}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{एक मिनिट}other{# मिनिटे}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{एक सेकंद}other{# सेकंद}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# तास}other{# तास}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# मिनिट}other{# मिनिटे}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# सेकंद}other{# सेकंद}}"</string>
<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>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> ला तुमचे डीफॉल्ट नेव्हिगेशन ॲप म्हणून सेट करायचे आहे का?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"परवानगीची गरज नाही"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> ला तुमच्या सूचनांशी संवाद साधण्याची आणि तुमचा फोन, एसएमएस, संपर्क आणि Calendar च्या परवानग्या अ‍ॅक्सेस करण्याची अनुमती मिळेल."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> ला तुमच्या सुचानांसोबत संवाद साधण्याची आणि तुमचा फोन, एसएमएस, संपर्क, मायक्रोफोन व जवळपासच्या डिव्हाइसच्या परवानग्या अ‍ॅक्सेस करण्याची अनुमती मिळेल."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> ला तुमच्या सूचनांशी संवाद साधण्याची आणि कनेक्‍ट केलेल्या डिव्हाइसवर तुमची ॲप्स स्ट्रीम करण्याची अनुमती दिली जाईल."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> ला जवळपासची डिव्हाइसवर आशय स्ट्रीम करण्याची अनुमती मिळेल."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"ही सेवा तुमचे फोटो, मीडिया आणि सूचना तुमच्या फोनवरून दुसऱ्या डिव्हाइसवर शेअर करते."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"सद्य डीफॉल्ट"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"पुन्हा विचारू नका"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"डीफॉल्ट सेट करा"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ला या डिव्हाइसवरील &lt;b&gt;फोटो, व्हिडिओ, संगीत, ऑडिओ व इतर फाइल&lt;/b&gt; अ‍ॅक्सेस करू द्यायच्या?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ला या डिव्हाइसवरील संगीत आणि ऑडिओ अ‍ॅक्सेस करण्याची अनुमती द्यायची आहे का?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ला या डिव्हाइसवरील फोटो आणि व्हिडिओ अ‍ॅक्सेस करण्याची अनुमती द्यायची आहे का?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ला आणखी फोटो अ‍ॅक्सेस करण्याची अनुमती द्यायची आहे का?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ला ऑडिओ रेकॉर्ड करू द्यायचा?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"ॲप फक्त तुम्ही ॲप वापरत असतानाच ऑडिओ रेकॉर्ड करू शकते"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ला ऑडिओ रेकॉर्ड करायची अनुमती द्यायची?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"तुम्ही कॉपी केलेला मजकूर, इमेज किंवा इतर आशय ॲप्स अ‍ॅक्सेस करतात तेव्हा, मेसेज दाखवा"</string>
<string name="show_password_title" msgid="2877269286984684659">"पासवर्ड दाखवा"</string>
<string name="show_password_summary" msgid="1110166488865981610">"तुम्ही टाइप कराल त्‍याप्रमाणे वर्ण थोडक्‍यात डिस्प्ले करा"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"हे अ‍ॅप तृतीय पक्षांसोबत <xliff:g id="PERMISSION_NAME">%s</xliff:g> डेटा शेअर करू शकते असे या अ‍ॅपने नमूद केले आहे"</string>
</resources>
diff --git a/PermissionController/res/values-ms/strings.xml b/PermissionController/res/values-ms/strings.xml
index 71df0dd2d..c0f7e1409 100644
--- a/PermissionController/res/values-ms/strings.xml
+++ b/PermissionController/res/values-ms/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Kekalkan “Semasa apl sedang digunakan”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Simpan “Kali ini sahaja”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Lagi maklumat"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Benarkan akses kepada semua foto"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Pilih foto"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Pilih lagi foto"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Jangan pilih lebih banyak foto"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Jangan benarkan juga"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Tolak"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> daripada <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 hari}other{# hari}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 jam}other{# jam}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 saat}other{# saat}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# hari}other{# hari}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# jam}other{# jam}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# saat}other{# saat}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Sebarang kebenaran"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Pada bila-bila masa"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"7 hari yang lalu"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"24 jam yang lalu"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Sejam yang lalu"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"15 minit yang lalu"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"1 minit terakhir"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{# hari yang lalu}other{# hari yang lalu}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{# jam yang lalu}other{# jam yang lalu}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{# minit yang lalu}other{# minit yang lalu}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Tiada penggunaan kebenaran"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Akses terbaharu pada bila-bila masa"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Akses terbaharu dalam 7 hari terakhir"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Penggunaan kebenaran dalam 1 jam terakhir"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Penggunaan kebenaran dalam 15 minit terakhir"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Penggunaan kebenaran dalam 1 minit terakhir"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Tidak digunakan dalam tempoh 24 jam yang lalu"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Tidak digunakan dalam tempoh 7 hari yang lalu"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Tidak digunakan dalam # hari yang lalu}other{Tidak digunakan dalam # hari yang lalu}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Tidak digunakan dalam # jam yang lalu}other{Tidak digunakan dalam # jam yang lalu}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Digunakan oleh 1 apl}other{Digunakan oleh # apl}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Lihat semua dalam Papan Pemuka"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Ditapis mengikut: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Benarkan akses kepada media sahaja"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Benarkan sepanjang masa"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Benarkan hanya semasa menggunakan apl"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Benarkan semua foto"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Benarkan foto dipilih"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Tanya setiap kali"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Jangan benarkan"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Lokasi tepat"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Tidak dibenarkan"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Lihat lagi apl yang boleh mengakses semua fail"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 hari}other{# hari}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 jam}other{# jam}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minit}other{# minit}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 saat}other{# saat}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# jam}other{# jam}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minit}other{# minit}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# saat}other{# saat}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Peringatan kebenaran"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 apl tidak digunakan"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> apl yang tidak digunakan"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Tetapkan <xliff:g id="APP_NAME">%1$s</xliff:g> sebagai apl navigasi lalai anda?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Kebenaran tidak diperlukan"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> akan dibenarkan berinteraksi dengan pemberitahuan anda dan mengakses kebenaran Telefon, SMS, Kenalan dan Kalendar anda."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> akan dibenarkan untuk berinteraksi dengan pemberitahuan anda dan mengakses kebenaran Telefon, SMS, Kenalan, Mikrofon dan Peranti berdekatan anda."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> akan dibenarkan untuk berinteraksi dengan pemberitahuan anda dan menstrim apl anda pada peranti tersambung."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> akan dibenarkan menstrim kandungan kepada peranti berdekatan."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Perkhidmatan ini berkongsi foto, media dan pemberitahuan anda daripada telefon anda ke peranti lain."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Lalai semasa"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Jangan tanya lagi"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Tetapkan sbg lalai"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Benarkan &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; mengakses &lt;b&gt;foto, video, muzik, audio dan fail lain&lt;/b&gt; pada peranti ini?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Benarkan &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; mengakses muzik dan audio pada peranti ini?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Benarkan &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; mengakses foto dan video pada peranti ini?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Beri &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; akses kepada lebih banyak foto?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Benarkan &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; merakam audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Apl hanya boleh merakam audio semasa anda menggunakan apl tersebut"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Benarkan &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; merakam audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Tunjukkan mesej apabila apl mengakses teks, imej atau kandungan lain yang telah anda salin"</string>
<string name="show_password_title" msgid="2877269286984684659">"Tunjukkan kata laluan"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Paparkan aksara seketika sambil anda menaip"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Apl ini menyatakan bahawa data <xliff:g id="PERMISSION_NAME">%s</xliff:g> mungkin dikongsikan dengan pihak ketiga"</string>
</resources>
diff --git a/PermissionController/res/values-my/strings.xml b/PermissionController/res/values-my/strings.xml
index 253af91d1..25de12f71 100644
--- a/PermissionController/res/values-my/strings.xml
+++ b/PermissionController/res/values-my/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"\"အက်ပ်ကို အသုံးပြုနေစဉ်\" ကို ဆက်ထားရန်"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"\"ဤတစ်ကြိမ်သာ\" ကို မှတ်ထားရန်"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"နောက်ထပ်"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"ဓာတ်ပုံအားလုံး သုံးခွင့်ပြုရန်"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ဓာတ်ပုံများရွေးရန်"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"နောက်ထပ်ဓာတ်ပုံများ ရွေးရန်"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"နောက်ထပ်ဓာတ်ပုံများ မရွေးပါနှင့်"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"မည်သို့ပင်ဖြစ်စေ ခွင့်မပြုပါ"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"ပယ်ရန်"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> ထဲမှ <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ရက်}other{# ရက်}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 နာရီ}other{# နာရီ}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{၁ မိနစ်}other{# မိနစ်}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{၁ စက္ကန့်}other{# စက္ကန့်}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ရက်}other{# ရက်}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# နာရီ}other{# နာရီ}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# မိနစ်}other{# မိနစ်}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# စက္ကန့်}other{# စက္ကန့်}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"မည်သည့် ခွင့်ပြုချက်မဆို"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"အချိန်မရွေး"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"ပြီးခဲ့သော ၇ ရက်"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"ပြီးခဲ့သော ၂၄ နာရီ"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"ပြီးခဲ့သော ၁ နာရီ"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"ပြီးခဲ့သော ၁၅ မိနစ်"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"ပြီးခဲ့သည့် ၁ မိနစ်"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{ပြီးခဲ့သော # ရက်}other{ပြီးခဲ့သော # ရက်}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{ပြီးခဲ့သော # နာရီ}other{ပြီးခဲ့သော # နာရီ}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{ပြီးခဲ့သော # မိနစ်}other{ပြီးခဲ့သော # မိနစ်}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"မည်သည့်ခွင့်ပြုချက်မှ မသုံးပါ"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"အချိန်မရွေး လတ်တလောအဖြစ်ဆုံး ဝင်သုံးမှု"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"ပြီးခဲ့သော ၇ ရက်အတွင်း လတ်တလော ဝင်သုံးမှု"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"ပြီးခဲ့သော ၁ မိနစ်အတွင်း ခွင့်ပြုချက်သုံးစွဲမှု"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"ပြီးခဲ့သော ၁၅ မိနစ်အတွင်း ခွင့်ပြုချက်သုံးစွဲမှု"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"ပြီးခဲ့သော ၁ မိနစ်အတွင်း ခွင့်ပြုချက်သုံးစွဲမှု"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"ပြီးခဲ့သည့် ၂၄ နာရီအတွင်း အသုံးမပြုပါ"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"ပြီးခဲ့သည့် ၇ ရက်အတွင်း အသုံးမပြုပါ"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{ပြီးခဲ့သော # ရက်အတွင်း သုံးမထားပါ}other{ပြီးခဲ့သော # ရက်အတွင်း သုံးမထားပါ}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{ပြီးခဲ့သော # နာရီအတွင်း သုံးမထားပါ}other{ပြီးခဲ့သော # နာရီအတွင်း သုံးမထားပါ}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{အက်ပ် ၁ ခုက အသုံးပြုထားသည်}other{အက်ပ် # ခုက အသုံးပြုထားသည်}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"အားလုံးကို \'ဒက်ရှ်ဘုတ်\' တွင်ကြည့်ရန်"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"စစ်ထုတ်စနစ်- <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"မီဒီယာကိုသာ သုံးခွင့်ပြုရန်"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"အမြဲ ခွင့်ပြုရန်"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"အက်ပ်ကိုသုံးစဉ်သာ ခွင့်ပြုရန်"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"ဓာတ်ပုံအားလုံး ခွင့်ပြုရန်"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"ရွေးထားသောဓာတ်ပုံများ ခွင့်ပြုရန်"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"အမြဲမေးရန်"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"ခွင့်မပြုပါ"</string>
<string name="precise_image_description" msgid="6349638632303619872">"နေရာအတိအကျ"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"ခွင့်ပြုမထားပါ"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"ဖိုင်အားလုံးသုံးနိုင်သည့် နောက်ထပ်အက်ပ်များ ကြည့်ရန်"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ရက်}other{# ရက်}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 နာရီ}other{# နာရီ}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{၁ မိနစ်}other{# မိနစ်}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{၁ စက္ကန့်}other{# စက္ကန့်}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# နာရီ}other{# နာရီ}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# မိနစ်}other{# မိနစ်}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# စက္ကန့်}other{# စက္ကန့်}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"ခွင့်ပြုချက် သတိပေးမှုများ"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"အသုံးမပြုသောအက်ပ် 1 ခု"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"အသုံးမပြုသောအက်ပ် <xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ခု"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> ကို မူရင်းလမ်းညွှန်အက်ပ်အဖြစ် သတ်မှတ်မလား။"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"ခွင့်ပြုချက် မလိုပါ"</string>
<string name="role_watch_description" msgid="267003778693177779">"သင်၏ ‘ဖုန်း’၊ ‘SMS စာတိုစနစ်’၊ ‘အဆက်အသွယ်များ’ နှင့် ‘ပြက္ခဒိန်’ ခွင့်ပြုချက်များကို သုံးရန်နှင့် အကြောင်းကြားချက်များကို ပြန်လှန်တုံ့ပြန်ရန် <xliff:g id="APP_NAME">%1$s</xliff:g> ကို ခွင့်ပြုပါမည်။"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"သင့်အကြောင်းကြားချက်များနှင့် ပြန်လှန်တုံ့ပြန်ရန်နှင့် သင့်ဖုန်း၊ SMS စာတိုစနစ်၊ အဆက်အသွယ်၊ မိုက်ခရိုဖုန်းနှင့် အနီးတစ်ဝိုက်ရှိ စက်များ၏ ခွင့်ပြုချက်များအား <xliff:g id="APP_NAME">%1$s</xliff:g> ကို သုံးခွင့်ပြုပါမည်။"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"သင့်အကြောင်းကြားချက်များကို ပြန်လှန်တုံ့ပြန်ရန်နှင့် သင့်အက်ပ်များကို ချိတ်ဆက်ထားသောစက်သို့ တိုက်ရိုက်လွှင့်ရန် <xliff:g id="APP_NAME">%1$s</xliff:g> ကို ခွင့်ပြုပါမည်။"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> ကို အနီးတစ်ဝိုက်ရှိ စက်များထံသို့ အကြောင်းအရာ တိုက်ရိုက်ထုတ်လွှင့်ခွင့်ပြုမည်။"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"ဤဝန်ဆောင်မှုသည် သင်၏ဖုန်းမှ ဓာတ်ပုံ၊ မီဒီယာနှင့် အကြောင်းကြားချက်များကို အခြားစက်ပစ္စည်းများသို့ မျှဝေသည်။"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"လက်ရှိ မူရင်း"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"ထပ်မမေးပါနှင့်"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"မူရင်း သတ်မှတ်ရန်"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"စက်ရှိ &lt;b&gt;ဓာတ်ပုံ၊ ဗီဒီယို၊ တေးဂီတ၊ အသံနှင့်အခြားဖိုင်များ&lt;/b&gt; ကို &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; အား သုံးခွင့်ပေးမလား။"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"ဤစက်ရှိ တေးဂီတနှင့် အသံဖိုင်ကို &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; အား သုံးခွင့်ပေးမလား။"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"ဤစက်ရှိ ဓာတ်ပုံနှင့် ဗီဒီယိုများကို &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; အား သုံးခွင့်ပေးမလား။"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ကို နောက်ထပ်ဓာတ်ပုံများ သုံးခွင့်ပေးမလား။"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ကို အသံဖမ်းယူခွင့် ပေးလိုပါသလား။"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"ဤအက်ပ်ကို အသုံးပြုနေသည့် အချိန်တွင်သာ ၎င်းက အသံဖမ်းနိုင်သည်။"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ကို အသံဖမ်းခွင့် ပေးလိုပါသလား။"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"အက်ပ်များက သင်မိတ္တူကူးထားသော စာသား၊ ပုံများ (သို့) အခြားအကြောင်းအရာကို သုံးသောအခါ အကြောင်းကြားပါ"</string>
<string name="show_password_title" msgid="2877269286984684659">"စကားဝှက်များပြရန်"</string>
<string name="show_password_summary" msgid="1110166488865981610">"စာရိုက်သည့်အခါ အက္ခရာများကို ခဏတာပြသည်"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"ဤအက်ပ်က ၎င်းသည် <xliff:g id="PERMISSION_NAME">%s</xliff:g> ဒေတာကို ပြင်ပအဖွဲ့များနှင့် မျှဝေနိုင်ကြောင်း ဖော်ပြထားသည်"</string>
</resources>
diff --git a/PermissionController/res/values-nb/strings.xml b/PermissionController/res/values-nb/strings.xml
index cd1123900..cd8742d07 100644
--- a/PermissionController/res/values-nb/strings.xml
+++ b/PermissionController/res/values-nb/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Behold «Mens appen er i bruk»"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Behold «Bare denne gangen»"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Mer info"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Gi tilgang til alle bilder"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Velg bilder"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Velg flere bilder"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Ikke velg flere bilder"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ikke tillat likevel"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Avvis"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> av <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dag}other{# dager}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 time}other{# timer}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sek}other{# sek}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dag}other{# dager}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# time}other{# timer}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sek}other{# sek}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Hvilken som helst tillatelse"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Når som helst"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"De siste 7 dagene"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"De siste 24 timene"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Den siste timen"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"De siste 15 minuttene"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Det siste minuttet"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Den siste dagen}other{De siste # dagene}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Den siste timen}other{De siste # timene}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Det siste minuttet}other{De siste # minuttene}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Ingen bruk av tillatelsen"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Siste tilgang når som helst"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Siste tilgang i løpet av de siste syv dagene"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Bruk av tillatelser den siste timen"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Bruk av tillatelser de siste 15 minuttene"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Bruk av tillatelser det siste minuttet"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Ikke brukt de siste 24 timene"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Ikke brukt i løpet av de siste 7 dagene"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Ikke brukt i løpet av den siste dagen}other{Ikke brukt i løpet av de siste # dagene}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Ikke brukt den siste timen}other{Ikke brukt de siste # timene}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Brukt av 1 app}other{Brukt av # apper}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Se alt i oversikten"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrert etter: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Har bare tilgang til medier"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Tillat hele tiden"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Bare tillat når appen brukes"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Tillat alle bilder"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Tillat valgte bilder"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Spør hver gang"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Ikke tillat"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Nøyaktig posisjon"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Ikke tillatt"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Se flere apper som kan bruke alle filer"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dag}other{# dager}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 time}other{# timer}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minutt}other{# minutter}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekund}other{# sekunder}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# time}other{# timer}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minutt}other{# minutter}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekund}other{# sekunder}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Påminnelser om tillatelser"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"Én ubrukt app"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ubrukte apper"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Vil du angi <xliff:g id="APP_NAME">%1$s</xliff:g> som standardapp for navigering?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Ingen tillatelser er nødvendige"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> får tillatelse til å samhandle med varslene dine og får tilgang til Telefon, SMS, kontakter og Kalender."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> får tillatelse til å samhandle med varslene dine og har tilgang til tillatelsene for telefon, SMS, kontakter, mikrofon og enheter i nærheten."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> får tillatelse til å samhandle med varslene dine og strømme appene dine til den tilkoblede enheten."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> får tillatelse til å strømme innhold til enheter i nærheten."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Denne tjenesten deler bildene, mediene og varslene dine fra telefonen din til andre enheter."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Gjeldende standard"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ikke spør igjen"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Angi som standard"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Vil du la &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; bruke &lt;b&gt;bilder, videoer, musikk, lyd og andre filer&lt;/b&gt; på denne enheten?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Vil du la &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; bruke musikk og lyd på denne enheten?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Vil du la &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; bruke bilder og videoer på denne enheten?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Vil du gi &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tilgang til flere bilder?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Vil du la &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ta opp lyd?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Appen kan bare ta opp lyd mens du bruker den."</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Vil du la &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ta opp lyd?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Vis en melding når apper bruker tekst, bilder eller annet innhold du har kopiert"</string>
<string name="show_password_title" msgid="2877269286984684659">"Vis passord"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Vis tegnene et øyeblikk mens du skriver"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Denne appen har oppgitt at den kan dele <xliff:g id="PERMISSION_NAME">%s</xliff:g>-data med tredjeparter"</string>
</resources>
diff --git a/PermissionController/res/values-ne/strings.xml b/PermissionController/res/values-ne/strings.xml
index b53f18fb6..96ba97fd3 100644
--- a/PermissionController/res/values-ne/strings.xml
+++ b/PermissionController/res/values-ne/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“एप प्रयोगमा भएको बेलामा” शीर्षक कायम राख्नुहोस्"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“यस बेला मात्र” राख्नुहोस्"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"थप जानकारी"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"सबै फोटो हेर्ने तथा प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"फोटोहरू चयन गर्नुहोस्"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"थप फोटोहरू चयन गर्नुहोस्"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"थप फोटोहरू चयन नगर्नुहोस्"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"जे भए पनि फेरि नसोध्नुहोस्"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"खारेज गर्नुहोस्"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> मध्ये <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{१ दिन}other{# दिन}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{१ घण्टा}other{# घण्टा}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{१ मिनेट}other{# मिनेट}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{१ सेकेन्ड}other{# सेकेन्ड}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# दिन}other{# दिन}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# घण्टा}other{# घण्टा}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# मिनेट}other{# मिनेट}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# सेकेन्ड}other{# सेकेन्ड}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"कुनै पनि अनुमति"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"कुनै पनि समय"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"पछिल्ला ७ दिन"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"पछिल्लो २४ घन्टा"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"पछिल्लो १ घन्टा"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"पछिल्लो १५ मिनेट"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"पछिल्लो १ मिनेट"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{पछिल्लो # दिन}other{पछिल्ला # दिन}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{पछिल्लो # घण्टा}other{पछिल्ला # घण्टा}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{पछिल्लो # मिनेट}other{पछिल्ला # मिनेट}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"उपयोगको अनुमति छैन"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"पछिल्लो पटक जुनसुकै बेला गरिएको पहुँच"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"गत ७ दिनमा पछिल्लो पटक गरिएको पहुँच"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"पछिल्लो १ घन्टामा गरिएको अनुमतिको प्रयोग"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"पछिल्लो १५ मिनेटमा गरिएको अनुमतिको प्रयोग"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"पछिल्लो १ मिनेटमा गरिएको अनुमतिको प्रयोग"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"पछिल्ला २४ घण्टामा प्रयोग गरिएको छैन"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"पछिल्ला ७ दिनमा प्रयोग गरिएको छैन"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{पछिल्लो # दिनमा प्रयोग गरिएको छैन}other{पछिल्ला # दिनमा प्रयोग गरिएको छैन}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{पछिल्लो # घण्टामा प्रयोग गरिएको छैन}other{पछिल्ला # घण्टामा प्रयोग गरिएको छैन}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{एउटा एप‌ले प्रयोग गरेको}other{# वटा एप‌ले प्रयोग गरेको}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ड्यासबोर्डमा सबै कुरा हेर्नुहोस्"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"यसअनुसार फिल्टर गरिएको: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"केवल मिडिया प्रयोग गर्ने अनुमति दिइयोस्"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"सधैँ अनुमति दिइयोस्"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"यो एप प्रयोग गरिरहेका बेला मात्र अनुमति दिइयोस्"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"सबै फोटो हेर्ने तथा प्रयोग गर्ने अनुमति दिनुहोस्"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"चयन गरिएका फोटोहरू हेर्ने तथा प्रयोग गर्ने अनुमति दिनुहोस्"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"प्रत्येक पटक सोधियोस्"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"अनुमति नदिइयोस्"</string>
<string name="precise_image_description" msgid="6349638632303619872">"सटीक स्थान"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"अनुमति नदिइएका"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"सबै फाइल हेर्ने र प्रयोग गर्ने अनुमति भएका थप एपहरू हेर्नुहोस्"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{१ दिन}other{# दिन}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{१ घण्टा}other{# घण्टा}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{१ मिनेट}other{# मिनेट}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{१ सेकेन्ड}other{# सेकेन्ड}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# घण्टा}other{# घण्टा}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# मिनेट}other{# मिनेट}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# सेकेन्ड}other{# सेकेन्ड}}"</string>
<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>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> लाई आफ्नो डिफल्ट नेभिगेसन एप बनाउने हो?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"कुनै पनि अनुमति चाहिन्न"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> लाई तपाईंका सूचना जाँच गर्ने र फोन, SMS, सम्पर्क ठेगाना तथा पात्रोका अनुमति हेर्ने तथा प्रयोग गर्ने अनुमति दिइने छ।"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> लाई तपाईंका सूचना हेर्ने र फोन, SMS, कन्ट्याक्ट, माइक्रोफोन तथा नजिकैका डिभाइससम्बन्धी अनुमतिहरू हेर्ने तथा प्रयोग गर्ने अनुमति दिइने छ।"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> लाई तपाईंका सूचना हेर्ने र कनेक्ट भएको डिभाइसमा तपाईंका एपहरू स्ट्रिम गर्ने अनुमति दिइने छ।"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> लाई नजिकैका डिभाइसहरूमा सामग्री स्ट्रिम गर्ने अनुमति दिइने छ।"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"तपाईं यो सेवाका सहायताले अन्य डिभाइससँग आफ्नो फोनमा भएका फोटो, मिडिया र सूचनाहरू सेयर गर्न सक्नुहुन्छ।"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"हालको डिफल्ट एप"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"फेरि नसोध्नुहोस्"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"डिफल्ट सेट गर्नुहोस्"</string>
@@ -450,7 +464,7 @@
<string name="assistant_record_audio_user_sensitive_title" msgid="5532123360322362378">"सहायक ट्रिगर भएको पत्ता लागेमा देखाउनुहोस्"</string>
<string name="assistant_record_audio_user_sensitive_summary" msgid="6482937591816401619">"आवाज सहायक सक्रिय गर्न माइक्रोफोनको प्रयोग गरिँदा स्टाटस बारमा आइकन देखाउनुहोस्"</string>
<string name="permgrouprequest_storage_isolated" msgid="4892154224026852295">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई आफ्नो डिभाइसका फोटो र मिडियामाथि पहुँच राख्न दिने हो?"</string>
- <string name="permgrouprequest_contacts" msgid="8391550064551053695">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई कन्ट्याक्ट प्रयोग गर्न दिने हो?"</string>
+ <string name="permgrouprequest_contacts" msgid="8391550064551053695">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई सम्पर्क ठेगानाको सूची प्रयोग गर्न दिने हो?"</string>
<string name="permgrouprequest_location" msgid="6990232580121067883">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;लाई यो डिभाइसको लोकेसन प्रयोग दिने हो?"</string>
<string name="permgrouprequestdetail_location" msgid="2635935335778429894">"तपाईंले एप प्रयोग गरिरहेका बेला मात्र उक्त एपले स्थानमाथि पहुँच राख्न सक्ने छ"</string>
<string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;लाई यो डिभाइसको लोकेसन प्रयोग दिने हो?"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई यो डिभाइसमा रहेका &lt;b&gt;फोटो, भिडियो, सङ्गीत, अडियो तथा अन्य फाइलहरू&lt;/b&gt; प्रयोग गर्न दिने हो?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई यस डिभाइसमा रहेका सङ्गीत तथा अन्य अडियो फाइलहरू प्रयोग गर्न दिने हो?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई यस डिभाइसमा रहेका फोटो र भिडियोहरू प्रयोग गर्न दिने हो?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई थप फोटोहरू हेर्ने तथा प्रयोग गर्ने अनुमति दिने हो?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई अडियो रेकर्ड गर्न दिने हो?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"तपाईंले यो एप प्रयोग गरिरहेका बेलामा मात्र यसले अडियो रेकर्ड गर्न सक्ने छ"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; लाई अडियो रेकर्ड गर्न दिने हो?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"मैले कपी गरेका टेक्स्ट, फोटो वा अन्य सामग्री एपहरूले प्रयोग गर्दा म्यासेज देखाइयोस्"</string>
<string name="show_password_title" msgid="2877269286984684659">"पासवर्डहरू देखाइयोस्"</string>
<string name="show_password_summary" msgid="1110166488865981610">"टाइप गर्दै गर्दा वर्णहरू झलक्क देखाइयोस्"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"यो एपले यसले <xliff:g id="PERMISSION_NAME">%s</xliff:g> को डेटा तेस्रो पक्षसँग सेयर गर्न सक्छ भन्ने जानकारी दिएको छ"</string>
</resources>
diff --git a/PermissionController/res/values-night/themes.xml b/PermissionController/res/values-night/themes.xml
index b4257ab4e..7ac9b190b 100644
--- a/PermissionController/res/values-night/themes.xml
+++ b/PermissionController/res/values-night/themes.xml
@@ -40,5 +40,5 @@
<item name="android:background">@color/divider_color_secondary</item>
</style>
- <style name="Theme.DeviceDefault.Dialog.Alert.DayNight" parent="@android:style/Theme.DeviceDefault.Dialog.Alert" />
+ <style name="Theme.DeviceDefault.Dialog.NoActionBar.DayNight" parent="@android:style/Theme.DeviceDefault.Dialog.NoActionBar" />
</resources>
diff --git a/PermissionController/res/values-nl/strings.xml b/PermissionController/res/values-nl/strings.xml
index 2ec869034..4ca4ae4b5 100644
--- a/PermissionController/res/values-nl/strings.xml
+++ b/PermissionController/res/values-nl/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Instelling \'Terwijl de app wordt gebruikt\' behouden"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"\'Alleen deze keer\' behouden"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Meer informatie"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Toegang geven tot alle foto\'s"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Foto\'s selecteren"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Meer foto\'s selecteren"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Geen foto\'s meer selecteren"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Toch niet toestaan"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Sluiten"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> van <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dag}other{# dagen}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 uur}other{# uur}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sec}other{# sec}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dag}other{# dagen}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# uur}other{# uur}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Minstens 1 recht"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Altijd"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Afgelopen 7 dagen"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Afgelopen 24 uur"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Afgelopen uur"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Afgelopen 15 minuten"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Afgelopen minuut"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Afgelopen dag}other{Afgelopen # dagen}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Afgelopen uur}other{Afgelopen # uur}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Afgelopen minuut}other{Afgelopen # minuten}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Geen rechtengebruik"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Laatste toegang"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Laatste toegang in de afgelopen zeven dagen"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Rechtengebruik in het afgelopen uur"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Rechtengebruik in de afgelopen 15 minuten"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Rechtengebruik in de afgelopen minuut"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Niet gebruikt in de afgelopen 24 uur"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Niet gebruikt in de afgelopen 7 dagen"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Niet gebruikt in de afgelopen dag}other{Niet gebruikt in de afgelopen # dagen}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Niet gebruikt in het afgelopen uur}other{Niet gebruikt in de afgelopen # uur}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Gebruikt door 1 app}other{Gebruikt door # apps}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Alles op dashboard tonen"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Gefilterd op: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Alleen toegang tot media toestaan"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Altijd toestaan"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Toestaan bij gebruik van app"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Alle foto\'s toestaan"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Geselecteerde foto\'s toestaan"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Altijd vragen"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Niet toestaan"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Exacte locatie"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Niet toegestaan"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Bekijk meer apps die toegang tot alle bestanden hebben."</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dag}other{# dagen}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 uur}other{# uur}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuut}other{# minuten}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 seconde}other{# seconden}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# uur}other{# uur}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuut}other{# minuten}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# seconde}other{# seconden}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Herinneringen voor rechten"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 niet-gebruikte app"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> niet-gebruikte apps"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> instellen als je standaard navigatie-app?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Geen rechten nodig"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> kan interactie hebben met je meldingen en toegang krijgen tot je rechten voor telefoon, sms, contacten en agenda."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> mag interactie hebben met je meldingen en krijgt toegang tot de rechten Telefoon, Sms, Contacten, Microfoon en Apparaten in de buurt."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> mag interactie hebben met je meldingen en je apps streamen naar het verbonden apparaat."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> krijgt toestemming om content te streamen naar apparaten in de buurt."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Deze service deelt foto\'s, media en meldingen van je telefoon met andere apparaten."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Huidige standaard-app"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Niet meer vragen"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Standaard instellen"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang geven tot &lt;b&gt;foto\'s, video\'s, muziek, audio en andere bestanden&lt;/b&gt; op dit apparaat?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang geven tot muziek en audio op dit apparaat?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang geven tot foto\'s en video\'s op dit apparaat?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toegang geven tot meer foto\'s?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toestaan om audio op te nemen?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Deze app kan alleen audio opnemen als je de app gebruikt"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; toestaan om audio op te nemen?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Toon een bericht als apps toegang hebben tot tekst, afbeeldingen of andere content die je hebt gekopieerd"</string>
<string name="show_password_title" msgid="2877269286984684659">"Wachtwoorden tonen"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Tekens kort tonen terwijl je typt"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Deze app heeft vermeld dat deze <xliff:g id="PERMISSION_NAME">%s</xliff:g>-gegevens kan delen met derden."</string>
</resources>
diff --git a/PermissionController/res/values-or/strings.xml b/PermissionController/res/values-or/strings.xml
index 0eda28e08..0fc62fdf2 100644
--- a/PermissionController/res/values-or/strings.xml
+++ b/PermissionController/res/values-or/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“ଆପଟି ବ୍ୟବହାରରେ ଥିବା ସମୟରେ”କୁ ରଖନ୍ତୁ"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“କେବଳ ଏହି ସମୟ” ରଖନ୍ତୁ"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"ଅଧିକ ସୂଚନା"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"ସମସ୍ତ ଫଟୋକୁ ଆକ୍ସେସ କରିବାର ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ଫଟୋଗୁଡ଼ିକୁ ଚୟନ କରନ୍ତୁ"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"ଅଧିକ ଫଟୋ ଚୟନ କରନ୍ତୁ"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"ଅଧିକ ଫଟୋ ଚୟନ କରନ୍ତୁ ନାହିଁ"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"ଯେ କୌଣସି ମତେ ଅନୁମତି ଦିଅ ନାହିଁ"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"ଖାରଜ କରନ୍ତୁ"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> ରୁ <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ଦିନ}other{# ଦିନ}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ଘଣ୍ଟା}other{# ଘଣ୍ଟା}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 ମିନିଟ}other{# ମିନିଟ}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 ସେକେଣ୍ଡ}other{# ସେକେଣ୍ଡ}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ଦିନ}other{# ଦିନ}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ଘଣ୍ଟା}other{# ଘଣ୍ଟା}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# ମିନିଟ}other{# ମିନିଟ}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# ସେକେଣ୍ଡ}other{# ସେକେଣ୍ଡ}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ଯେକୌଣସି ଅନୁମତି"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ଯେକୌଣସି ସମୟରେ"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"ଗତ 7 ଦିନ"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"ଗତ 24 ଘଣ୍ଟା"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"ଗତ 1 ଘଣ୍ଟା"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"ଗତ 15 ମିନିଟ୍"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"ଗତ 1 ମିନିଟ୍‌ରେ"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{ଗତ # ଦିନ}other{ଗତ # ଦିନ}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{ଗତ # ଘଣ୍ଟା}other{ଗତ # ଘଣ୍ଟା}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{ଗତ # ମିନିଟ}other{ଗତ # ମିନିଟ}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"ବ୍ୟବହାର ପାଇଁ କୌଣସି ଅନୁମତି ନାହିଁ"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"ଯେକୌଣସି ସମୟରେ କରାଯାଇଥିବା ସମ୍ପ୍ରତ୍ତି ଆକ୍ସେସ୍‍"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"ଗତ 7 ଦିନରେ ଅତି ସମ୍ପ୍ରତ୍ତି ହୋଇଥିବା ଆକ୍ସେସ୍‍"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"ଗତ 1 ଘଣ୍ଟାରେ ବ୍ୟବହୃତ ଅନୁମତି"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"ଗତ 15 ମିନିଟ୍‌ରେ ବ୍ୟବହୃତ ହୋଇଥିବା ଅନୁମତି"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"ଗତ 1 ମିନିଟ୍‌ରେ ବ୍ୟବହୃତ ହୋଇଥିବା ଅନୁମତି"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"ଗତ 24 ଘଣ୍ଟାରେ ବ୍ୟବହାର କରାଯାଇନାହିଁ"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"ଗତ 7 ଦିନରେ ବ୍ୟବହାର କରାଯାଇନାହିଁ"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{ଗତ # ଦିନରେ ବ୍ୟବହାର କରାଯାଇନାହିଁ}other{ଗତ # ଦିନରେ ବ୍ୟବହାର କରାଯାଇନାହିଁ}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{ଗତ # ଘଣ୍ଟାରେ ବ୍ୟବହାର କରାଯାଇନାହିଁ}other{ଗତ # ଘଣ୍ଟାରେ ବ୍ୟବହାର କରାଯାଇନାହିଁ}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1ଟି ଆପ ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଇଛି}other{#ଟି ଆପ ଦ୍ୱାରା ବ୍ୟବହାର କରାଯାଇଛି}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ଡ୍ୟାସ୍‍‍ବୋର୍ଡରେ ସବୁ ଦେଖନ୍ତୁ"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"<xliff:g id="PERM">%1$s</xliff:g> ଦ୍ବାରା ଫିଲ୍ଟର୍ କରାଯାଇଛି"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"କେବଳ ମିଡିଆକୁ ଆକ୍ସେସ୍ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"ସବୁ ସମୟ ପାଇଁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"ଆପ୍ ବ୍ୟବହାର ବେଳେ କେବଳ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"ସମସ୍ତ ଫଟୋକୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"ଚୟନିତ ଫଟୋଗୁଡ଼ିକୁ ଅନୁମତି ଦିଅନ୍ତୁ"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"ପ୍ରତ୍ୟେକ ଥର ପଚାରନ୍ତୁ"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"ଅନୁମତି ଦିଅନ୍ତୁ ନାହିଁ"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ସଠିକ୍ ଲୋକେସନ୍"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"ଅନୁମତି ନାହିଁ"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"ସମସ୍ତ ଫାଇଲକୁ ଆକ୍ସେସ କରିପାରୁଥିବା ଅଧିକ ଆପ୍ସ ଦେଖନ୍ତୁ"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ଦିନ}other{# ଦିନ}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ଘଣ୍ଟା}other{# ଘଣ୍ଟା}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 ମିନିଟ}other{# ମିନିଟ}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 ସେକେଣ୍ଡ}other{# ସେକେଣ୍ଡ}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ଘଣ୍ଟା}other{# ଘଣ୍ଟା}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# ମିନିଟ}other{# ମିନିଟ}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# ସେକେଣ୍ଡ}other{# ସେକେଣ୍ଡ}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"ଅନୁମତି ରିମାଇଣ୍ଡର୍"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1ଟି ଅବ୍ୟବହୃତ ଆପ୍"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g>ଟି ଅବ୍ୟବହୃତ ଆପ୍"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g>କୁ ଆପଣଙ୍କ ଡିଫଲ୍ଟ ନାଭିଗେସନ ଆପ ଭାବେ ସେଟ କରିବେ?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"କୌଣସି ଅନୁମତି ଆବଶ୍ୟକ ନାହିଁ"</string>
<string name="role_watch_description" msgid="267003778693177779">"ଆପଣଙ୍କ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରିବା ଏବଂ ଆପଣଙ୍କ ଫୋନ୍, SMS, ଯୋଗାଯୋଗ ଓ କ୍ୟାଲେଣ୍ଡର ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ୍ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ।"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"ଆପଣଙ୍କ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରିବା ଏବଂ ଆପଣଙ୍କର ଫୋନ, SMS, କଣ୍ଟାକ୍ଟ, ମାଇକ୍ରୋଫୋନ ଓ ଆଖପାଖର ଡିଭାଇସ ଅନୁମତିଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ।"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"ଆପଣଙ୍କ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକ ସହ ଇଣ୍ଟରାକ୍ଟ କରିବା ଏବଂ ସଂଯୋଗ କରାଯାଇଥିବା ଡିଭାଇସଗୁଡ଼ିକରେ ଆପଣଙ୍କର ଆପଗୁଡ଼ିକ ଷ୍ଟ୍ରିମ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ।"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"ଆଖପାଖର ଡିଭାଇସଗୁଡ଼ିକରେ ବିଷୟବସ୍ତୁ ଷ୍ଟ୍ରିମ କରିବା ପାଇଁ <xliff:g id="APP_NAME">%1$s</xliff:g>କୁ ଅନୁମତି ଦିଆଯିବ।"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"ଏହି ସେବା ଆପଣଙ୍କ ଫୋନରୁ ଆପଣଙ୍କ ଫଟୋ, ମିଡିଆ ଏବଂ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକୁ ଅନ୍ୟ ଡିଭାଇସଗୁଡ଼ିକ ସହ ସେୟାର କରେ।"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"ସମ୍ପ୍ରତ୍ତି ଡିଫଲ୍ଟ"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"ଆଉ ପଚାରନ୍ତୁ ନାହିଁ"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ଡିଫଲ୍ଟ ଭାବେ ସେଟ୍ କରନ୍ତୁ"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"ଏହି ଡିଭାଇସରେ ଥିବା&lt;b&gt;ଫଟୋ, ଭିଡିଓ, ମ୍ୟୁଜିକ, ଅଡିଓ ଓ ଅନ୍ୟ ଫାଇଲ&lt;/b&gt; ଆକ୍ସେସ କରିବା ପାଇଁ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"ଏହି ଡିଭାଇସରେ ଥିବା ମ୍ୟୁଜିକ ଏବଂ ଅଡିଓକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"ଏହି ଡିଭାଇସରେ ଥିବା ଫଟୋ ଏବଂ ଭିଡିଓଗୁଡ଼ିକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"ଅଧିକ ଫଟୋକୁ ଆକ୍ସେସ କରିବା ପାଇଁ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;କୁ ଅନୁମତି ଦେବେ?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;କୁ ଅଡିଓ ରେକର୍ଡ କରିବା ପାଇଁ ଅନୁମତି ଦେବେ କି?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"ଆପଣ ଆପକୁ ବ୍ୟବହାର କରୁଥିବା ସମୟରେ କେବଳ ଏହା ଅଡିଓ ରେକର୍ଡ କରିବାକୁ ସକ୍ଷମ ହେବ"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;କୁ ଅଡିଓ ରେକର୍ଡ କରିବାକୁ ଅନୁମତି ଦେବେ କି?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"ଯେତେବେଳେ ଆପଣ କପି କରିଥିବା ଟେକ୍ସଟ, ଇମେଜ କିମ୍ବା ଅନ୍ୟ ବିଷୟବସ୍ତୁକୁ ଆପ୍ସ ଆକ୍ସେସ କରେ, ସେତେବେଳେ ଏକ ମେସେଜ ଦେଖାନ୍ତୁ"</string>
<string name="show_password_title" msgid="2877269286984684659">"ପାସୱାର୍ଡଗୁଡ଼ିକ ଦେଖାନ୍ତୁ"</string>
<string name="show_password_summary" msgid="1110166488865981610">"ଆପଣ ଟାଇପ କରିବା ସମୟରେ କେରେକ୍ଟରଗୁଡ଼ିକୁ କିଛି ସମୟ ପାଇଁ ଡିସପ୍ଲେ କରନ୍ତୁ"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"ଏହି ଆପ ଉଲ୍ଲେଖ କରିଛି ଯେ ଏହା ତୃତୀୟ ପକ୍ଷଗୁଡ଼ିକ ସହ <xliff:g id="PERMISSION_NAME">%s</xliff:g> ଡାଟା ସେୟାର କରିପାରେ"</string>
</resources>
diff --git a/PermissionController/res/values-pa/strings.xml b/PermissionController/res/values-pa/strings.xml
index 60318c9a2..03f743e16 100644
--- a/PermissionController/res/values-pa/strings.xml
+++ b/PermissionController/res/values-pa/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“ਐਪ ਵਰਤੋਂ ਵਿੱਚ ਹੋਣ ਵੇਲੇ” ਨੂੰ ਰੱਖੋ"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“ਸਿਰਫ਼ ਇਸ ਸਮੇਂ ਲਈ ਇਜਾਜ਼ਤ ਦਿਓ” ਰੱਖੋ"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"ਹੋਰ ਜਾਣਕਾਰੀ"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"ਸਾਰੀਆਂ ਫ਼ੋਟੋਆਂ ਤੱਕ ਪਹੁੰਚ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ਫ਼ੋਟੋਆਂ ਚੁਣੋ"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"ਹੋਰ ਫ਼ੋਟੋਆਂ ਚੁਣੋ"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"ਹੋਰ ਫ਼ੋਟੋਆਂ ਨਾ ਚੁਣੋ"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"ਫਿਰ ਵੀ ਆਗਿਆ ਨਾ ਦਿਓ"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"ਖਾਰਜ ਕਰੋ"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> ਵਿੱਚੋਂ <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ਦਿਨ}one{# ਦਿਨ}other{# ਦਿਨ}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ਘੰਟਾ}one{# ਘੰਟਾ}other{# ਘੰਟੇ}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 ਮਿੰਟ}one{# ਮਿੰਟ}other{# ਮਿੰਟ}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 ਸਕਿੰਟ}one{# ਸਕਿੰਟ}other{# ਸਕਿੰਟ}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ਦਿਨ}one{# ਦਿਨ}other{# ਦਿਨ}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ਘੰਟਾ}one{# ਘੰਟਾ}other{# ਘੰਟੇ}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# ਮਿੰਟ}one{# ਮਿੰਟ}other{# ਮਿੰਟ}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# ਸਕਿੰਟ}one{# ਸਕਿੰਟ}other{# ਸਕਿੰਟ}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ਕੋਈ ਵੀ ਇਜਾਜ਼ਤ"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ਕਿਸੇ ਵੀ ਵੇਲੇ"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"ਪਿਛਲੇ 7 ਦਿਨ"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"ਪਿਛਲੇ 24 ਘੰਟੇ"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"ਪਿਛਲਾ 1 ਘੰਟਾ"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"ਪਿਛਲੇ 15 ਮਿੰਟ"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"ਪਿਛਲੇ 1 ਮਿੰਟ"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{ਪਿਛਲਾ # ਦਿਨ}one{ਪਿਛਲਾ # ਦਿਨ}other{ਪਿਛਲੇ # ਦਿਨ}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{ਪਿਛਲਾ # ਘੰਟਾ}one{ਪਿਛਲਾ # ਘੰਟਾ}other{ਪਿਛਲੇ # ਘੰਟੇ}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{ਪਿਛਲਾ # ਮਿੰਟ}one{ਪਿਛਲਾ # ਮਿੰਟ}other{ਪਿਛਲੇ # ਮਿੰਟ}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"ਕੋਈ ਇਜਾਜ਼ਤ ਨਹੀਂ ਵਰਤੀ ਗਈ"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"ਕਿਸੇ ਵੀ ਵੇਲੇ ਦੀ ਸਭ ਤੋਂ ਹਾਲੀਆ ਪਹੁੰਚ"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"ਪਿਛਲੇ 7 ਦਿਨਾਂ ਵਿੱਚ ਸਭ ਤੋਂ ਹਾਲੀਆ ਪਹੁੰਚ"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"ਪਿਛਲੇ 1 ਘੰਟੇ ਵਿੱਚ ਇਜਾਜ਼ਤ ਵਰਤੋਂ"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"ਪਿਛਲੇ 15 ਮਿੰਟ ਵਿੱਚ ਇਜਾਜ਼ਤ ਵਰਤੋਂ"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"ਪਿਛਲੇ 1 ਮਿੰਟ ਵਿੱਚ ਇਜਾਜ਼ਤ ਵਰਤੋਂ"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"ਪਿਛਲੇ 24 ਘੰਟਿਆਂ ਵਿੱਚ ਵਰਤੀਆਂ ਨਹੀਂ ਗਈਆਂ"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"ਪਿਛਲੇ 7 ਦਿਨਾਂ ਵਿੱਚ ਵਰਤੀਆਂ ਨਹੀਂ ਗਈਆਂ"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{ਪਿਛਲੇ # ਦਿਨ ਵਿੱਚ ਨਹੀਂ ਵਰਤੀ ਗਈ}one{ਪਿਛਲੇ # ਦਿਨ ਵਿੱਚ ਨਹੀਂ ਵਰਤੀ ਗਈ}other{ਪਿਛਲੇ # ਦਿਨਾਂ ਵਿੱਚ ਨਹੀਂ ਵਰਤੀ ਗਈ}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{ਪਿਛਲੇ # ਘੰਟੇ ਵਿੱਚ ਨਹੀਂ ਵਰਤੀ ਗਈ}one{ਪਿਛਲੇ # ਘੰਟੇ ਵਿੱਚ ਨਹੀਂ ਵਰਤੀ ਗਈ}other{ਪਿਛਲੇ # ਘੰਟਿਆਂ ਵਿੱਚ ਨਹੀਂ ਵਰਤੀ ਗਈ}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 ਐਪ ਵੱਲੋਂ ਇਜਾਜ਼ਤਾਂ ਵਰਤੀਆਂ ਗਈਆਂ}one{# ਐਪ ਵੱਲੋਂ ਇਜਾਜ਼ਤਾਂ ਵਰਤੀਆਂ ਗਈਆਂ}other{# ਐਪਾਂ ਵੱਲੋਂ ਇਜਾਜ਼ਤਾਂ ਵਰਤੀਆਂ ਗਈਆਂ}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ਸਭ ਡੈਸ਼ਬੋਰਡ ਵਿੱਚ ਦੇਖੋ"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"ਇਸ ਮੁਤਾਬਕ ਫਿਲਟਰ ਕੀਤਾ ਗਿਆ: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"ਸਿਰਫ਼ ਮੀਡੀਆ ਲਈ ਪਹੁੰਚ ਕਰਨ ਦਿਓ"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"ਹਰ ਵੇਲੇ ਕਰਨ ਦਿਓ"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"ਸਿਰਫ਼ ਐਪ ਵਰਤੇ ਜਾਣ ਵੇਲੇ ਕਰਨ ਦਿਓ"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"ਸਾਰੀਆਂ ਫ਼ੋਟੋਆਂ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"ਚੋਣਵੀਆਂ ਫ਼ੋਟੋਆਂ ਦੀ ਆਗਿਆ ਦਿਓ"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"ਹਰ ਵਾਰ ਪੁੱਛੋ"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"ਨਾ ਕਰਨ ਦਿਓ"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ਸਹੀ ਟਿਕਾਣਾ"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"ਗੈਰ-ਮਨਜ਼ੂਰਸ਼ੁਦਾ"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"ਸਾਰੀਆਂ ਫ਼ਾਈਲਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰ ਸਕਣ ਵਾਲੀਆਂ ਹੋਰ ਐਪਾਂ ਦੇਖੋ"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ਦਿਨ}one{# ਦਿਨ}other{# ਦਿਨ}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ਘੰਟਾ}one{# ਘੰਟਾ}other{# ਘੰਟੇ}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 ਮਿੰਟ}one{# ਮਿੰਟ}other{# ਮਿੰਟ}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 ਸਕਿੰਟ}one{# ਸਕਿੰਟ}other{# ਸਕਿੰਟ}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ਘੰਟਾ}one{# ਘੰਟਾ}other{# ਘੰਟੇ}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# ਮਿੰਟ}one{# ਮਿੰਟ}other{# ਮਿੰਟ}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# ਸਕਿੰਟ}one{# ਸਕਿੰਟ}other{# ਸਕਿੰਟ}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"ਇਜਾਜ਼ਤਾਂ ਦੀਆਂ ਯਾਦ-ਸੂਚਨਾਵਾਂ"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ਅਣਵਰਤੀ ਐਪ"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ਅਣਵਰਤੀਆਂ ਐਪਾਂ"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"ਕੀ <xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਤੁਹਾਡੀ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਨੈਵੀਗੇਸ਼ਨ ਐਪ ਵਜੋਂ ਸੈੱਟ ਕਰਨਾ ਹੈ?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"ਕਿਸੇ ਇਜਾਜ਼ਤ ਦੀ ਲੋੜ ਨਹੀਂ ਹੈ"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਤੁਹਾਡੀਆਂ ਸੂਚਨਾਵਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰਨ ਅਤੇ ਤੁਹਾਡੇ ਫ਼ੋਨ, SMS, ਸੰਪਰਕ ਅਤੇ ਕੈਲੰਡਰ ਦੀਆਂ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਹੋਵੇਗੀ।"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਤੁਹਾਡੀਆਂ ਸੂਚਨਾਵਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰਨ ਅਤੇ ਤੁਹਾਡੇ ਫ਼ੋਨ, SMS, ਸੰਪਰਕਾਂ, ਮਾਈਕ੍ਰੋਫ਼ੋਨ ਅਤੇ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ ਸੰਬੰਧੀ ਇਜਾਜ਼ਤਾਂ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ।"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਤੁਹਾਡੀਆਂ ਸੂਚਨਾਵਾਂ ਨਾਲ ਅੰਤਰਕਿਰਿਆ ਕਰਨ ਅਤੇ ਤੁਹਾਡੀਆਂ ਐਪਾਂ ਨੂੰ ਕਨੈਕਟ ਕੀਤੇ ਡੀਵਾਈਸ \'ਤੇ ਸਟ੍ਰੀਮ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ।"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> ਨੂੰ ਨਜ਼ਦੀਕੀ ਡੀਵਾਈਸਾਂ ਦੀ ਸਮੱਗਰੀ ਨੂੰ ਸਟ੍ਰੀਮ ਕਰਨ ਦੀ ਆਗਿਆ ਹੋਵੇਗੀ।"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"ਇਹ ਸੇਵਾ ਤੁਹਾਡੀਆਂ ਫ਼ੋਟੋਆਂ, ਮੀਡੀਆ ਅਤੇ ਸੂਚਨਾਵਾਂ ਨੂੰ ਤੁਹਾਡੇ ਫ਼ੋਨ ਤੋਂ ਹੋਰ ਡੀਵਾਈਸਾਂ ਨਾਲ ਸਾਂਝਾ ਕਰਦੀ ਹੈ।"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"ਮੌਜੂਦਾ ਪੂਰਵ-ਨਿਰਧਾਰਤ"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"ਦੁਬਾਰਾ ਨਾ ਪੁੱਛੋ"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ਪੂਰਵ-ਨਿਰਧਾਰਤ ਵਜੋਂ ਸੈੱਟ ਕਰੋ"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ ਇਸ ਡੀਵਾਈਸ \'ਤੇ &lt;b&gt;ਫ਼ੋਟੋਆਂ, ਵੀਡੀਓ, ਸੰਗੀਤ, ਆਡੀਓ ਅਤੇ ਹੋਰ ਫ਼ਾਈਲਾਂ&lt;/b&gt; ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੇਣੀ ਹੈ?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਸੰਗੀਤ ਅਤੇ ਆਡੀਓ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੇਣੀ ਹੈ?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ ਇਸ ਡੀਵਾਈਸ \'ਤੇ ਫ਼ੋਟੋਆਂ ਅਤੇ ਵੀਡੀਓ ਤੱਕ ਪਹੁੰਚ ਕਰਨ ਦੇਣੀ ਹੈ?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ ਹੋਰ ਫ਼ੋਟੋਆਂ ਤੱਕ ਪਹੁੰਚ ਦੇਣੀ ਹੈ?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰਨ ਦੇਣੀ ਹੈ?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"ਤੁਹਾਡੇ ਵੱਲੋਂ ਐਪ ਦੀ ਵਰਤੋਂ ਕਰਨ ਵੇਲੇ ਹੀ ਐਪ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰ ਸਕੇਗੀ"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"ਕੀ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ਨੂੰ ਆਡੀਓ ਰਿਕਾਰਡ ਕਰਨ ਦੀ ਇਜਾਜ਼ਤ ਦੇਣੀ ਹੈ?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"ਜਦੋਂ ਐਪਾਂ ਲਿਖਤ, ਚਿੱਤਰ ਜਾਂ ਤੁਹਾਡੇ ਵੱਲੋਂ ਕਾਪੀ ਕੀਤੀ ਹੋਰ ਸਮੱਗਰੀ ਤੱਕ ਪਹੁੰਚ ਕਰਦੀਆਂ ਹਨ, ਤਾਂ ਕੋਈ ਸੁਨੇਹਾ ਦਿਖਾਓ"</string>
<string name="show_password_title" msgid="2877269286984684659">"ਪਾਸਵਰਡ ਦਿਖਾਓ"</string>
<string name="show_password_summary" msgid="1110166488865981610">"ਟਾਈਪ ਕਰਨ ਵੇਲੇ ਅੱਖਰ-ਚਿੰਨ੍ਹਾਂ ਨੂੰ ਥੋੜ੍ਹੇ ਸਮੇਂ ਲਈ ਦਿਖਾਓ"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"ਇਸ ਐਪ ਨੇ ਸਪਸ਼ਟ ਕੀਤਾ ਕਿ ਇਹ ਤੀਜੀਆਂ ਧਿਰਾਂ ਨਾਲ <xliff:g id="PERMISSION_NAME">%s</xliff:g> ਡਾਟੇ ਨੂੰ ਸਾਂਝਾ ਕਰ ਸਕਦੀ ਹੈ"</string>
</resources>
diff --git a/PermissionController/res/values-pl/strings.xml b/PermissionController/res/values-pl/strings.xml
index 07a85de06..c64aa7def 100644
--- a/PermissionController/res/values-pl/strings.xml
+++ b/PermissionController/res/values-pl/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Zachowaj „Podczas używania aplikacji”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Zachowaj „Tylko tym razem”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Więcej"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Zezwól na dostęp do wszystkich zdjęć"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Wybierz zdjęcia"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Wybierz więcej zdjęć"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Nie wybieraj kolejnych zdjęć"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"I tak nie zezwalaj"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Odrzuć"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> z <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dzień}few{# dni}many{# dni}other{# dnia}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 godzina}few{# godziny}many{# godzin}other{# godziny}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}few{# min}many{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}few{# s}many{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dzień}few{# dni}many{# dni}other{# dnia}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# godzina}few{# godziny}many{# godzin}other{# godziny}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}few{# min}many{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}few{# s}many{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Dowolne uprawnienie"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Dowolny czas"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Ostatnie 7 dni"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Ostatnie 24 godziny"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Ostatnia godzina"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Ostatnie 15 minut"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Ostatnia minuta"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Ostatni dzień}few{Ostatnie # dni}many{Ostatnie # dni}other{Ostatnie # dnia}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Ostatnia godzina}few{Ostatnie # godz.}many{Ostatnie # godz.}other{Ostatnie # godz.}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Ostatnia minuta}few{Ostatnie # minuty}many{Ostatnie # minut}other{Ostatnie # minuty}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Brak użycia uprawnień"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Ostatni dostęp w dowolnym okresie"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Najnowsze użycie w ciągu ostatnich 7 dni"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Użycie uprawnień w ciągu ostatniej godziny"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Użycie uprawnień w ciągu ostatnich 15 minut"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Użycie uprawnień w ciągu ostatniej minuty"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Nie używano w ciągu ostatnich 24 godzin"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Nie używano w ciągu ostatnich 7 dni"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Nie używano w ciągu ostatniego dnia}few{Nie używano w ciągu ostatnich # dni}many{Nie używano w ciągu ostatnich # dni}other{Nie używano w ciągu ostatniej # dnia}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Nie używano w ciągu ostatniej godziny}few{Nie używano w ciągu ostatnich # godzin}many{Nie używano w ciągu ostatnich # godzin}other{Nie używano w ciągu ostatnich # godzin}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Uprawnienie używane przez 1 aplikację}few{Uprawnienie używane przez # aplikacje}many{Uprawnienie używane przez # aplikacji}other{Uprawnienie używane przez # aplikacji}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Pokaż wszystko w panelu"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrowane według: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Pozwól na dostęp tylko do multimediów"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Zawsze zezwalaj"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Zezwalaj tylko podczas używania aplikacji"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Zezwól na wszystkie zdjęcia"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Zezwól na wybrane zdjęcia"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Zawsze pytaj"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Nie zezwalaj"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Dokładna lokalizacja"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nie mają dostępu"</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>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 godzina}few{# godziny}many{# godzin}other{# godziny}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuta}few{# minuty}many{# minut}other{# minuty}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekunda}few{# sekundy}many{# sekund}other{# sekundy}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# godzina}few{# godziny}many{# godzin}other{# godziny}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuta}few{# minuty}many{# minut}other{# minuty}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekunda}few{# sekundy}many{# sekund}other{# sekundy}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Przypomnienia o uprawnieniach"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 nieużywana aplikacja"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Nieużywane aplikacje (<xliff:g id="NUMBER_OF_APPS">%s</xliff:g>)"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Czy aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> ma być domyślną aplikacją do nawigacji?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nie potrzebuje uprawnień"</string>
<string name="role_watch_description" msgid="267003778693177779">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> będzie mogła korzystać z powiadomień oraz uprawnień dotyczących Telefonu, SMS-ów, Kontaktów i Kalendarza."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> będzie mogła wchodzić w interakcję z powiadomieniami i korzystać z uprawnień dotyczących telefonu, SMS-ów, kontaktów, mikrofonu oraz urządzeń w pobliżu."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> będzie mogła korzystać z powiadomień oraz odtwarzać strumieniowo dane z aplikacji na połączonych urządzeniach."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Aplikacja <xliff:g id="APP_NAME">%1$s</xliff:g> będzie mogła odtwarzać strumieniowo treści na urządzeniach w pobliżu."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ta usługa udostępnia Twoje zdjęcia, multimedia i powiadomienia z telefonu innym urządzeniom."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Bieżąca aplikacja domyślna"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Nie pytaj ponownie"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Ustaw jako domyślną"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Zezwolić aplikacji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; na dostęp do zdjęć, filmów, muzyki, dźwięków i innych plików na tym urządzeniu?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Zezwolić aplikacji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; na dostęp do muzyki i innych plików audio na tym urządzeniu?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Zezwolić aplikacji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; na dostęp do zdjęć i filmów na tym urządzeniu?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Przyznać aplikacji „<xliff:g id="APP_NAME">%1$s</xliff:g>” dostęp do kolejnych zdjęć?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Zezwolić aplikacji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; na nagrywanie dźwięku?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Aplikacja będzie mogła nagrywać dźwięk tylko wtedy, gdy będzie używana"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Zezwolić aplikacji „<xliff:g id="APP_NAME">%1$s</xliff:g>” na nagrywanie dźwięku?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Pokazuj komunikat, gdy aplikacja uzyskuje dostęp do skopiowanego tekstu, obrazów lub innych treści"</string>
<string name="show_password_title" msgid="2877269286984684659">"Pokazuj hasła"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Wpisywane znaki są przez chwilę wyświetlane"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Ta aplikacja deklaruje, że może udostępniać dane typu <xliff:g id="PERMISSION_NAME">%s</xliff:g> osobom trzecim"</string>
</resources>
diff --git a/PermissionController/res/values-pt-rBR/strings.xml b/PermissionController/res/values-pt-rBR/strings.xml
index 70b6bb89b..5f03a288b 100644
--- a/PermissionController/res/values-pt-rBR/strings.xml
+++ b/PermissionController/res/values-pt-rBR/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Manter \"Enquanto o app estiver em uso\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Manter \"Apenas esta vez\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Mais inform."</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Permitir o acesso a todas as fotos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Selecionar fotos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Selecionar mais fotos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Não selecionar mais fotos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Não permitir mesmo assim"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Dispensar"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> de <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dia}one{# dia}many{# dias}other{# dias}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hora}one{# hora}many{# horas}other{# horas}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1min}one{#min}many{#min}other{#min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1s}one{#s}many{#s}other{#s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dia}one{# dia}many{# de dias}other{# dias}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hora}one{# hora}many{# de horas}other{# horas}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}one{# min}many{# de min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{#s}one{#s}many{# de segundos}other{#s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Qualquer permissão"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Qualquer horário"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Últimos 7 dias"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Últimas 24 horas"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Última hora"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Últimos 15 minutos"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Último minuto"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{No último dia}one{No último # dia}many{Nos últimos # de dias}other{Nos últimos # dias}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Na última # hora}one{Na última # hora}many{Nas últimas # de horas}other{Nas últimas # horas}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{No último # minuto}one{No último # minuto}many{Nos últimos # de minutos}other{Nos últimos # minutos}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Nenhum uso de permissões"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Acesso mais recente em qualquer momento"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Acesso mais recente nos últimos sete dias"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Uso da permissão na última hora"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Uso da permissão nos últimos 15 minutos"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Uso da permissão no último minuto"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Permissão não usada nas últimas 24 horas"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Permissão não usada nos últimos sete dias"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Não usado no último # dia}one{Não usado no último # dia}many{Não usado nos últimos # de dias}other{Não usado nos últimos # dias}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Não usado na última # hora}one{Não usado na última # hora}many{Não usado nas últimas # de horas}other{Não usado nas últimas # horas}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Permissões usadas por 1 app}one{Permissões usadas por # app}many{Used by # apps}other{Permissões usadas por # apps}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Ver tudo no painel"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrado por: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Permitir acesso apenas a arquivos de mídia"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Permitir o tempo todo"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Permitir durante o uso do app"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Permitir acesso a todas as fotos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Permitir acesso às fotos selecionadas"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Perguntar sempre"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Não permitir"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Local exato"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Não permitido"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Veja mais apps que têm acesso a todos os arquivos"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dia}one{# dia}many{# dias}other{# dias}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hora}one{# hora}many{# horas}other{# horas}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuto}one{# minuto}many{# minutos}other{# minutos}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 segundo}one{# segundo}many{# segundos}other{# segundos}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hora}one{# hora}many{# de horas}other{# horas}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuto}one{# minuto}many{# de minutos}other{# minutos}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# segundo}one{# segundo}many{# de segundos}other{# segundos}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Lembretes de permissões"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 app não usado"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> apps não usados"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Definir <xliff:g id="APP_NAME">%1$s</xliff:g> como o app de navegação padrão?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nenhuma permissão necessária"</string>
<string name="role_watch_description" msgid="267003778693177779">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> poderá interagir com suas notificações e acessar as permissões do Telefone, de SMS, de Contatos e da Agenda."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"O <xliff:g id="APP_NAME">%1$s</xliff:g> vai poder interagir com suas notificações e acessar os apps Telefone, SMS, Contatos, microfone e as permissões de dispositivos por perto."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> poderá interagir com suas notificações e fazer streaming de outros apps para os dispositivos conectados."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> poderá transmitir conteúdo para dispositivos por perto."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Este serviço compartilha fotos, mídia e notificações do smartphone com outros dispositivos."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Padrão atual"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Não perguntar novamente"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Definir como padrão"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acesse &lt;b&gt;fotos, vídeos, músicas, áudios e outros arquivos&lt;/b&gt; neste dispositivo?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acesse músicas e áudios neste dispositivo?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acesse fotos e vídeos neste dispositivo?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Conceder a &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acesso a mais fotos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Permitir que o app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grave áudio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"O app poderá gravar áudio apenas quando estiver em uso"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grave áudio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Mostrar uma mensagem quando os apps acessarem textos, imagens ou outros conteúdos copiados"</string>
<string name="show_password_title" msgid="2877269286984684659">"Mostrar senhas"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Mostrar os caracteres rapidamente enquanto você digita"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Este app declarou que pode compartilhar dados de <xliff:g id="PERMISSION_NAME">%s</xliff:g> com terceiros"</string>
</resources>
diff --git a/PermissionController/res/values-pt-rPT/strings.xml b/PermissionController/res/values-pt-rPT/strings.xml
index a70346229..a1a313408 100644
--- a/PermissionController/res/values-pt-rPT/strings.xml
+++ b/PermissionController/res/values-pt-rPT/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Manter \"Enquanto a app está a ser utilizada”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Manter “Apenas desta vez”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Mais informação"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Permitir acesso a todas as fotos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Selecionar fotos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Selecionar mais fotos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Não selecionar mais fotos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Não permitir mesmo assim"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Ignorar"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> de <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dia}many{# dias}other{# dias}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hora}many{# horas}other{# horas}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}many{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}many{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dia}many{# dias}other{# dias}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hora}many{# horas}other{# horas}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}many{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}many{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Qualquer autorização"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Em qualquer altura"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Últimos 7 dias"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Últimas 24 horas"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Última hora"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Últimos 15 minutos"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Último minuto"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{# dia anterior}many{# dias anteriores}other{# dias anteriores}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{# hora anterior}many{# horas anteriores}other{# horas anteriores}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{# minuto anterior}many{# minutos anteriores}other{# minutos anteriores}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Autorizações não utilizadas"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Acesso mais recente em qualquer altura"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Acesso mais recente nos últimos 7 dias"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Utilização das autorizações na última hora"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Utilização das autorizações nos últimos 15 minutos"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Utilização das autorizações no último minuto"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Sem utilização nas últimas 24 horas"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Não utilizada nos últimos 7 dias"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Não usada há # dia}many{Não usada há # dias}other{Não usada há # dias}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Não usada há # hora}many{Não usada há # horas}other{Não usada há # horas}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Utilização: 1 app}many{Utilização: # apps}other{Utilização: # apps}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Ver tudo no painel de controlo"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrado por: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Permitir apenas o acesso ao conteúdo multimédia"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Permitir sempre"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Permitir apenas enquanto uso a app"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Permita todas as fotos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Permita fotos selecionadas"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Perguntar sempre"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Não permitir"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Localização exata"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Não permitidas"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Veja mais apps que podem aceder a todos os ficheiros"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dia}many{# dias}other{# dias}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hora}many{# horas}other{# horas}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuto}many{# minutos}other{# minutos}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 segundo}many{# segundos}other{# segundos}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hora}many{# horas}other{# horas}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuto}many{# minutos}other{# minutos}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# segundo}many{# segundos}other{# segundos}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Lembretes de autorização"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 app não utilizada"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> apps não utilizadas"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Definir a app <xliff:g id="APP_NAME">%1$s</xliff:g> como a sua app de navegação predefinida?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Não são necessárias autorizações"</string>
<string name="role_watch_description" msgid="267003778693177779">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> poderá interagir com as suas notificações e aceder às autorizações do Telefone, SMS, Contactos e Calendário."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> vai poder interagir com as suas notificações e aceder às autorizações do Telemóvel, SMS, Contactos, Microfone e Dispositivos próximos."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> poderá interagir com as suas notificações e fazer stream das suas apps para o dispositivo ligado."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> vai poder fazer stream de conteúdo para dispositivos próximos."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Este serviço partilha as fotos, o conteúdo multimédia e as notificações do seu telemóvel com outros dispositivos."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Predefinição atual"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Não perguntar novamente"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Definir como predef."</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda a &lt;b&gt;fotos, vídeos, música, áudio, etc.&lt;/b&gt; neste dispositivo?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda a música e áudio neste dispositivo?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda a fotos e vídeos neste dispositivo?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Concede à app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acesso a mais fotos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grave áudio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"A app apenas poderá gravar áudio enquanto a estiver a utilizar."</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grave áudio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Apresente uma mensagem quando as apps acedem a texto, imagens ou outro conteúdo que copiou"</string>
<string name="show_password_title" msgid="2877269286984684659">"Mostrar palavras-passe"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Apresente rapidamente os carateres ao escrever"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Esta app declarou que pode partilhar dados da autorização <xliff:g id="PERMISSION_NAME">%s</xliff:g> com terceiros"</string>
</resources>
diff --git a/PermissionController/res/values-pt/strings.xml b/PermissionController/res/values-pt/strings.xml
index 70b6bb89b..5f03a288b 100644
--- a/PermissionController/res/values-pt/strings.xml
+++ b/PermissionController/res/values-pt/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Manter \"Enquanto o app estiver em uso\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Manter \"Apenas esta vez\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Mais inform."</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Permitir o acesso a todas as fotos"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Selecionar fotos"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Selecionar mais fotos"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Não selecionar mais fotos"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Não permitir mesmo assim"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Dispensar"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> de <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dia}one{# dia}many{# dias}other{# dias}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hora}one{# hora}many{# horas}other{# horas}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1min}one{#min}many{#min}other{#min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1s}one{#s}many{#s}other{#s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dia}one{# dia}many{# de dias}other{# dias}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hora}one{# hora}many{# de horas}other{# horas}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}one{# min}many{# de min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{#s}one{#s}many{# de segundos}other{#s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Qualquer permissão"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Qualquer horário"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Últimos 7 dias"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Últimas 24 horas"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Última hora"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Últimos 15 minutos"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Último minuto"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{No último dia}one{No último # dia}many{Nos últimos # de dias}other{Nos últimos # dias}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Na última # hora}one{Na última # hora}many{Nas últimas # de horas}other{Nas últimas # horas}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{No último # minuto}one{No último # minuto}many{Nos últimos # de minutos}other{Nos últimos # minutos}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Nenhum uso de permissões"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Acesso mais recente em qualquer momento"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Acesso mais recente nos últimos sete dias"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Uso da permissão na última hora"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Uso da permissão nos últimos 15 minutos"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Uso da permissão no último minuto"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Permissão não usada nas últimas 24 horas"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Permissão não usada nos últimos sete dias"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Não usado no último # dia}one{Não usado no último # dia}many{Não usado nos últimos # de dias}other{Não usado nos últimos # dias}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Não usado na última # hora}one{Não usado na última # hora}many{Não usado nas últimas # de horas}other{Não usado nas últimas # horas}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Permissões usadas por 1 app}one{Permissões usadas por # app}many{Used by # apps}other{Permissões usadas por # apps}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Ver tudo no painel"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrado por: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Permitir acesso apenas a arquivos de mídia"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Permitir o tempo todo"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Permitir durante o uso do app"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Permitir acesso a todas as fotos"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Permitir acesso às fotos selecionadas"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Perguntar sempre"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Não permitir"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Local exato"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Não permitido"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Veja mais apps que têm acesso a todos os arquivos"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dia}one{# dia}many{# dias}other{# dias}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hora}one{# hora}many{# horas}other{# horas}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuto}one{# minuto}many{# minutos}other{# minutos}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 segundo}one{# segundo}many{# segundos}other{# segundos}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hora}one{# hora}many{# de horas}other{# horas}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuto}one{# minuto}many{# de minutos}other{# minutos}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# segundo}one{# segundo}many{# de segundos}other{# segundos}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Lembretes de permissões"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 app não usado"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> apps não usados"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Definir <xliff:g id="APP_NAME">%1$s</xliff:g> como o app de navegação padrão?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nenhuma permissão necessária"</string>
<string name="role_watch_description" msgid="267003778693177779">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> poderá interagir com suas notificações e acessar as permissões do Telefone, de SMS, de Contatos e da Agenda."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"O <xliff:g id="APP_NAME">%1$s</xliff:g> vai poder interagir com suas notificações e acessar os apps Telefone, SMS, Contatos, microfone e as permissões de dispositivos por perto."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> poderá interagir com suas notificações e fazer streaming de outros apps para os dispositivos conectados."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"O app <xliff:g id="APP_NAME">%1$s</xliff:g> poderá transmitir conteúdo para dispositivos por perto."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Este serviço compartilha fotos, mídia e notificações do smartphone com outros dispositivos."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Padrão atual"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Não perguntar novamente"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Definir como padrão"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acesse &lt;b&gt;fotos, vídeos, músicas, áudios e outros arquivos&lt;/b&gt; neste dispositivo?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acesse músicas e áudios neste dispositivo?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acesse fotos e vídeos neste dispositivo?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Conceder a &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; acesso a mais fotos?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Permitir que o app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grave áudio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"O app poderá gravar áudio apenas quando estiver em uso"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; grave áudio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Mostrar uma mensagem quando os apps acessarem textos, imagens ou outros conteúdos copiados"</string>
<string name="show_password_title" msgid="2877269286984684659">"Mostrar senhas"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Mostrar os caracteres rapidamente enquanto você digita"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Este app declarou que pode compartilhar dados de <xliff:g id="PERMISSION_NAME">%s</xliff:g> com terceiros"</string>
</resources>
diff --git a/PermissionController/res/values-ro/strings.xml b/PermissionController/res/values-ro/strings.xml
index 54d1b820e..30a9320c5 100644
--- a/PermissionController/res/values-ro/strings.xml
+++ b/PermissionController/res/values-ro/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Păstrează opțiunea „Când aplicația este folosită”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Păstrează „Doar de data aceasta”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Mai multe info."</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Permite accesul la toate fotografiile"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Selectează fotografii"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Selectează mai multe fotografii"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Nu selecta mai multe fotografii"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Nu permite în nicio situație"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Închide"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> din <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{O zi}few{# zile}other{# de zile}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{O oră}few{# ore}other{# de ore}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min.}few{# min.}other{# min.}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{O sec.}few{# sec.}other{# sec.}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# zi}few{# zile}other{# de zile}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# oră}few{# ore}other{# de ore}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min.}few{# min.}other{# min.}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}few{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Orice permisiune"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Oricând"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Ultimele 7 zile"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Ultimele 24 de ore"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Ultima oră"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Ultimele 15 minute"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Ultimul minut"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Ultima zi}few{Ultimele # zile}other{Ultimele # de zile}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Ultima oră}few{Ultimele # ore}other{Ultimele # de ore}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Ultimul minut}few{Ultimele # minute}other{Ultimele # de minute}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Nicio permisiune folosită"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Cel mai recent acces oricând"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Cea mai recentă accesare din ultimele șapte zile"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Folosirea permisiunii în ultima oră"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Folosirea permisiunii în ultimele 15 minute"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Folosirea permisiunii în ultimul minut"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Nu s-a(u) folosit în ultimele 24 de ore"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Nu a fost folosită în ultimele șapte zile"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Permisiunea nu a fost folosită în ultima zi}few{Permisiunea nu a fost folosită în ultimele # zile}other{Permisiunea nu a fost folosită în ultimele # de zile}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Permisiunea nu a fost folosită în ultima oră}few{Permisiunea nu a fost folosită în ultimele # ore}other{Permisiunea nu a fost folosită în ultimele # de ore}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Folosită de o aplicație}few{Folosită de # aplicații}other{Folosită de # de aplicații}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Vezi totul în Tabloul de bord"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrat după: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Permite accesul numai la fișierele media"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Permite întotdeauna"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Permite numai când folosești aplicația"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Permite toate fotografiile"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Permite fotografiile selectate"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Întreabă de fiecare dată"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Nu permite"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Locația exactă"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nepermise"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Vezi mai multe aplicații care pot accesa toate fișierele"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{O zi}few{# zile}other{# de zile}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{O oră}few{# ore}other{# de ore}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{Un minut}few{# minute}other{# de minute}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{O secundă}few{# secunde}other{# de secunde}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# oră}few{# ore}other{# de ore}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minut}few{# minute}other{# de minute}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# secundă}few{# secunde}other{# de secunde}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Mementouri de permisiune"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"O aplicație nefolosită"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> aplicații nefolosite"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Setezi <xliff:g id="APP_NAME">%1$s</xliff:g> ca aplicație de navigare prestabilită?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nu este nevoie de permisiuni"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> va putea să interacționeze cu notificările și să-ți acceseze permisiunile pentru Telefon, SMS-uri, Agendă și Calendar."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> va putea să interacționeze cu notificările tale și să-ți acceseze permisiunile pentru telefon, SMS-uri, agendă, microfon și dispozitivele din apropiere."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> va putea să interacționeze cu notificările și să redea în stream conținutul din aplicații pe dispozitivul conectat."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> va putea reda conținut pe dispozitivele din apropiere."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Acest serviciu trimite fotografiile, conținutul media și notificările de pe telefon pe alte dispozitive."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Aplicația prestabilită actuală"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Nu mai întreba"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Setează ca prestabilită"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Permiți accesul &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; la &lt;b&gt;fotografii, clipuri, conținut audio, muzică și alte fișiere&lt;/b&gt; de pe dispozitiv?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Permiți accesul &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; la muzică și fișiere audio de pe acest dispozitiv?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să acceseze fotografiile și videoclipurile de pe dispozitiv?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Permiți accesul &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; la mai multe fotografii?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să înregistreze audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Aplicația va putea să înregistreze conținut audio doar când o folosești"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Permiți ca &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; să înregistreze audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Afișează un mesaj când aplicațiile accesează text, imagini sau alte tipuri de conținut pe care le-ai copiat"</string>
<string name="show_password_title" msgid="2877269286984684659">"Afișează parolele"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Caracterele se afișează pentru scurt timp, pe măsură ce tastezi"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Aplicația afirmă că poate trimite terților date despre <xliff:g id="PERMISSION_NAME">%s</xliff:g>"</string>
</resources>
diff --git a/PermissionController/res/values-ru/strings.xml b/PermissionController/res/values-ru/strings.xml
index cf107f854..c38324b84 100644
--- a/PermissionController/res/values-ru/strings.xml
+++ b/PermissionController/res/values-ru/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Оставить доступ только в активном режиме"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Оставить \"Только в этот раз\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Подробнее"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Разрешить доступ ко всем фото"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Выбрать фото"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Выбрать ещё фото"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Больше не выбирать фото"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Все равно запретить"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Закрыть"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> из <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 день}one{# день}few{# дня}many{# дней}other{# дня}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 час}one{# час}few{# часа}many{# часов}other{# часа}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 минута}one{# минута}few{# минуты}many{# минут}other{# минуты}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 секунда}one{# секунда}few{# секунды}many{# секунд}other{# секунды}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# день}one{# день}few{# дня}many{# дней}other{# дня}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# час}one{# час}few{# часа}many{# часов}other{# часа}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# минута}one{# минута}few{# минуты}many{# минут}other{# минуты}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# секунда}one{# секунда}few{# секунды}many{# секунд}other{# секунды}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Все разрешения"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Все время"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Последние 7 дней"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Последние 24 часа"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Последний час"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Последние 15 минут"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Последняя минута"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{За последний # день}one{За последний # день}few{За последние # дня}many{За последние # дней}other{За последние # дня}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{За последний # час}one{За последний # час}few{За последние # часа}many{За последние # часов}other{За последние # часа}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{За последнюю # минуту}one{За последнюю # минуту}few{За последние # минуты}many{За последние # минут}other{За последние # минуты}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Разрешения не использовались"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Доступ к разрешениям за все время"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Доступ за последние 7 дней"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Разрешения, использованные за последний час"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Разрешения, использованные за последние 15 минут"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Разрешения, использованные за последнюю минуту"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Не использовалось последние 24 часа"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Не использовалось последние 7 дней"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{С момента последнего использования прошел # день}one{С момента последнего использования прошел # день}few{С момента последнего использования прошло # дня}many{С момента последнего использования прошло # дней}other{С момента последнего использования прошло # дня}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{С момента последнего использования прошел # час}one{С момента последнего использования прошел # час}few{С момента последнего использования прошло # часа}many{С момента последнего использования прошло # часов}other{С момента последнего использования прошло # часа}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Использует 1 приложение}one{Использует # приложение}few{Используют # приложения}many{Используют # приложений}other{Используют # приложения}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Показать все в панели управления"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Фильтр: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Разрешить доступ только к медиафайлам"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Разрешить в любом режиме"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Разрешить только во время использования приложения"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Разрешить все фото"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Разрешить выбранные фото"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Спрашивать каждый раз"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Запретить"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Точное местоположение"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Доступ запрещен"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Ещё приложения с доступом ко всем файлам"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 день}one{# день}few{# дня}many{# дней}other{# дня}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 час}one{# час}few{# часа}many{# часов}other{# часа}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 минута}one{# минута}few{# минуты}many{# минут}other{# минуты}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 секунда}one{# секунда}few{# секунды}many{# секунд}other{# секунды}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# час}one{# час}few{# часа}many{# часов}other{# часа}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# минута}one{# минута}few{# минуты}many{# минут}other{# минуты}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# секунда}one{# секунда}few{# секунды}many{# секунд}other{# секунды}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Напоминания о разрешениях"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 неиспользуемое приложение"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Неиспользуемые приложения (<xliff:g id="NUMBER_OF_APPS">%s</xliff:g>)"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Сделать приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" навигатором по умолчанию?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Разрешения не требуются."</string>
<string name="role_watch_description" msgid="267003778693177779">"Приложению \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" будет предоставлен доступ к уведомлениям, а также следующие разрешения: телефон, SMS, контакты и календарь."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" получит доступ к уведомлениям, а также разрешения \"Телефон\", SMS, \"Контакты\", \"Микрофон\" и \"Устройства поблизости\"."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" сможет взаимодействовать с уведомлениями и транслировать приложения на подключенное устройство."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Приложение \"<xliff:g id="APP_NAME">%1$s</xliff:g>\" сможет транслировать контент на устройства поблизости."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Этот сервис открывает другим устройствам доступ к фотографиям, медиафайлам и уведомлениям на вашем телефоне."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Используется по умолчанию"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Больше не спрашивать"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"По умолчанию"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Разрешить приложению &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ к &lt;b&gt;фото, видео, аудио и другим файлам&lt;/b&gt; на устройстве?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Разрешить приложению &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ к музыке и аудио на устройстве?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Разрешить приложению &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ к фото и видео на устройстве?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Предоставить приложению &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ к другим фотографиям?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Разрешить приложению &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; записывать аудио?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Приложение будет записывать аудио, только когда вы им пользуетесь."</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Разрешить приложению &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; записывать аудио?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Показывать уведомления, когда приложения обращаются к скопированному тексту, изображениям или другому контенту"</string>
<string name="show_password_title" msgid="2877269286984684659">"Показывать пароли"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Ненадолго показывать символы при вводе"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Это приложение может передавать третьим лицам данные, относящиеся к категории \"<xliff:g id="PERMISSION_NAME">%s</xliff:g>\""</string>
</resources>
diff --git a/PermissionController/res/values-si/strings.xml b/PermissionController/res/values-si/strings.xml
index 5859b9652..b34a2aeaf 100644
--- a/PermissionController/res/values-si/strings.xml
+++ b/PermissionController/res/values-si/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“යෙදුම භාවිතයේ තිබෙන අතරතුර” තබා ගන්න"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“මේ වතාවේ පමණක්” තබා ගන්න"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"තවත් තතු"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"සියලු ඡායාරූප වෙත ප්‍රවේශ වීමට ඉඩ දෙන්න"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ඡායාරූප තෝරන්න"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"තවත් ඡායාරූප තෝරන්න"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"තවත් ඡායාරූප තෝරන්න එපා"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"කෙසේ වෙතත් ඉඩ නොදෙන්න"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"ඉවත ලන්න"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>කින් <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{දින 1ක්}one{දින #ක්}other{දින #ක්}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{පැය 1ක්}one{පැය #ක්}other{පැය #ක්}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{මිනි 1ක්}one{මිනි #ක්}other{මිනි #ක්}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{තත් 1ක්}one{තත් #ක්}other{තත් #ක්}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# දිනක්}one{දින #ක්}other{දින #ක්}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{පැය #ක්}one{පැය #ක්}other{පැය #ක්}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{විනා #ක්}one{විනාඩි #ක්}other{විනාඩි #ක්}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{තත් #}one{තත් #ක්}other{තත් #ක්}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ඕනෑම අවසරයක්"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ඕනෑම වේලාවක"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"පසුගිය දින 7"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"පසුගිය පැය 24"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"පසුගිය පැය 1"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"පසුගිය මිනිත්තු 15"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"පසුගිය විනාඩි 1"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{අවසාන දින #}one{අවසාන දින #}other{අවසාන දින #}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{පසුගිය පැය #}one{පසුගිය පැය #}other{පසුගිය පැය #}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{පසුගිය මිනිත්තු #}one{පසුගිය මිනිත්තු #}other{පසුගිය මිනිත්තු #}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"අවසර භාවිත නැත"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"ඕනෑම වේලාවක ඉතාම මෑත ප්‍රවේශය"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"අවසාන දින 7 ක් තුළ ඉතා මෑත ප්‍රවේශය"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"අවසාන 1 පැයක් තුළ අවසර භාවිතය"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"අවසාන විනාඩි 15 තුළ අවසර භාවිතය"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"අවසාන 1 විනාඩිය තුළ අවසර භාවිතය"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"පසුගිය පැය 24 තුළ භාවිත කර නැත"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"පසුගිය දින 7 තුළ භාවිත කර නැත"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{පසුගිය දින # තුළ භාවිතා කර නැත}one{පසුගිය දින # තුළ භාවිතා කර නැත}other{පසුගිය දින # තුළ භාවිතා කර නැත}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{පසුගිය පැය # තුළ භාවිතා කර නැත}one{පසුගිය පැය # තුළ භාවිතා කර නැත}other{පසුගිය පැය # තුළ භාවිතා කර නැත}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{යෙදුම් 1කින් භාවිත කෙරේ}one{යෙදුම් #කින් භාවිත කෙරේ}other{යෙදුම් #කින් භාවිත කෙරේ}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"උපකරණ පුවරුවේ ඇති සියල්ල බලන්න"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"මේ අනුව පෙරහන්න: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"මාධ්‍ය වෙත ප්‍රවේශය පමණක් ඉඩ දෙන්න"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"සැම විටම ඉඩ දෙන්න"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"යෙදුම භාවිතයේදී පමණක් ඉඩ දෙන්න"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"ඡායාරූප සියල්ලට අවසර දෙන්න"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"තෝරන ලද ඡායාරූපවලට අවසර දෙන්න"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"සෑම විටම ඉල්ලන්න"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"ඉඩ නොදෙන්න"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ඉතා නිවැරදි ස්ථානය"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"ඉඩ නොදේ"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"සියලු ගොනු වෙත ප්‍රවේශ විය හැකි තව යෙදුම් බලන්න"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{දින 1ක්}one{දින #ක්}other{දින #ක්}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{පැය 1ක්}one{පැය #ක්}other{පැය #ක්}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{මිනිත්තු 1ක්}one{මිනිත්තු #ක්}other{මිනිත්තු #ක්}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{තත්පර 1ක්}one{තත්පර #ක්}other{තත්පර #ක්}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{පැය #ක්}one{පැය #ක්}other{පැය #ක්}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{මිනිත්තු #ක්}one{මිනිත්තු #ක්}other{මිනිත්තු #ක්}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{තත්පර #}one{තත්පර #}other{තත්පර #}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"අවසර සිහි කැඳවීම්"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"භාවිත නොකළ යෙදුම් 1"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"භාවිත නොකළ යෙදුම් <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබගේ පෙරනිමි සංචාලන යෙදුම ලෙස සකසන්නද?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"අවසර අවශ්‍ය නැත"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> ඔබගේ දැනුම්දීම් සමඟ අන්තර්ක්‍රියා කිරීමට සහ ඔබගේ දුරකථනය, කෙටි පණිවුඩ, සම්බන්ධතා සහ දින දර්ශන අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙනු ඇත."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> හට ඔබේ දැනුම්දීම් සමග අන්තර් ක්‍රියා කිරීමට සහ ඔබේ දුරකථනය, SMS, සම්බන්ධතා, මයික්‍රෆෝනය සහ අවට උපාංග අවසර වෙත ප්‍රවේශ වීමට ඉඩ දෙනු ඇත."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> හට ඔබගේ දැනුම්දීම් සමඟ අන්තර්ක්‍රියා කිරීමට සහ ඔබගේ යෙදුම් සම්බන්ධිත උපාංගයට ප්‍රවාහ කිරීමට ඉඩ දෙනු ඇත."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> හට අවට උපාංග වෙත අන්තර්ගතය ප්‍රවාහ කිරීමට ඉඩ දෙනු ඇත."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"මෙම සේවාව ඔබගේ ඡායාරූප, මාධ්‍ය සහ දැනුම්දීම් ඔබගේ දුරකථනයෙන් වෙනත් උපාංග වෙත බෙදා ගනී."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"වත්මන් පෙරනිමිය"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"නැවත නොඅසන්න"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"පෙරනිමි ලෙස සකසන්න"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;b&gt; හට මෙම උපාංගයේ &lt;b&gt;ඡායාරූප, වීඩියෝ, සංගීතය, ශ්‍රව්‍ය සහ වෙනත් ගොනු&lt;b&gt; වෙත ප්‍රවේශ වීමට ඉඩ දෙන්නද?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;b&gt; හට මෙම උපාංගයේ සංගීතය සහ ශ්‍රව්‍ය වෙත ප්‍රවේශ වීමට ඉඩ දෙන්නද?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;b&gt; හට මෙම උපාංගයේ ඇති ඡායාරූප සහ වීඩියෝ වෙත ප්‍රවේශ වීමට ඉඩ දෙන්නද?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; හට තවත් ඡායාරූප වෙත ප්‍රවේශය දෙන්නද?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;b&gt; වෙත ශබ්දය පටි ගත කිරීමට ඉඩ දෙන්නද?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"යෙදුමට ඔබ යෙදුම භාවිත කරන අතරතුර ඕඩියෝ පටිගත කිරීමට පමණක් හැකි වනු ඇත"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; හට ඕඩියෝ පටිගත කිරීමට ඉඩ දෙන්නද?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"යෙදුම් ඔබ පිටපත් කර ඇති පාඨ, රූප හෝ වෙනත් අන්තර්ගතය වෙත ප්‍රවේශ වන විට පණිවුඩයක් පෙන්වන්න"</string>
<string name="show_password_title" msgid="2877269286984684659">"මුරපද පෙන්වන්න"</string>
<string name="show_password_summary" msgid="1110166488865981610">"ඔබ ටයිප් කරන විට අනුලකුණු කෙටියෙන් පෙන්වන්න"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"මෙම යෙදුම තෙවන පාර්ශව සමග <xliff:g id="PERMISSION_NAME">%s</xliff:g> දත්ත බෙදා ගත හැකි යැයි මෙය සඳහන් කළා"</string>
</resources>
diff --git a/PermissionController/res/values-sk/strings.xml b/PermissionController/res/values-sk/strings.xml
index bf5caf0a6..a00338f4c 100644
--- a/PermissionController/res/values-sk/strings.xml
+++ b/PermissionController/res/values-sk/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Ponechať Počas používania aplikácie"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Ponechať možnosť Iba tentokrát"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Ďalšie info"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Povoliť prístup k všetkým fotkám"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Vybrať fotky"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Vybrať ďalšie fotky"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Nevyberať ďalšie fotky"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Aj tak nepovoliť"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Zavrieť"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> z <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 deň}few{# dni}many{# dňa}other{# dní}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 hodina}few{# hodiny}many{# hodiny}other{# hodín}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}few{# min}many{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}few{# s}many{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# deň}few{# dni}many{# dňa}other{# dní}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# hodina}few{# hodiny}many{# hodiny}other{# hodín}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}few{# min}many{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}few{# s}many{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Všetky povolenia"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Kedykoľvek"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Posledných 7 dní"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Posledných 24 hodín"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Posledná 1 hodina"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Posledných 15 minút"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Posledná 1 minúta"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Posledný # deň}few{Posledné # dni}many{Posledných # dňa}other{Posledných # dní}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Posledná # hodina}few{Posledné # hodiny}many{Posledných # hodiny}other{Posledných # hodín}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Posledná # minúta}few{Posledné # minúty}many{Posledných # minúty}other{Posledných # minút}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Žiadne využitie povolení"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Posledný prístup kedykoľvek"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Posledný prístup za posledných sedem dní"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Používanie povolení za poslednú hodinu"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Používanie povolení za posledných 15 minút"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Používanie povolení za poslednú minútu"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Nepoužité za posledných 24 hodín"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Nepoužité za posledných 7 dní"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Nepoužité za posledný # deň}few{Nepoužité za posledné # dni}many{Nepoužité za posledných # dňa}other{Nepoužité za posledných # dní}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Nepoužité za poslednú # hodinu}few{Nepoužité za posledné # hodiny}many{Nepoužité za posledných # hodiny}other{Nepoužité za posledných # hodín}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Používa 1 aplikácia}few{Používajú # aplikácie}many{Used by # apps}other{Používa # aplikácií}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Zobraziť všetko v hlavnom paneli"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrované podľa: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Povoliť prístup iba k médiám"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Povoliť vždy"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Povoliť iba pri používaní aplikácie"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Povoliť všetky fotky"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Povoliť vybrané fotky"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Vždy sa opýtať"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Nepovoliť"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Presná poloha"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nepovolené"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Zobraziť ďalšie aplikácie s prístupom k všetkým súborom"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 deň}few{# dni}many{# dňa}other{# dní}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 hodina}few{# hodiny}many{# hodiny}other{# hodín}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minúta}few{# minúty}many{# minúty}other{# minút}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekunda}few{# sekundy}many{# sekundy}other{# sekúnd}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# hodina}few{# hodiny}many{# hodiny}other{# hodín}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minúta}few{# minúty}many{# minúty}other{# minút}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekunda}few{# sekundy}many{# sekundy}other{# sekúnd}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Pripomenutia povolení"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 nepoužívaná aplikácia"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Nepoužívané aplikácie: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Chcete aplikáciu <xliff:g id="APP_NAME">%1$s</xliff:g> nastaviť ako predvolenú navigačnú aplikáciu?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nie sú potrebné žiadne povolenia"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> bude môcť interagovať s vašimi upozorneniami a získa prístup k telefónu, SMS, kontaktom a kalendáru."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> bude môcť interagovať s vašimi upozorneniami a získa prístup k povoleniam pre telefón, SMS, kontakty, mikrofón a zariadenia v okolí."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> bude môcť interagovať s vašimi upozorneniami a streamovať vaše aplikácie do pripojeného zariadenia."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Aplikácia <xliff:g id="APP_NAME">%1$s</xliff:g> bude mať povolené streamovať obsah do zariadení v okolí."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Táto služba zdieľa fotky, médiá a upozornenia z vášho telefónu do iných zariadení."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Aktuálne predvolená"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Nabudúce sa nepýtať"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Nastav. ako predvol."</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Povoliť aplikácii &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; prístup k &lt;b&gt;fotkám, videám, hudbe, zvuku a ďalším súborom&lt;/b&gt; v tomto zariadení?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Chcete povoliť aplikácii &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; prístup k hudbe a zvuku v tomto zariadení?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Chcete povoliť aplikácii &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; prístup k fotkám a videám v tomto zariadení?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Chcete udeliť aplikácii <xliff:g id="APP_NAME">%1$s</xliff:g> prístup k ďalším fotkám?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Chcete povoliť aplikácii &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; nahrávať zvuk?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Táto aplikácia bude môcť nahrávať zvuk iba vtedy, keď ju budete používať"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Chcete povoliť aplikácii &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; nahrávať zvuk?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Zobrazovať správu, keď sa aplikácie získajú pristup k textu, obrázkom alebo inému obsahu, ktorý ste skopírovali"</string>
<string name="show_password_title" msgid="2877269286984684659">"Zobrazovať heslá"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Pri písaní nakrátko zobrazovať zadávané znaky"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Táto aplikácia uvádzala, že môže zdieľať údaje povolenia <xliff:g id="PERMISSION_NAME">%s</xliff:g> s tretími stranami"</string>
</resources>
diff --git a/PermissionController/res/values-sl/strings.xml b/PermissionController/res/values-sl/strings.xml
index fb8fc505d..2632333c7 100644
--- a/PermissionController/res/values-sl/strings.xml
+++ b/PermissionController/res/values-sl/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Obdrži »Ko je aplikacija v uporabi«"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Obdrži »Samo tokrat«"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Več informacij"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Dovoli dostop do vseh fotografij"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Izbira fotografij"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Izbira več fotografij"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Brez izbire več fotografij"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ne dovoli kljub temu"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Opusti"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> od <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dan}one{# dan}two{# dneva}few{# dni}other{# dni}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ura}one{# ura}two{# uri}few{# ure}other{# ur}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}one{# min}two{# min}few{# min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 s}one{# s}two{# s}few{# s}other{# s}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dan}one{# dan}two{# dneva}few{# dni}other{# dni}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ura}one{# ura}two{# uri}few{# ure}other{# ur}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}one{# min}two{# min}few{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# s}one{# s}two{# s}few{# s}other{# s}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Katero koli dovoljenje"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Kadar koli"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Zadnjih 7 dni"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Zadnjih 24 ur"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Zadnja ura"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Zadnjih 15 minut"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Zadnja minuta"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Zadnji # dan}one{Zadnji # dan}two{Zadnja # dneva}few{Zadnje # dni}other{Zadnjih # dni}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Zadnja # ura}one{Zadnja # ura}two{Zadnji # uri}few{Zadnje # ure}other{Zadnjih # ur}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Zadnja # minuta}one{Zadnja # minuta}two{Zadnji # minuti}few{Zadnje # minute}other{Zadnjih # minut}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Ni uporabe dovoljenj"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Zadnja uporaba kadar koli"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Zadnja uporaba v zadnjih 7 dneh"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Uporaba dovoljenj v zadnji uri"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Uporaba dovoljenj v zadnjih 15 minutah"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Uporaba dovoljenj v zadnji minuti"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Brez uporabe v zadnjih 24 urah"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Brez uporabe v zadnjih 7 dneh"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Brez uporabe v zadnjem # dnevu}one{Brez uporabe v zadnjem # dnevu}two{Brez uporabe v zadnjih # dneh}few{Brez uporabe v zadnjih # dneh}other{Brez uporabe v zadnjih # dneh}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Brez uporabe v zadnji # uri}one{Brez uporabe v zadnji # uri}two{Brez uporabe v zadnjih # urah}few{Brez uporabe v zadnjih # urah}other{Brez uporabe v zadnjih # urah}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Uporablja 1 aplikacija}one{Uporablja # aplikacija}two{Uporabljata # aplikaciji}few{Uporabljajo # aplikacije}other{Uporablja # aplikacij}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Prikaži vse na nadzorni plošči"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrirano po: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Dovoli samo dostop do predstavnosti"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Vedno dovoli"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Dovoli samo med uporabo aplikacije"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Dovoli vse fotografije"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Dovoli izbrane fotografije"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Vedno vprašaj"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Ne dovoli"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Natančna lokacija"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Ni dovoljeno"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Prikaži več aplikacij z dostopom do vseh datotek"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dan}one{# dan}two{# dneva}few{# dni}other{# dni}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ura}one{# ura}two{# uri}few{# ure}other{# ur}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuta}one{# minuta}two{# minuti}few{# minute}other{# minut}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekunda}one{# sekunda}two{# sekundi}few{# sekunde}other{# sekund}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ura}one{# ura}two{# uri}few{# ure}other{# ur}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuta}one{# minuta}two{# minuti}few{# minute}other{# minut}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekunda}one{# sekunda}two{# sekundi}few{# sekunde}other{# sekund}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Opomniki za dovoljenja"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 neuporabljena aplikacija"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Št. neuporabljenih aplikacij: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Želite nastaviti aplikacijo <xliff:g id="APP_NAME">%1$s</xliff:g> kot privzeto aplikacijo za navigacijo?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nobeno dovoljenje ni potrebno"</string>
<string name="role_watch_description" msgid="267003778693177779">"Aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> bosta omogočena interakcija z obvestili in dostop do dovoljenj za telefon, sporočila SMS, stike in koledar."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> bosta omogočeni interakcija z obvestili in uporaba dovoljenj Telefon, SMS, Stiki, Mikrofon in Naprave v bližini."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> bosta omogočena interakcija z obvestili in pretočno izvajanje aplikacij v povezano napravo."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Aplikaciji <xliff:g id="APP_NAME">%1$s</xliff:g> bo omogočeno pretočno predvajanje vsebine v napravah v bližini."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ta storitev deli vaše fotografije, predstavnost in obvestila iz vašega telefona z drugimi napravami."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Trenutna privzeta nastavitev"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ne vprašaj me več"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Nastavi kot privzeto"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Dovolite aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; dostop do &lt;b&gt;fotografij, videoposnetkov, glasbe, zvočnih datotek in drugih datotek&lt;/b&gt; v tej napravi?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Dovolite aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; dostop do glasbe in zvočnih datotek v tej napravi?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Dovolite aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; dostop do fotografij in videoposnetkov v tej napravi?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Ali aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; dovolite dostop do več fotografij?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Dovolite aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; snemanje zvoka?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Aplikacija bo lahko snemala zvok le med vašo uporabo aplikacije."</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Želite aplikaciji &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; omogočiti snemanje zvoka?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Pokaži sporočilo, ko aplikacije dostopijo do besedila, slik ali drugih vsebin, ki ste jih kopirali."</string>
<string name="show_password_title" msgid="2877269286984684659">"Pokaži gesla"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Za trenutek prikaži znake med vnašanjem."</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Ta aplikacija navaja, da bo podatke o dovoljenju »<xliff:g id="PERMISSION_NAME">%s</xliff:g>« morda delila s tretjimi osebami."</string>
</resources>
diff --git a/PermissionController/res/values-sq/strings.xml b/PermissionController/res/values-sq/strings.xml
index 3b6e72247..b3bb656c7 100644
--- a/PermissionController/res/values-sq/strings.xml
+++ b/PermissionController/res/values-sq/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Mbaje \"Kur aplikacioni është në përdorim\""</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Mbaje “Vetëm këtë herë”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Më shumë info."</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Lejo qasjen te të gjitha fotografitë"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Zgjidh fotografitë"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Zgjidh fotografi të tjera"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Mos zgjidh fotografi të tjera"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Mos lejo gjithsesi"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Hiqe"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> nga <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ditë}other{# ditë}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 orë}other{# orë}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min.}other{# min.}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sek.}other{# sek.}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ditë}other{# ditë}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# orë}other{# orë}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min.}other{# min.}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sek.}other{# sek.}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Çdo autorizim"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Në çdo kohë"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"7 ditët e fundit"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"24 orët e fundit"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"1 orën e fundit"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"15 minutat e fundit"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Minuta e fundit"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Dita e fundit}other{# ditët e fundit}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Ora e fundit}other{# orët e fundit}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Minuta e fundit}other{# minutat e fundit}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Nuk ka përdorime të lejeve"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Qasja më e fundit në çdo kohë"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Qasja më e fundit në 7 ditët e fundit"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Përdorimi i autorizimeve në 1 orën e fundit"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Përdorimi i autorizimeve në 15 minutat e fundit"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Përdorimi i autorizimeve në 1 minutën e fundit"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Asnjë përdorim gjatë 24 orëve të fundit"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Nuk është përdorur gjatë 7 ditëve të fundit"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Nuk është përdorur gjatë ditës së fundit}other{Nuk është përdorur gjatë # ditëve të fundit}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Nuk është përdorur gjatë orës së fundit}other{Nuk është përdorur gjatë # orëve të fundit}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Përdorur nga 1 aplikacion}other{Përdorur nga # aplikacione}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Shikoji të gjitha te \"Paneli\""</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtruar sipas: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Lejo qasjen vetëm në media"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Lejo gjithmonë"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Lejo vetëm kur përdor aplikacionin"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Lejo të gjitha fotografitë"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Lejo fotografitë e zgjedhura"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Pyet çdo herë"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Mos lejo"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Vendndodhja e saktë"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Nuk lejohet"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Shiko më shumë aplikacione me qasje tek të gjithë skedarët"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ditë}other{# ditë}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 orë}other{# orë}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minutë}other{# minuta}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekondë}other{# sekonda}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# orë}other{# orë}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minutë}other{# minuta}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekondë}other{# sekonda}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Alarmet rikujtuese për lejet"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 aplikacion i papërdorur"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> aplikacione të papërdorura"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Të caktohet <xliff:g id="APP_NAME">%1$s</xliff:g> si aplikacioni i parazgjedhur i navigimit?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Nuk nevojitet asnjë leje"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> do të lejohet të ndërveprojë me njoftimet e tua dhe të ketë qasje te lejet e \"Telefonit\", \"SMS-ve\", \"Kontakteve\" dhe \"Kalendarit\"."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> do të lejohet të ndërveprojë me njoftimet e tua dhe të ketë qasje te lejet e \"Telefonit\", \"SMS-ve\", \"Kontakteve\", \"Mikrofonit\" dhe të \"Pajisjeve në afërsi\"."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> do të lejohet të ndërveprojë me njoftimet e tua dhe të transmetojë aplikacionet në pajisjen e lidhur."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> do të lejohet që të transmetojë përmbajtje te pajisjet në afërsi."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ky shërbim ndan fotografitë, median dhe njoftimet nga telefoni yt me pajisje të tjera."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Parazgjedhja aktuale"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Mos pyet më"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Cakto si parazgjedhje"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Të lejohet që &lt;b&amp;gt<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; të ketë qasje te &lt;b&gt;fotografitë, videot, muzika, audioja e të tjera&lt;/b&gt; në pajisje?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Të lejohet që &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; të ketë qasje te muzika dhe te audioja në këtë pajisje?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Të lejohet që &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; të ketë qasje te fotografitë dhe videot në këtë pajisje?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"T\'i jepet &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; qasje në fotografi të tjera?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Të lejohet që &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; të regjistrojë audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Aplikacioni do të mund të regjistrojë audion vetëm kur ti po e përdor aplikacionin"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Të lejohet që &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; të regjistrojë audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Shfaq një mesazh kur aplikacionet qasen te tekstet, imazhet ose përmbajtje të tjera që ke kopjuar"</string>
<string name="show_password_title" msgid="2877269286984684659">"Shfaq fjalëkalimet"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Shfaq karakteret shkurtimisht kur shkruan"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Ky aplikacion deklaroi se mund të ndajë të dhënat e \"<xliff:g id="PERMISSION_NAME">%s</xliff:g>\" me palë të treta"</string>
</resources>
diff --git a/PermissionController/res/values-sr/strings.xml b/PermissionController/res/values-sr/strings.xml
index 21859c0e6..380b46d44 100644
--- a/PermissionController/res/values-sr/strings.xml
+++ b/PermissionController/res/values-sr/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Задржи „Док се апликација користи“"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Задржи Само овај пут"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Више информација"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Дозволи приступ свим сликама"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Изаберите слике"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Изаберите још слика"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Немојте да бирате више слика"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ионако не дозволи"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Одбаци"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> од <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 дан}one{# дан}few{# дана}other{# дана}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 сат}one{# сат}few{# сата}other{# сати}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 мин}one{# мин}few{# мин}other{# мин}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 сек}one{# сек}few{# сек}other{# сек}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# дан}one{# дан}few{# дана}other{# дана}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# сат}one{# сат}few{# сата}other{# сати}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# мин}one{# мин}few{# мин}other{# мин}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# сек}one{# сек}few{# сек}other{# сек}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Било која дозвола"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Било када"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Последњих 7 дана"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Последња 24 сата"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Последњи сат"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Последњих 15 минута"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Последњи минут"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Последњи # дан}one{Последњи # дан}few{Последња # дана}other{Последњих # дана}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Последњи # сат}one{Последњи # сат}few{Последња # сата}other{Последњих # сати}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Последњи # минут}one{Последњи # минут}few{Последња # минута}other{Последњих # минута}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Дозволе нису коришћене"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Најскорији приступ у било ком тренутку"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Најскорији приступ у последњих 7 дана"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Коришћење дозвола у последњих сат времена"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Коришћење дозволе у последњих 15 минута"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Коришћење дозвола у последњем минуту"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Није коришћено у последња 24 сата"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Није коришћено у последњих 7 дана"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Није коришћено током последњег # дана}one{Није коришћено током последњег # дана}few{Није коришћено током последња # дана}other{Није коришћено током последњих # дана}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Није коришћено током последњег # сата}one{Није коришћено током последњег # сата}few{Није коришћено током последња # сата}other{Није коришћено током последњих # сати}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Користи 1 апликација}one{Користи # апликација}few{Користе # апликације}other{Користи # апликација}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Прикажи све на контролној табли"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Филтрирано према: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Дозволи само приступ медијима"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Дозволи увек"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Дозв. само док се апл. користи"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Дозволи све слике"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Дозволи изабране слике"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Питај сваки пут"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Не дозволи"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Прецизна локација"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Није дозвољено"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Прикажи још апликација са приступом свим фајловима"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 дан}one{# дан}few{# дана}other{# дана}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 сат}one{# сат}few{# сата}other{# сати}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 минут}one{# минут}few{# минута}other{# минута}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 секунда}one{# секунда}few{# секунде}other{# секунди}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# сат}one{# сат}few{# сата}other{# сати}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# минут}one{# минут}few{# минута}other{# минута}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# секунда}one{# секунда}few{# секунде}other{# секунди}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Подсетници за дозволе"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 апликација која се не користи"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Апликација које се не користе: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Желите да подесите апликацију <xliff:g id="APP_NAME">%1$s</xliff:g> као подразумевану апликацију за навигацију?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Није потребна ниједна дозвола"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> ће добити дозволу за интеракцију са обавештењима и приступ дозволама за телефон, SMS поруке, контакте и календар."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> ће добити дозволу за интеракцију са обавештењима и приступ дозволама за телефон, SMS, контакте, микрофон и уређаје у близини."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> ће добити дозволу за интеракцију са обавештењима и стримовање апликација на повезаном уређају."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> ће моћи да стримује садржај на уређајима у близини."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ова услуга дели слике, медијски садржај и обавештења са телефона на другим уређајима."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Тренутно подразумевана"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Не питај поново"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Подеси као подразум."</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Приступ сликама, видеу, музици, звуку и другом на уређају за &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Дозвољавате ли приступ музици и звуку на овом уређају за &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Дозвољавате ли приступ сликама и видеу на овом уређају за &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Желите ли да апликацији &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; дозволите приступ већем броју слика?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Желите да дозволите да &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; снима звук?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Апликација ће моћи да снима звук само док користите апликацију"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Желите да дозволите да &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; снима звук?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Приказује поруку када апликације приступају тексту, сликама или другом садржају који сте копирали"</string>
<string name="show_password_title" msgid="2877269286984684659">"Приказуј лозинке"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Приказује знакове накратко док куцате"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Ова апликација наводи да може да дели податке (<xliff:g id="PERMISSION_NAME">%s</xliff:g>) са трећим странама"</string>
</resources>
diff --git a/PermissionController/res/values-sv/strings.xml b/PermissionController/res/values-sv/strings.xml
index e78447e25..92818e4cf 100644
--- a/PermissionController/res/values-sv/strings.xml
+++ b/PermissionController/res/values-sv/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Behåll När appen används"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Behåll Bara den här gången"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Mer info"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Ge åtkomst till alla foton"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Välj foton"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Välj fler foton"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Välj inte fler foton"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Tillåt inte ändå"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Stäng"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> av <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 dag}other{# dagar}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 timme}other{# timmar}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}other{# min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sek}other{# sek}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# dag}other{# dagar}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# timme}other{# timmar}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# min}other{# min}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sek}other{# sek}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Alla behörigheter"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"När som helst"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Senaste 7 dagarna"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"De senaste 24 timmarna"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Senaste timmen"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"De senaste 15 minuterna"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Senaste minuten"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Senaste # dagen}other{Senaste # dagarna}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Senaste # timmen}other{Senaste # timmarna}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Senaste # minuten}other{Senaste # minuterna}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Ingen behörighetsanvändning"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Den senaste åtkomsten när som helst"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Den senaste åtkomsten under de senaste sju dagarna"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Behörighetsanvändning under den senaste timmen"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Behörighetsanvändning under den senaste kvarten"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Behörighetsanvändning under den senaste minuten"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Har inte använts under de senaste 24 timmarna"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Har inte använts under de senaste sju dagarna"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Har inte använts under den senaste # dagen}other{Har inte använts under de senaste # dagarna}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Har inte använts under den senaste # timmen}other{Har inte använts under de senaste # timmarna}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Används av 1 app}other{Används av # appar}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Visa alla i översikten"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtreras efter: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Tillåt endast åtkomst till media"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Tillåt alltid"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Tillåt bara när appen används"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Tillåt alla foton"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Tillåt utvalda foton"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Fråga varje gång"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Tillåt inte"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Exakt plats"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Tillåts inte"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Se fler appar som kan komma åt alla filer"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 dag}other{# dagar}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 timme}other{# timmar}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minut}other{# minuter}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 sekund}other{# sekunder}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# timme}other{# timmar}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minut}other{# minuter}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# sekund}other{# sekunder}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Behörighetspåminnelser"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 app som inte används"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> appar som inte används"</string>
@@ -348,7 +352,7 @@
<string name="accessibility_service_dialog_bottom_text_multiple" msgid="7009848932395519852">"De här apparna får visa din skärm, dina åtgärder och inmatningar, utföra åtgärder och styra skärmen."</string>
<string name="role_assistant_label" msgid="4727586018198208128">"Digital assistentapp, standard"</string>
<string name="role_assistant_short_label" msgid="3369003713187703399">"Digital assistentapp"</string>
- <string name="role_assistant_description" msgid="6622458130459922952">"Med assistentappar kan du få hjälp som baseras på den information som visas på den aktuella skärmen. Vissa appar har stöd för både översikts- och röstinmatningstjänster för att hjälpa dig."</string>
+ <string name="role_assistant_description" msgid="6622458130459922952">"Med assistentappar kan du få hjälp av som baseras på den information som visas på den aktuella skärmen. Vissa appar har stöd för både översikts- och röstinmatningstjänster för att hjälpa dig."</string>
<string name="role_browser_label" msgid="2877796144554070207">"Standard webbläsarapp"</string>
<string name="role_browser_short_label" msgid="6745009127123292296">"Webbläsarapp"</string>
<string name="role_browser_description" msgid="3465253637499842671">"Appar som visar länkar du trycker på och du använder för att ansluta till internet"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Vill du ställa in <xliff:g id="APP_NAME">%1$s</xliff:g> som standardapp för navigering?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Inga behörigheter krävs"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> får behörighet att interagera med dina aviseringar och komma åt behörigheterna för Telefon, Sms, Kontakter och Kalender."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> får interagera med dina aviseringar och får åtkomst till behörigheterna Telefon, Sms, Kontakter, Mikrofon och Enheter i närheten."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> får behörighet att interagera med dina aviseringar och streama dina appar till den anslutna enheten."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> får tillåtelse att streama innehåll till enheter i närheten."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Den här tjänsten delar dina foton, videor och aviseringar på telefonen med andra enheter."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Nuvarande standardapp"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Fråga inte igen"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Ange som standard"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Vill du ge &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; åtkomst till &lt;b&gt;foton, videor, musik, ljud och andra filer&lt;/b&gt; på enheten?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Vill du ge &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; åtkomst till musik och ljud på enheten?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Vill du ge &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; åtkomst till foton och videor på enheten?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Vill du ge &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; åtkomst till fler foton?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Vill du ge &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; behörighet att spela in ljud?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Appen kan bara spela in ljud medan du använder den"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Vill du ge &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; behörighet att spela in ljud?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Visa ett meddelande när appar får åtkomst till text, bilder eller annat som du har kopierat"</string>
<string name="show_password_title" msgid="2877269286984684659">"Visa lösenord"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Visa tecknen en kort stund medan du skriver"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Appen har angett att den kan dela <xliff:g id="PERMISSION_NAME">%s</xliff:g>-data med tredje part"</string>
</resources>
diff --git a/PermissionController/res/values-sw/strings.xml b/PermissionController/res/values-sw/strings.xml
index 13080affe..33a294075 100644
--- a/PermissionController/res/values-sw/strings.xml
+++ b/PermissionController/res/values-sw/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Usibadilishe “Wakati programu inatumika”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Usibadilishe “Wakati huu pekee”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Maelezo zaidi"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Ruhusu picha zote zifikiwe"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Chagua picha"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Chagua picha zaidi"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Usichague picha zaidi"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Usiruhusu hata hivyo"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Ondoa"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> kati ya <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{Siku moja}other{Siku #}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{Saa moja}other{Saa #}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{Dak 1}other{Dak #}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{Sek 1}other{Sek #}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{Siku #}other{Siku #}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{Saa #}other{Saa #}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{Dak #}other{Dak #}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{Sek #}other{Sek #}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Ruhusa yoyote"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Wakati wowote"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Siku 7 zilizopita"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Saa 24 zilizopita"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Saa 1 iliyopita"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Dakika 15 zilizopita"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Dakika 1 iliyopita"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Siku # iliyopita}other{Siku # zilizopita}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Saa # iliyopita}other{Saa # zilizopita}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Dakika # iliyopita}other{Dakika # zilizopita}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Hakuna matumizi ya ruhusa"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Zinazotumia zaidi wakati wowote"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Zilizotumiwa zaidi katika siku 7 zilizopita"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Ruhusa zilizotumiwa zaidi katika saa 1 iliyopita"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Matumizi ya ruhusa katika dakika 15 zilizopita."</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Matumizi ya ruhusa katika dakika 1 iliyopita"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Haijatumiwa katika saa 24 zilizopita"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Haijatumiwa katika siku saba zilizopita"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Haijatumiwa katika siku # iliyopita}other{Haijatumiwa katika siku # zilizopita}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Haijatumiwa katika saa # iliyopita}other{Haijatumiwa katika saa # zilizopita}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Imetumiwa na programu moja}other{Imetumiwa na programu #}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Yaone yote kwenye Dashibodi"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Zilizochujwa kulingana na: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Ruhusu ufikiaji wa maudhui pekee"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Ruhusu kila wakati"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Ruhusu tu unapotumia programu"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Ruhusu picha zote"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Ruhusu picha zilizochaguliwa"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Uliza kila wakati"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Usiruhusu"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Eneo mahususi"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Zisizoruhusiwa"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Angalia programu zaidi zinazoweza kufikia faili zote"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{Siku moja}other{Siku #}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{Saa moja}other{Saa #}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{Dakika moja}other{Dakika #}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{Sekunde 1}other{Sekunde #}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{Saa #}other{Saa #}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{Dakika #}other{Dakika #}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{Sekunde #}other{Sekunde #}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Vikumbusho vya ruhusa"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"Programu moja isiyotumika"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Programu <xliff:g id="NUMBER_OF_APPS">%s</xliff:g> zisizotumika"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Je, ungependa kuweka <xliff:g id="APP_NAME">%1$s</xliff:g> iwe programu yako chaguomsingi ya maelekezo?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Hakuna ruhusa zinazohitajika"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> itaruhusiwa kushirikiana na arifa zako na kufikia ruhusa za Simu, SMS, Anwani na Kalenda yako."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> itaruhusiwa kutumia arifa zako na kufikia ruhusa zako za Simu, SMS, Anwani, Maikrofoni na Vifaa vilivyo Karibu."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> itaruhusiwa kufikia arifa zako na kutiririsha programu zako kwenye kifaa kilichounganishwa."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> itaruhusiwa kutiririsha maudhui kwenye vifaa vilivyo karibu."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Huduma hii inashiriki picha, maudhui na arifa zako kutoka kwenye simu yako hadi vifaa vingine."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Chaguomsingi ya sasa"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Isiniulize tena"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Weka iwe chaguomsingi"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Ungependa kuruhusu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ifikie &lt;b&gt;picha, video, muziki, sauti na faili zingine&lt;/b&gt; kwenye kifaa hiki?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Ungependa kuruhusu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ifikie muziki na sauti kwenye kifaa hiki?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Ungependa kuruhusu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ifikie picha na video kwenye kifaa hiki?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Ungependa kuipatia &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; idhini ya kufikia picha zaidi?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Ungependa kuruhusu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; kurekodi sauti?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Programu itaweza kurekodi sauti unapoitumia tu"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Ungependa kuiruhusu &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; irekodi sauti?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Onyesha ujumbe programu zinapofikia maandishi, picha au maudhui mengine uliyonakili"</string>
<string name="show_password_title" msgid="2877269286984684659">"Onyesha manenosiri"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Onyesha herufi kwa muda mfupi unapoandika"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Maelezo ya programu yanasema kuwa inaweza kushiriki data ya <xliff:g id="PERMISSION_NAME">%s</xliff:g> na kampuni za wengine"</string>
</resources>
diff --git a/PermissionController/res/values-ta/strings.xml b/PermissionController/res/values-ta/strings.xml
index d5c87a0c3..22588ea61 100644
--- a/PermissionController/res/values-ta/strings.xml
+++ b/PermissionController/res/values-ta/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“ஆப்ஸ் உபயோகத்தில் இருக்கும்போது” என்று வைக்கவும்"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"”இப்போது மட்டும்” வைத்திரு"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"மேலும் தகவல்"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"அனைத்துப் படங்களையும் அணுக அனுமதி"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"படங்களைத் தேர்ந்தெடு"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"மேலும் படங்களைத் தேர்ந்தெடு"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"மேலும் படங்களைத் தேர்ந்தெடுக்க வேண்டாம்"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"பரவாயில்லை, அனுமதிக்க வேண்டாம்"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"நிராகரி"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> / <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 நாள்}other{# நாட்கள்}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 மணிநேரம்}other{# மணிநேரம்}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 நிமிடம்}other{# நிமிடங்கள்}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 வினாடி}other{# வினாடிகள்}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# நாள்}other{# நாட்கள்}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# மணிநேரம்}other{# மணிநேரம்}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# நிமிடம்}other{# நிமிடங்கள்}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# வினாடி}other{# வினாடிகள்}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"அனுமதி எதுவாயினும்"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"எந்த நேரமும்"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"கடந்த 7 நாட்கள்"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"கடந்த 24 மணிநேரம்"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"கடந்த ஒரு மணிநேரம்"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"கடந்த 15 நிமிடங்கள்"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"கடந்த 1 நிமிடத்தில்"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{கடந்த # நாள்}other{கடந்த # நாட்கள்}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{கடந்த # மணிநேரம்}other{கடந்த # மணிநேரம்}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{கடந்த # நிமிடம்}other{கடந்த # நிமிடங்கள்}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"உபயோகிக்கப்படாத அனுமதிகள்"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"மிகச் சமீபத்திய அணுகல்"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"கடந்த 7 நாட்களில் மிகச் சமீபத்திய அணுகல்"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"கடந்த 1 மணிநேரத்தில் அணுகல் உபயோகம்"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"கடந்த 15 நிமிடங்களில் அணுகல் உபயோகம்"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"கடந்த 1 நிமிடத்தில் அணுகல் உபயோகம்"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"கடந்த 24 மணிநேரத்தில் பயன்படுத்தப்படவில்லை"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"கடந்த 7 நாட்களில் பயன்படுத்தப்படவில்லை"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{கடந்த # நாளில் பயன்படுத்தப்படவில்லை}other{கடந்த # நாட்களில் பயன்படுத்தப்படவில்லை}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{கடந்த # மணிநேரத்தில் பயன்படுத்தப்படவில்லை}other{கடந்த # மணிநேரத்தில் பயன்படுத்தப்படவில்லை}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 ஆப்ஸால் பயன்படுத்தப்பட்டது}other{# ஆப்ஸால் பயன்படுத்தப்பட்டது}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"டாஷ்போர்டில் அனைத்தையும் காட்டு"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"இதன்படி வடிகட்டப்பட்டது: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"மீடியா ஃபைல்களை மட்டும் அணுக அனுமதி"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"எப்போதும் அனுமதி"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"ஆப்ஸை உபயோகிக்கும்போது மட்டும் அனுமதி"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"அனைத்துப் படங்களையும் அனுமதி"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"தேர்ந்தெடுக்கப்பட்ட படங்களை அனுமதி"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"ஒவ்வொரு முறையும் கேள்"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"அனுமதிக்க வேண்டாம்"</string>
<string name="precise_image_description" msgid="6349638632303619872">"துல்லியமான இருப்பிடம்"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"அனுமதிக்கப்படாதவை"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"அனைத்து ஃபைல்களையும் அணுகக்கூடிய கூடுதல் ஆப்ஸைப் பாருங்கள்"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 நாள்}other{# நாட்கள்}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 மணிநேரம்}other{# மணிநேரம்}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 நிமிடம்}other{# நிமிடங்கள்}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 வினாடி}other{# வினாடிகள்}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# மணிநேரம்}other{# மணிநேரம்}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# நிமிடம்}other{# நிமிடங்கள்}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# வினாடி}other{# வினாடிகள்}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"அனுமதிக்கான நினைவூட்டல்கள்"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 பயன்படுத்தாத ஆப்ஸ்"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> பயன்படுத்தாத ஆப்ஸ்"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸை உங்கள் வழிகாட்டும் இயல்புநிலை ஆப்ஸாக அமைக்கவா?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"அனுமதிகள் தேவையில்லை"</string>
<string name="role_watch_description" msgid="267003778693177779">"உங்கள் அறிவிப்புகளைப் பயன்படுத்துவதற்கான அனுமதியையும் மொபைல், மெசேஜ், தொடர்புகள், கேலெண்டர் ஆகியவற்றின் அனுமதிகளுக்கான அணுகலையும் <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் பெறும்."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"உங்கள் மொபைல், மெசேஜ், தொடர்புகள், மைக்ரோஃபோன், அருகிலுள்ள சாதனங்கள் ஆகியவற்றுக்கான அணுகலையும் உங்கள் அறிவிப்புகளைப் பார்ப்பதற்கான அனுமதியையும் <xliff:g id="APP_NAME">%1$s</xliff:g> பெறும்."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"உங்கள் அறிவிப்புகளைப் பயன்படுத்துவதற்கான அனுமதியையும் இணைக்கப்பட்டுள்ள சாதனத்தில் உங்கள் ஆப்ஸை ஸ்ட்ரீம் செய்யவும் <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் அனுமதிக்கப்படும்."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"அருகிலுள்ள சாதனங்களில் உள்ளடக்கத்தை ஸ்ட்ரீம் செய்ய <xliff:g id="APP_NAME">%1$s</xliff:g> ஆப்ஸ் அனுமதிக்கப்படும்."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"இந்தச் சேவை உங்கள் படங்கள், மீடியா, அறிவிப்புகள் ஆகியவற்றை மொபைலிலிருந்து பிற சாதனங்களுக்குப் பகிரும்."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"தற்போதைய இயல்பான ஆப்ஸ்"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"மீண்டும் கேட்காதே"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"இயல்பு ஆப்ஸாக அமை"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"சாதனத்திலுள்ள &lt;b&gt;படம், வீடியோ, இசை, ஆடியோ &amp; பிற ஃபைல்களின்&lt;/b&gt; அணுகலை &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ஆப்ஸுக்கு வழங்கவா?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"இந்தச் சாதனத்திலுள்ள இசை மற்றும் ஆடியோவுக்கான அணுகலை &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ஆப்ஸுக்கு வழங்கவா?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"இந்தச் சாதனத்திலுள்ள படங்கள் மற்றும் வீடியோக்களுக்கான அணுகலை &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ஆப்ஸுக்கு வழங்கவா?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"மேலும் படங்களை அணுக &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ஆப்ஸை அனுமதிக்கவா?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"ஆடியோ ரெக்கார்டு செய்ய &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ஆப்ஸை அனுமதிக்கவா?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"இந்த ஆப்ஸை நீங்கள் உபயோகிக்கும்போது மட்டுமே ஆடியோ ரெக்கார்டு செய்யும்"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"ஆடியோ ரெக்கார்டு செய்ய &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ஆப்ஸை அனுமதிக்கவா?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"நீங்கள் நகலெடுத்த வார்த்தைகளையோ படங்களையோ பிறவற்றையோ ஆப்ஸ் அணுகும்போது ஓர் அறிவிப்பைக் காட்டும்"</string>
<string name="show_password_title" msgid="2877269286984684659">"கடவுச்சொற்களைக் காட்டுதல்"</string>
<string name="show_password_summary" msgid="1110166488865981610">"டைப் செய்யும்போதே எழுத்துகளைச் சற்று நேரம் காட்டும்"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"இந்த ஆப்ஸில் குறிப்பிட்டுள்ளபடி மூன்றாம் தரப்பினருடன் <xliff:g id="PERMISSION_NAME">%s</xliff:g> தரவை இது பகிரலாம்"</string>
</resources>
diff --git a/PermissionController/res/values-te/strings.xml b/PermissionController/res/values-te/strings.xml
index 873a011eb..e66166f73 100644
--- a/PermissionController/res/values-te/strings.xml
+++ b/PermissionController/res/values-te/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“యాప్ వినియోగంలో ఉన్నప్పుడు” నిలిపి ఉంచు"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“కేవలం ఈసారి మాత్రమే” ఇలాగే ఉంచు"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"మరింత సమాచారం"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"ఫోటోలన్నింటికీ యాక్సెస్‌ను అనుమతించండి"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"ఫోటోలను ఎంచుకోండి"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"మరిన్ని ఫోటోలను ఎంచుకోండి"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"మరిన్ని ఫోటోలను ఎంచుకోవద్దు"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"ఏదేమైనా అనుమతించవద్దు"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"విస్మరించు"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> యొక్క <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 రోజు}other{# రోజులు}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 గంట}other{# గంటలు}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 నిమి}other{# నిమిషాలు}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 సెకను}other{# సెకన్లు}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# రోజు}other{# రోజులు}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# గంట}other{# గంటలు}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# నిమి}other{# నిమిషాలు}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# సెక}other{# సెకన్లు}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ఏ అనుమతి అయినా"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ఎప్పుడైనా"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"గత 7 రోజులు"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"గత 24 గంటలు"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"గత 1 గంట"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"గత 15 నిమిషాలు"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"చివరి 1 నిమిషం"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{గడిచిన # రోజులో}other{గడిచిన # రోజులలో}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{గడిచిన # గంటలో}other{గడిచిన # గంటలలో}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{గడిచిన # నిమిషంలో}other{గడిచిన # నిమిషాలలో}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"అనుమతి వినియోగాలేవీ లేవు"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"ఏ సమయంలోనైనా అత్యంత ఇటీవలి యాక్సెస్"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"గత 7 రోజులలో అత్యంత ఇటీవలి యాక్సెస్‌లు"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"గడిచిన ఒక గంటలో అనుమతి వినియోగించబడింది"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"గత 15 నిమిషాలలో అనుమతి వినియోగించబడింది"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"గత నిమిషంలో అనుమతి వినియోగించబడింది"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"గత 24 గంటలలో ఉపయోగించలేదు"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"గత 7 రోజుల్లో ఉపయోగించలేదు"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{గత # రోజులో ఉపయోగించలేదు}other{గత # రోజుల్లో ఉపయోగించలేదు}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{గత # గంటలో ఉపయోగించలేదు}other{గత # గంటలలో ఉపయోగించలేదు}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 యాప్ ద్వారా ఉపయోగించబడింది}other{# యాప్‌ల ద్వారా ఉపయోగించబడింది}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"అన్నింటినీ డాష్‌బోర్డ్‌లో చూడండి"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"దీని ద్వారా ఫిల్టర్ చేయబడింది: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"మీడియాకు మాత్రమే యాక్సెస్‌ను అనుమతించండి"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"ఎల్లప్పుడూ అనుమతించండి"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"యాప్‌ను ఉపయోగిస్తున్నప్పుడు మాత్రమే అనుమతించండి"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"ఫోటోలన్నింటినీ అనుమతించండి"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"ఎంచుకోబడిన ఫోటోలను అనుమతించండి"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"ప్రతిసారి అడగాలి"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"అనుమతించవద్దు"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ఖచ్చితమైన లొకేషన్"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"అనుమతించబడలేదు"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"అన్ని ఫైళ్లను యాక్సెస్ చేయగల మరిన్ని యాప్‌లను చూడండి"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 రోజు}other{# రోజులు}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 గంట}other{# గంటలు}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 నిమిషం}other{# నిమిషాలు}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 సెకను}other{# సెకన్లు}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# గంట}other{# గంటలు}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# నిమిషం}other{# నిమిషాలు}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# సెకను}other{# సెకన్లు}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"అనుమతి రిమైండర్‌లు"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ఉపయోగించని యాప్‌"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ఉపయోగించని యాప్‌లు"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> యాప్‌ను మీ ఆటోమేటిక్ సెట్టింగ్ నావిగేషన్ యాప్‌గా సెటప్ చేయాలా?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"అనుమతులు అవసరం లేదు"</string>
<string name="role_watch_description" msgid="267003778693177779">"మీ నోటిఫికేషన్‌లతో ఇంటరాక్ట్ అవ్వడానికి అలాగే మీ ఫోన్, SMS, కాంటాక్ట్‌లు అలాగే Calendar అనుమతులను యాక్సెస్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> అనుమతించబడుతుంది."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> మీ నోటిఫికేషన్‌లతో ఇంటరాక్ట్ అవ్వడానికి, అలాగే మీ ఫోన్, SMS, కాంటాక్ట్‌లు, మైక్రోఫోన్, సమీపంలోని పరికరాల అనుమతులను యాక్సెస్ చేయడానికి అనుమతించబడుతుంది."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"మీ నోటిఫికేషన్‌లతో ఇంటరాక్ట్ అవ్వడానికి, కనెక్ట్ అయిన పరికరానికి మీ యాప్‌లను స్ట్రీమ్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> అనుమతించబడుతుంది."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"సమీపంలోని పరికరాలకు కంటెంట్‌ను స్ట్రీమ్ చేయడానికి <xliff:g id="APP_NAME">%1$s</xliff:g> అనుమతించబడుతుంది."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"ఈ సర్వీస్ మీ ఫోటోలు, మీడియా, ఇంకా నోటిఫికేషన్‌లను మీ ఫోన్ నుండి ఇతర పరికరాలకు షేర్ చేస్తుంది."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"ప్రస్తుతం ఆటోమేటిక్‌గా ఉంది"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"మళ్లీ అడగవద్దు"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ఆటోమేటిక్ చేయండి"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"ఈపరికరంలో &lt;b&gt;ఫోటోలు, వీడియోలు, మ్యూజిక్, ఆడియో, ఇతర ఫైళ్ల&lt;/b&gt; యాక్సెస్‌కు &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ను అనుమతించాలా?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"ఈ పరికరంలో మ్యూజిక్‌ను, ఆడియోను యాక్సెస్ చేయడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;‌ను అనుమతించాలా?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"ఈ పరికరంలో ఫోటోలను, వీడియోలను యాక్సెస్ చేయడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;‌ను అనుమతించాలా?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;‌కు మరిన్ని ఫోటోలను యాక్సెస్ చేయడానికి అనుమతి ఇవ్వాలా?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"ఆడియోను రికార్డ్ చేయడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ను అనుమతించాలా?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"మీరు యాప్‌ను ఉపయోగిస్తున్నప్పుడు మాత్రమే ఈ యాప్, ఆడియోను రికార్డ్ చేయగలుగుతుంది"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"ఆడియోను రికార్డ్ చేయడానికి &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;ను అనుమతించాలా?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"మీరు కాపీ చేసిన టెక్స్ట్, ఇమేజ్‌లను లేదా ఇతర కంటెంట్‌ను యాప్‌లు యాక్సెస్ చేసినప్పుడు మెసేజ్‌ను చూపుతుంది"</string>
<string name="show_password_title" msgid="2877269286984684659">"పాస్‌వర్డ్‌లను చూపిస్తుంది"</string>
<string name="show_password_summary" msgid="1110166488865981610">"మీరు టైప్ చేస్తున్నప్పుడు అక్షరాలను క్లుప్తంగా చూపిస్తుంది"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"ఈ యాప్, అది <xliff:g id="PERMISSION_NAME">%s</xliff:g> డేటాను థర్డ్-పార్టీలతో షేర్ చేయవచ్చని పేర్కొంది"</string>
</resources>
diff --git a/PermissionController/res/values-th/strings.xml b/PermissionController/res/values-th/strings.xml
index 3d1339249..2da2a75d3 100644
--- a/PermissionController/res/values-th/strings.xml
+++ b/PermissionController/res/values-th/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"คงไว้ที่ “เมื่อมีการใช้แอป”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"คงไว้ที่ “เฉพาะครั้งนี้”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"ข้อมูลเพิ่มเติม"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"ให้สิทธิ์เข้าถึงรูปภาพทั้งหมด"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"เลือกรูปภาพ"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"เลือกรูปภาพเพิ่มเติม"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"อย่าเลือกรูปภาพเพิ่มเติม"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"ยังคงไม่อนุญาต"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"ปิด"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> จาก <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> รายการ"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 วัน}other{# วัน}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 ชั่วโมง}other{# ชั่วโมง}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 นาที}other{# นาที}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 วินาที}other{# วินาที}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# วัน}other{# วัน}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# ชั่วโมง}other{# ชั่วโมง}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# นาที}other{# นาที}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# วินาที}other{# วินาที}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"ทุกสิทธิ์"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"ทุกเวลา"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"7 วันที่ผ่านมา"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"24 ชั่วโมงที่ผ่านมา"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"1 ชั่วโมงที่ผ่านมา"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"15 นาทีที่ผ่านมา"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"1 นาทีที่ผ่านมา"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{# วันที่ผ่านมา}other{# วันที่ผ่านมา}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{# ชั่วโมงที่ผ่านมา}other{# ชั่วโมงที่ผ่านมา}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{# นาทีที่ผ่านมา}other{# นาทีที่ผ่านมา}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"ไม่มีการใช้สิทธิ์"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"การเข้าถึงล่าสุดทั้งหมด"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"การเข้าถึงล่าสุดในช่วง 7 วันที่ผ่านมา"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"การใช้สิทธิ์ในช่วง 1 ชั่วโมงที่ผ่านมา"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"การใช้สิทธิ์ในช่วง 15 นาทีที่ผ่านมา"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"การใช้สิทธิ์ในช่วง 1 นาทีที่ผ่านมา"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"ไม่ได้ใช้ใน 24 ชั่วโมงที่ผ่านมา"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"ไม่ได้ใช้ใน 7 วันที่ผ่านมา"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{ไม่ได้ใช้ใน # วันที่ผ่านมา}other{ไม่ได้ใช้ใน # วันที่ผ่านมา}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{ไม่ได้ใช้ใน # ชั่วโมงที่ผ่านมา}other{ไม่ได้ใช้ใน # ชั่วโมงที่ผ่านมา}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{ใช้โดยแอป 1 แอป}other{ใช้โดยแอป # แอป}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"ดูทั้งหมดในหน้าแดชบอร์ด"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"กรองตาม: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"อนุญาตให้เข้าถึงสื่อเท่านั้น"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"อนุญาตตลอด"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"อนุญาตขณะมีการใช้แอปเท่านั้น"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"อนุญาตรูปภาพทั้งหมด"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"อนุญาตรูปภาพที่เลือก"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"ถามทุกครั้ง"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"ไม่อนุญาต"</string>
<string name="precise_image_description" msgid="6349638632303619872">"ตำแหน่งที่แน่นอน"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"ไม่อนุญาต"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"ดูแอปอื่นๆ ที่สามารถเข้าถึงไฟล์ทั้งหมดได้"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 วัน}other{# วัน}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 ชั่วโมง}other{# ชั่วโมง}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 นาที}other{# นาที}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 วินาที}other{# วินาที}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# ชั่วโมง}other{# ชั่วโมง}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# นาที}other{# นาที}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# วินาที}other{# วินาที}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"การช่วยเตือนเกี่ยวกับสิทธิ์"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"แอปที่ไม่ได้ใช้ 1 รายการ"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"แอปที่ไม่ได้ใช้ <xliff:g id="NUMBER_OF_APPS">%s</xliff:g> รายการ"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"ตั้งค่า <xliff:g id="APP_NAME">%1$s</xliff:g> เป็นแอปเริ่มต้นสำหรับการนำทางไหม"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"ไม่ต้องใช้สิทธิ์"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับการแจ้งเตือนและได้รับสิทธิ์เข้าถึงโทรศัพท์, SMS, รายชื่อติดต่อ และปฏิทิน"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับการแจ้งเตือนและได้รับสิทธิ์เข้าถึงโทรศัพท์, SMS, รายชื่อติดต่อ, ไมโครโฟน และอุปกรณ์ที่อยู่ใกล้เคียง"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> จะได้รับอนุญาตให้โต้ตอบกับการแจ้งเตือนและสตรีมแอปไปยังอุปกรณ์ที่เชื่อมต่อ"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> จะได้รับอนุญาตให้สตรีมเนื้อหาไปยังอุปกรณ์ที่อยู่ใกล้เคียง"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"บริการนี้แชร์รูปภาพ สื่อ และการแจ้งเตือนจากโทรศัพท์ของคุณไปยังอุปกรณ์อื่น"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"แอปเริ่มต้นปัจจุบัน"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"ไม่ต้องถามอีก"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"ตั้งเป็นแอปเริ่มต้น"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"อนุญาตให้ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; เข้าถึง&lt;b&gt;รูปภาพ วิดีโอ เพลง เสียง และไฟล์อื่นๆ&lt;/b&gt; ในอุปกรณ์นี้ไหม"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"อนุญาตให้ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; เข้าถึงเพลงและเสียงในอุปกรณ์นี้ไหม"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"อนุญาตให้ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; เข้าถึงรูปภาพและวิดีโอในอุปกรณ์นี้ไหม"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"ให้สิทธ์ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; เข้าถึงรูปภาพเพิ่มเติมไหม"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"อนุญาตให้ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; บันทึกเสียงไหม"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"แอปจะบันทึกเสียงได้ในขณะที่คุณใช้แอปอยู่เท่านั้น"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"อนุญาตให้ &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; บันทึกเสียงไหม"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"แสดงข้อความเมื่อแอปเข้าถึงข้อความ รูปภาพ หรือเนื้อหาอื่นๆ ที่คุณคัดลอก"</string>
<string name="show_password_title" msgid="2877269286984684659">"แสดงรหัสผ่าน"</string>
<string name="show_password_summary" msgid="1110166488865981610">"แสดงอักขระชั่วครู่ขณะพิมพ์"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"แอปนี้ระบุว่าแอปอาจแชร์ข้อมูล <xliff:g id="PERMISSION_NAME">%s</xliff:g> กับองค์กรบุคคลที่สามของแอป"</string>
</resources>
diff --git a/PermissionController/res/values-tl/strings.xml b/PermissionController/res/values-tl/strings.xml
index a22d0b276..222676585 100644
--- a/PermissionController/res/values-tl/strings.xml
+++ b/PermissionController/res/values-tl/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Panatilihin ang “Habang ginagamit ang app”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Panatilihing “Sa pagkakataong ito lang”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Higit pang info"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Magbigay ng access sa lahat ng larawan"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Pumili ng mga larawan"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Pumili ng higit pang larawan"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Huwag pumili ng higit pang larawan"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Huwag pa ring payagan"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"I-dismiss"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> sa <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 araw}one{# araw}other{# na araw}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 oras}one{# oras}other{# na oras}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 min}one{# min}other{# na min}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 segundo}one{# segundo}other{# na segundo}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# araw}one{# araw}other{# na araw}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# oras}one{# oras}other{# na oras}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# minuto}one{# minuto}other{# na minuto}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# segundo}one{# segundo}other{# na segundo}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Anumang pahintulot"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Anumang oras"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Nakalipas na 7 araw"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Nakalipas na 24 na oras"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Nakalipas na 1 oras"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Nakalipas na 15 minuto"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Nakalipas na 1 minuto"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Nakalipas na # araw}one{Nakalipas na # araw}other{Nakalipas na # na araw}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Nakalipas na # oras}one{Nakalipas na # oras}other{Nakalipas na # na oras}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Nakalipas na # minuto}one{Nakalipas na # minuto}other{Nakalipas na # na minuto}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Walang paggamit ng pahintulot"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Pinakakamakailang pag-access anumang oras"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Pinakakamakailang pag-access sa nakaraang 7 araw"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Paggamit ng pahintulot sa loob ng nakaraang 1 oras"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Paggamit ng pahintulot sa nakaraang 15 minuto"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Paggamit ng pahintulot sa nakaraang 1 minuto"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Hindi ginamit sa nakalipas na 24 na oras"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Hindi ginamit sa nakalipas na 7 araw"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Hindi ginamit sa nakalipas na # araw}one{Hindi ginamit sa nakalipas na # araw}other{Hindi ginamit sa nakalipas na # na araw}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Hindi ginamit sa nakalipas na # oras}one{Hindi ginamit sa nakalipas na # oras}other{Hindi ginamit sa nakalipas na # na oras}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Ginagamit ng 1 app}one{Ginagamit ng # app}other{Ginagamit ng # na app}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Tingnan lahat sa Dashboard"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Na-filter ng: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Payagan lang ang pag-access ng media"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Payagan sa lahat ng oras"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Payagan lang habang ginagamit ang app"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Payagan ang lahat ng larawan"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Payagan ang mga piniling larawan"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Magtanong palagi"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Huwag payagan"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Eksaktong lokasyon"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Hindi pinapayagan"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Tumingin pa ng mga app na puwedeng mag-access ng lahat ng file"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 araw}one{# araw}other{# na araw}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 oras}one{# oras}other{# na oras}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 minuto}one{# minuto}other{# na minuto}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 segundo}one{# segundo}other{# na segundo}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# oras}one{# oras}other{# na oras}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# minuto}one{# minuto}other{# na minuto}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# segundo}one{# segundo}other{# na segundo}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Mga paalala sa pahintulot"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 hindi ginagamit na app"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> (na) hindi ginagamit na app"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Itakda ang <xliff:g id="APP_NAME">%1$s</xliff:g> bilang iyong default na app sa pag-navigate?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Walang kailangang pahintulot"</string>
<string name="role_watch_description" msgid="267003778693177779">"Papayagan ang <xliff:g id="APP_NAME">%1$s</xliff:g> na makipag-ugnayan sa iyong mga notification at ma-access ang iyong mga pahintulot sa Telepono, SMS, Mga Contact, at Kalendaryo."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Papayagan ang <xliff:g id="APP_NAME">%1$s</xliff:g> na makipag-ugnayan sa mga notification mo at i-access ang iyong mga pahintulot sa Telepono, SMS, Mga Contact, Mikropono, at Mga kalapit na device."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Papayagan ang <xliff:g id="APP_NAME">%1$s</xliff:g> na makipag-ugnayan sa iyong mga notification at i-stream ang iyong mga app sa nakakonektang device."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Papayagan ang <xliff:g id="APP_NAME">%1$s</xliff:g> na mag-stream ng content sa mga kalapit na device."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Ibinabahagi ng serbisyong ito ang iyong mga larawan, media, at notification sa ibang device mula sa iyong telepono."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Kasalukuyang default"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Huwag nang itanong muli"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Itakdang default"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Payagan ang &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; na i-access ang &lt;b&gt;mga larawan, video, musika, audio, at iba pang file&lt;/b&gt; sa device?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Payagan ang &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; na i-access ang musika at audio sa device na ito?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Payagan ang &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; na i-access ang mga larawan at video sa device na ito?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Payagan ang access ng &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; sa higit pang larawan?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Payagan ang &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; na mag-record ng audio?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Makakapag-record lang ng audio ang app habang ginagamit mo ang app"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Payagan ang &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; na mag-record ng audio?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Magpakita ng mensahe kapag ina-access ng mga app ang text, mga larawan, o iba pang content na nakopya mo"</string>
<string name="show_password_title" msgid="2877269286984684659">"Ipakita ang mga password"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Ipakita sandali ang mga character habang nagta-type ka"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Isinaad ng app na ito na puwedeng ibahagi nito ang data ng <xliff:g id="PERMISSION_NAME">%s</xliff:g> sa mga third party"</string>
</resources>
diff --git a/PermissionController/res/values-tr/strings.xml b/PermissionController/res/values-tr/strings.xml
index b3223ea4b..c7305ec2a 100644
--- a/PermissionController/res/values-tr/strings.xml
+++ b/PermissionController/res/values-tr/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“Uygulama kullanılırken” tut"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"\"Yalnızca bu defa\" sakla"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Daha fazla bilgi"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Tüm fotoğraflara erişim izni ver"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Fotoğraf seç"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Daha fazla fotoğraf seç"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Daha fazla fotoğraf seçme"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Yine de izin verme"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Kapat"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> / <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 gün}other{# gün}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 saat}other{# saat}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 dk.}other{# dk.}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 sn.}other{# sn.}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# gün}other{# gün}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# saat}other{# saat}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# dk.}other{# dk.}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# sn.}other{# sn.}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Tüm izinler"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Tüm zamanlar"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Son 7 gün"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Son 24 saat"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Son 1 saat"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Son 15 dakika"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Son 1 dakika"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Son # gün}other{Son # gün}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Son # saat}other{Son # saat}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Son # dakika}other{Son # dakika}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"İzin kullanılmadı"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Herhangi bir zamanda gerçekleşen en son erişim"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Son 7 gün içindeki en son erişimler"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Son 1 saat içinde gerçekleşen izin kullanımı"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Son 15 dakika içinde gerçekleştirilen izin kullanımı"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Son 1 dakika içinde gerçekleşen izin kullanımı"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Son 24 saat içinde kullanılmadı"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Son 7 gün içinde kullanılmadı"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Son # gün içinde kullanılmadı}other{Son # gün içinde kullanılmadı}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Son # saat içinde kullanılmadı}other{Son # saat içinde kullanılmadı}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 uygulama tarafından kullanıldı}other{# uygulama tarafından kullanıldı}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Tümünü Kontrol Paneli\'nde göster"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtre ölçütü: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Yalnızca medyaya erişim izni ver"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Her zaman izin ver"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Yalnızca uygulama kullanılırken izin ver"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Tüm fotoğraflara izin ver"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Seçilen fotoğraflara izin ver"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Her zaman sor"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"İzin verme"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Tam konum"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"İzin verilmeyenler"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Tüm dosyalara erişebilen diğer uygulamaları görün"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 gün}other{# gün}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 saat}other{# saat}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 dakika}other{# dakika}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 saniye}other{# saniye}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# saat}other{# saat}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# dakika}other{# dakika}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# saniye}other{# saniye}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"İzin hatırlatıcılar"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 kullanılmayan uygulama"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> kullanılmayan uygulama"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g>, varsayılan navigasyon uygulamanız olarak ayarlansın mı?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Herhangi bir izin gerekli değil"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> adlı uygulamanın bildirimlerinizle etkileşimde bulunup Telefon, SMS, Kişiler ve Takvim izinlerinize erişmesine izin verilir."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> adlı uygulamanın bildirimlerinizle etkileşimde bulunup Telefon, SMS, Kişiler, Mikrofon ve Yakındaki cihazlar izinlerine erişmesine izin verilir."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> adlı uygulamanın bildirimlerinizle etkileşimde bulunup uygulamalarınızı bağlı cihazda canlı oynatmasına izin verilecek."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> adlı uygulamanın yakındaki cihazlarda içerikleri canlı oynatmasına izin verilir."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Bu hizmet, telefonunuzdaki fotoğraf, medya ve bildirimleri diğer cihazlarla paylaşır."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Mevcut varsayılan"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Tekrar sorma"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Varsayılan olarak ayarla"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; için cihazdaki &lt;b&gt;fotoğraf, video, müzik, ses vb. dosyalara&lt;/b&gt; erişim verilsin mi?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasının bu cihazda müzik ve ses dosyalarına erişmesine izin verilsin mi?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasının bu cihazdaki fotoğraf ve videolara erişmesine izin verilsin mi?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasının daha fazla fotoğrafa erişmesine izin verilsin mi?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasının ses kaydetmesine izin verilsin mi?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Bu uygulama, yalnızca kullanıldığı sırada ses kaydedebilir"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uygulamasının ses kaydetmesine izin verilsin mi?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Uygulamalar kopyaladığınız metne, resimlere veya diğer içeriklere eriştiğinde mesaj gösterilsin."</string>
<string name="show_password_title" msgid="2877269286984684659">"Şifreleri göster"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Yazarken karakterleri kısa süreliğine göster"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Bu uygulama, <xliff:g id="PERMISSION_NAME">%s</xliff:g> verilerini üçüncü taraflarla paylaşabileceğini belirtti"</string>
</resources>
diff --git a/PermissionController/res/values-uk/strings.xml b/PermissionController/res/values-uk/strings.xml
index dfefbe975..86f496b69 100644
--- a/PermissionController/res/values-uk/strings.xml
+++ b/PermissionController/res/values-uk/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Дозволяти, лише коли додаток використовується"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Залишити дозвіл \"Лише цього разу\""</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Докладніше"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Надати доступ до всіх фотографій"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Вибрати фотографії"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Вибрати більше фотографій"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Не вибирати більше фотографій"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Усе одно не дозволяти"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Закрити"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> з <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 день}one{# день}few{# дні}many{# днів}other{# дня}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 година}one{# година}few{# години}many{# годин}other{# години}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 хвилина}one{# хвилина}few{# хвилини}many{# хвилин}other{# хвилини}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 секунда}one{# секунда}few{# секунди}many{# секунд}other{# секунди}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# день}one{# день}few{# дні}many{# днів}other{# дня}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# година}one{# година}few{# години}many{# годин}other{# години}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# хв}one{# хв}few{# хв}many{# хв}other{# хв}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# с}one{# с}few{# с}many{# с}other{# с}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Будь-який дозвіл"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"У будь-який час"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Останні 7 днів"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Останні 24 години"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Остання година"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Останні 15 хвилин"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Остання хвилина"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{За останній # день}one{За останній # день}few{За останні # дні}many{За останні # днів}other{За останні # дня}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{За останню # годину}one{За останню # годину}few{За останні # години}many{За останні # годин}other{За останні # години}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{За останню # хвилину}one{За останню # хвилину}few{За останні # хвилини}many{За останні # хвилин}other{За останні # хвилини}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Дозволи не використовувалися"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Доступ за останній час"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Доступ за останні 7 днів"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Використання дозволів за останню годину"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Використання дозволів за останні 15 хвилин"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Використання дозволів за останню хвилину"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Дозвіл не використовувався протягом останніх 24 годин"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Дозвіл не використовувався протягом останніх 7 днів"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Не використовувався протягом останнього # дня}one{Не використовувався протягом останнього # дня}few{Не використовувався протягом останніх # днів}many{Не використовувався протягом останніх # днів}other{Не використовувався протягом останніх # дня}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Не використовувався протягом останньої # години}one{Не використовувався протягом останньої # години}few{Не використовувався протягом останніх # годин}many{Не використовувався протягом останніх # годин}other{Не використовувався протягом останніх # години}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Використано в 1 додатку}one{Використано в # додатку}few{Використано в # додатках}many{Використано в # додатках}other{Використано в # додатка}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Переглянути деталі на інформаційній панелі"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Відфільтровано за параметром \"<xliff:g id="PERM">%1$s</xliff:g>\""</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Надати доступ лише до медіафайлів"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Дозволяти завжди"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Дозволяти, лише коли додаток використовується"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Дозволити всі фотографії"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Дозволити вибрані фотографії"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Запитувати щоразу"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Не дозволяти"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Точне місцезнаходження"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Заборонено"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Переглянути інші додатки, які мають доступ до всіх файлів"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 день}one{# день}few{# дні}many{# днів}other{# дня}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 година}one{# година}few{# години}many{# годин}other{# години}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 хвилина}one{# хвилина}few{# хвилини}many{# хвилин}other{# хвилини}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 секунда}one{# секунда}few{# секунди}many{# секунд}other{# секунди}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# година}one{# година}few{# години}many{# годин}other{# години}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# хвилина}one{# хвилина}few{# хвилини}many{# хвилин}other{# хвилини}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# секунда}one{# секунда}few{# секунди}many{# секунд}other{# секунди}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Нагадування про дозволи"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 невикористовуваний додаток"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Невикористовуваних додатків: <xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Використовувати <xliff:g id="APP_NAME">%1$s</xliff:g> як навігаційний додаток за умовчанням?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Дозволи не потрібні"</string>
<string name="role_watch_description" msgid="267003778693177779">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> зможе взаємодіяти з вашими сповіщеннями та отримає дозволи \"Телефон\", \"SMS\", \"Контакти\" й \"Календар\"."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> зможе взаємодіяти з вашими сповіщеннями й отримає дозволи \"Телефон\", \"SMS\", \"Контакти\", \"Мікрофон\" і \"Пристрої поблизу\"."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> зможе взаємодіяти з вашими сповіщеннями та транслювати ваші додатки на підключений пристрій."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> зможе транслювати контент на пристрої поблизу."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Цей сервіс передає ваші фотографії, медіафайли та сповіщення з вашого телефона на інші пристрої."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Поточний за умовчанням"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Не запитувати знову"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Вибрати за умовчанням"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Надати додатку &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ до &lt;b&gt;фото, відео, музики, аудіо й інших файлів&lt;/b&gt; на пристрої?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Надати додатку &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ до музики й аудіофайлів на цьому пристрої?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Надати додатку &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ до фотографій і відео на цьому пристрої?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Надати додатку &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; доступ до інших фотографій?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Дозволити додатку &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; записувати аудіо?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Додаток зможе записувати звук, лише коли ви використовуєте його"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Дозволити додатку &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; записувати звук?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"З’являтиметься сповіщення, коли будь-який додаток отримуватиме доступ до скопійованого вами тексту, зображень чи іншого контенту"</string>
<string name="show_password_title" msgid="2877269286984684659">"Показувати паролі"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Ненадовго показувати символи під час введення"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Цей додаток зазначає, що може передавати дані, передбачені дозволом \"<xliff:g id="PERMISSION_NAME">%s</xliff:g>\", третім сторонам"</string>
</resources>
diff --git a/PermissionController/res/values-ur/strings.xml b/PermissionController/res/values-ur/strings.xml
index a53ae34c8..e7a9c961b 100644
--- a/PermissionController/res/values-ur/strings.xml
+++ b/PermissionController/res/values-ur/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"”جب تک ایپ استعمال میں ہے“ رکھیں"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"\"صرف اس وقت\" رکھیں"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"مزید معلومات"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"سبھی تصاویر تک رسائی کی اجازت دیں"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"تصاویر منتخب کریں"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"مزید تصاویر منتخب کریں"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"مزید تصاویر منتخب نہ کریں"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"کسی بھی صورت میں اجازت نہ دیں"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"برخاست کریں"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> میں سے <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 دن}other{# دن}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 گھنٹہ}other{# گھنٹے}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 منٹ}other{# منٹ}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 سیکنڈ}other{# سیکنڈ}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# دن}other{# دن}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# گھنٹہ}other{# گھنٹے}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# منٹ}other{# منٹ}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# سیکنڈ}other{# سیکنڈ}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"کوئی بھی اجازت"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"کسی بھی وقت"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"آخری 7 دن"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"آخری 24 گھنٹے"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"آخری 1 گھنٹہ"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"آخری 15 منٹ"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"آخری 1 منٹ"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{گزشتہ # دن}other{گزشتہ # دن}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{گزشتہ # گھنٹہ}other{گزشتہ # گھنٹے}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{گزشتہ # منٹ}other{گزشتہ # منٹ}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"اجازت کا استعمال نہیں ہوا ہے"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"کسی بھی وقت پر حالیہ ترین رسائی"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"آخری 7 دنوں میں حالیہ ترین رسائی"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"آخری 1 گھنٹے میں اجازت کا استعمال"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"آخری 15 منٹ میں اجازت کا استعمال"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"آخری 1 منٹ میں اجازت کا استعمال"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"گزشتہ 24 گھنٹے میں استعمال نہیں کی گئی"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"گزشتہ 7 دن میں استعمال نہیں کی گئی"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{گزشتہ # دن میں استعمال نہیں کی گئی}other{گزشتہ # دن میں استعمال نہیں کی گئی}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{گزشتہ # گھنٹے میں استعمال نہیں کی گئی}other{گزشتہ # گھنٹے میں استعمال نہیں کی گئی}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 ایپ کے ذریعے استعمال کردہ}other{# ایپس کے ذریعے استعمال کردہ}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"سبھی کو ڈیش بورڈ میں دیکھیں"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"فلٹر کردہ بلحاظ: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"صرف میڈیا تک رسائی کی اجازت دیں"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"ہر وقت اجازت دیں"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"صرف ایپ استعمال کرتے وقت اجازت دیں"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"تمام تصاویر کی اجازت دیں"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"منتخب کردہ تصاویر کی اجازت دیں"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"ہر بار پوچھیں"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"اجازت نہ دیں"</string>
<string name="precise_image_description" msgid="6349638632303619872">"قطعی مقام"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"اجازت نہیں ہے"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"مزید ایپس دیکھیں جو تمام فائلز تک رسائی حاصل کر سکتی ہیں"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 دن}other{# دن}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 گھنٹہ}other{# گھنٹے}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 منٹ}other{# منٹ}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 سیکنڈ}other{# سیکنڈ}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# گھنٹہ}other{# گھنٹے}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# منٹ}other{# منٹ}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# سیکنڈ}other{# سیکنڈ}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"اجازت کی یاددہانیاں"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 غیر مستعمل ایپ"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> غیر مستعمل ایپس"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> کو آپ کی ڈیفالٹ نیویگیشن ایپ کے بطور سیٹ کریں؟"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"کوئی اجازت درکار نہیں ہے"</string>
<string name="role_watch_description" msgid="267003778693177779">"‏<xliff:g id="APP_NAME">%1$s</xliff:g> کو آپ کی اطلاعات کے ساتھ تعامل کرنے اور آپ کے فون، SMS، رابطوں اور کیلنڈر کی اجازتوں تک رسائی کی اجازت ہوگی۔"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"‏<xliff:g id="APP_NAME">%1$s</xliff:g> کو آپ کی اطلاعات کے ساتھ تعامل کرنے اور آپ کے فون، SMS، رابطوں، مائیکروفون اور قریبی آلات کی اجازتوں تک رسائی کی اجازت ہوگی۔"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> آپ کی اطلاعات کے ساتھ تعامل کرنے اور آپ کی ایپس کے منسلک آلے پر سلسلہ بندی کرنے کی اجازت ہوگی۔"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> کو قریبی آلات پر مواد کو اسٹریم کرنے کی اجازت ہوگی۔"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"یہ سروس آپ کی تصاویر، میڈیا اور اطلاعات کو آپ کے فون سے دوسرے آلات پر اشتراک کرتی ہے۔"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"موجودہ ڈیفالٹ"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"دوبارہ نہ پوچھیں"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"بطور ڈیفالٹ سیٹ کریں"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"‏اس آلے پر &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; کو &lt;b&gt;تصاویر، ویڈیوز، موسیقی، آڈیو اور دیگر فائلز&lt;/b&gt; تک رسائی کی اجازت دیں؟"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"‏اس آلے پر &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; کو موسیقی اور آڈیو تک رسائی کی اجازت دیں؟"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"‏اس آلے پر &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; کو تصاویر اور ویڈیوز تک رسائی کی اجازت دیں؟"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"‏مزید تصاویر کے لیے &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; کو رسائی کی منظوری دیں؟"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"‏&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; کو آڈیو ریکارڈ کرنے کی اجازت دیں؟"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"جب آپ ایپ استعمال کر رہے ہوں تب ایپ صرف آڈیو ریکارڈ کر پائے گی"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"‏&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; کو آڈیو ریکارڈ کرنے کی اجازت دیں؟"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"ایپس کے آپ کے کاپی کردہ ٹیکسٹ، تصاویر یا دیگر مواد تک رسائی حاصل کرنے پر پیغام دکھائیں"</string>
<string name="show_password_title" msgid="2877269286984684659">"پاس ورڈز دکھائیں"</string>
<string name="show_password_summary" msgid="1110166488865981610">"ٹائپ کرتے وقت حروف کو مختصر طور پر ڈسپلے کریں"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"اس ایپ نے بتایا ہے کہ یہ <xliff:g id="PERMISSION_NAME">%s</xliff:g> کے ڈیٹا کا اشتراک فریقین ثالث کے ساتھ کر سکتی ہے"</string>
</resources>
diff --git a/PermissionController/res/values-uz/strings.xml b/PermissionController/res/values-uz/strings.xml
index f084beaaf..f9b4b04fa 100644
--- a/PermissionController/res/values-uz/strings.xml
+++ b/PermissionController/res/values-uz/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"“Ilova ishlatilganda” rejimida qolsin"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"“Faqat shu safar” ruxsat berish"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Batafsil"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Barcha suratlarga kirish uchun ruxsat berish"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Surat tanlash"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Yana surat tanlash"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Boshqa surat tanlanmasin"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Baribir rad etilsin"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Yopish"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> / <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 kun}other{# kun}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 soat}other{# soat}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 daq}other{# daq}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 son}other{# son}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# kun}other{# kun}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# soat}other{# soat}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# daq}other{# daq}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# son}other{# son}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Har qanday ruxsat"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Har qanday vaqt"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Oxirgi 7 kun"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Oxirgi 24 soat"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Oxirgi 1 soat"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Oxirgi 15 daqiqa"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Oxirgi 1 daqiqa"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Oxirgi # kun}other{Oxirgi # kun}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Oxirgi # soat}other{Oxirgi # soat}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Oxirgi # daqiqa}other{Oxirgi # daqiqa}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Ruxsatlardan foydalanilmagan"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Butun vaqtda ruxsatlardan foydalanish"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Oxirgi 7 kunda ruxsatlardan foydalanish"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Oxirgi 1 soatda ishlatilgan ruxsatlar"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Oxirgi 15 daqiqada ishlatilgan ruxsatlar"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Oxirgi 1 daqiqada ishlatilgan ruxsatlar"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Oxirgi 24 soatda foydalanilmagan"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Oxirgi 7 kunda foydalanilmagan"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Oxirgi # kunda foydalanilmagan}other{Oxirgi # kunda foydalanilmagan}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Oxirgi # soatda foydalanilmagan}other{Oxirgi # soatda foydalanilmagan}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 ta ilova ishlatyapti}other{# ta ilova ishlatyapti}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Boshqaruv panelida ochish"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Filtrlar: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Faqat media fayllarga ruxsat"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Har doim ruxsat"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Faqat ilova faolligida ruxsat"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Barcha suratlarga kirish ruxsati"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Tanlangan suratlarga kirish ruxsati"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Har safar soʻralsin"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Rad etish"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Aniq joylashuv"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Ruxsat berilmagan"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Barcha fayllarga kirish uchun koʻproq ilovalarni koʻring"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 kun}other{# kun}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 soat}other{# soat}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 daqiqa}other{# daqiqa}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 soniya}other{# soniya}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# soat}other{# soat}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# daqiqa}other{# daqiqa}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# soniya}other{# soniya}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Ruxsat eslatmalari"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ta ishlatilmagan ilova"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ta ishlatilmagan ilova"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"<xliff:g id="APP_NAME">%1$s</xliff:g> asosiy navigatsiya ilovasida sifatida sozlansinmi?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Hech qanday ruxsat zarur emas"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga bildirishnomalar bilan ishlash va telefon, SMS, kontaktlar va taqvimga kirishga ruxsat beriladi"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga bildirishnomalar bilan ishlash va telefon, SMS, kontaktlar, mikrofon va yaqin-atrofdagi qurilmalarga kirishga ruxsat beriladi."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga bildirishnomalar bilan ishlash va ulangan qurilmalarga ilovalarni translatsiya qilishga ruxsat beriladi"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga yaqin-atrofdagi qurilmalarga kontent uzatish ruxsati beriladi."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Bu xizmat telefoningizdagi suratlar, media va bildirishnomalarni boshqa qurilmalarga ulashadi."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Hozirda asosiy ilova"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Boshqa soʻralmasin"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Birlamchi deb belgilash"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun ushbu qurilmadagi surat, video, musiqa, audio va fayllarga kirish ruxsati berilsinmi?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun ushbu qurilmadagi musiqa va audio fayllarga kirish ruxsati berilsinmi?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"<xliff:g id="APP_NAME">%1$s</xliff:g> uchun qurilmadagi rasm va videolarga kirish ruxsati berilsinmi?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"<xliff:g id="APP_NAME">%1$s</xliff:g> ilovasiga boshqa suratlarga kirish uchun ruxsat berilsinmi?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uchun audio yozib olish ruxsati berilsinmi?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Bu ilova faqat undan foydalanganingizda ovozlarni yozib oladi"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; uchun audio yozib olishga ruxsat berilsinmi?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Ilovalar siz nusxa olgan matn, rasmlar yoki boshqa kontentdan foydalanganda xabar chiqarish"</string>
<string name="show_password_title" msgid="2877269286984684659">"Parollar ochiq tursin"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Parolni kiritishda belgilar qisqa muddat ochiq turadi"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Bu ilovaning <xliff:g id="PERMISSION_NAME">%s</xliff:g> maʼlumotlarini begonalarga ulashishi mumkinligi xabar berildi"</string>
</resources>
diff --git a/PermissionController/res/values-v31/styles.xml b/PermissionController/res/values-v31/styles.xml
index 64fc2900d..7ce3ff6e7 100644
--- a/PermissionController/res/values-v31/styles.xml
+++ b/PermissionController/res/values-v31/styles.xml
@@ -90,6 +90,14 @@
parent="@style/PermissionGrantButtonTop"></style>
<style name="PermissionGrantButtonAllowOneTimeMaterial3"
parent="@style/PermissionGrantButtonMiddle"></style>
+ <style name="PermissionGrantButtonAllowAllPhotosMaterial3"
+ parent="@style/PermissionGrantButtonTop"></style>
+ <style name="PermissionGrantButtonAllowSelectedPhotosMaterial3"
+ parent="@style/PermissionGrantButtonMiddle"></style>
+ <style name="PermissionGrantButtonAllowMorePhotosMaterial3"
+ parent="@style/PermissionGrantButtonTop"></style>
+ <style name="PermissionGrantButtonDontAllowMorePhotosMaterial3"
+ parent="@style/PermissionGrantButtonBottom"></style>
<style name="PermissionGrantButtonDenyMaterial3"
parent="@style/PermissionGrantButtonBottom"></style>
<style name="PermissionGrantButtonNoUpgradeMaterial3"
diff --git a/PermissionController/res/values-v33/styles.xml b/PermissionController/res/values-v33/styles.xml
index f38eb51ce..1efb886cb 100644
--- a/PermissionController/res/values-v33/styles.xml
+++ b/PermissionController/res/values-v33/styles.xml
@@ -15,8 +15,7 @@
-->
<resources
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto">
+ xmlns:android="http://schemas.android.com/apk/res/android">
<!-- START SAFETY CENTER QUICK SETTINGS PAGE -->
<style name="SafetyCenterQsContainer"
@@ -174,9 +173,9 @@
<style name="SafetyCenterIndicatorCardView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:cardCornerRadius">@dimen/sc_card_corner_radius_large</item>
- <item name="app:cardElevation">0dp</item>
- <item name="app:cardBackgroundColor">?attr/colorSurface</item>
+ <item name="cardCornerRadius">@dimen/sc_card_corner_radius_large</item>
+ <item name="cardElevation">0dp</item>
+ <item name="cardBackgroundColor">?attr/colorSurface</item>
<item name="android:layout_marginTop">@dimen/sc_spacing_large</item>
<item name="android:clickable">true</item>
<item name="android:foreground">?android:attr/selectableItemBackground</item>
@@ -342,9 +341,9 @@
<style name="SafetyCenterStatusImage" parent="android:Widget.DeviceDefault">
<item name="android:layout_width">56dp</item>
<item name="android:layout_height">56dp</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintBottom_toBottomOf">@id/status_title_and_summary</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintBottom_toBottomOf">@id/status_title_and_summary</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
<item name="android:scaleType">centerInside</item>
<item name="android:gravity">center</item>
</style>
@@ -356,9 +355,9 @@
<item name="android:layout_marginTop">@dimen/sc_spacing_xxsmall</item>
<item name="android:layout_marginStart">@dimen/sc_spacing_large</item>
<item name="android:layout_marginEnd">@dimen/sc_spacing_xxxlarge</item>
- <item name="app:layout_constraintStart_toEndOf">@id/status_image</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintStart_toEndOf">@id/status_image</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
</style>
<style name="SafetyCenterStatusTitle" parent="android:Widget.DeviceDefault">
@@ -370,7 +369,7 @@
<style name="SafetyCenterStatusSummary" parent="android:Widget.DeviceDefault">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/status_title</item>
+ <item name="layout_constraintTop_toBottomOf">@id/status_title</item>
<item name="android:textAppearance">@style/TextAppearance.SafetyCenter.Body</item>
</style>
@@ -379,39 +378,39 @@
<style name="SafetyCenterStatusButton.ReviewSettings">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/status_title_and_summary</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintTop_toBottomOf">@id/status_title_and_summary</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
<item name="android:layout_marginTop">@dimen/sc_spacing_xxxlarge</item>
- <item name="app:backgroundTint">@color/safety_center_button_info</item>
+ <item name="backgroundTint">@color/safety_center_button_info</item>
</style>
<style name="SafetyCenterStatusButton.Rescan">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/review_settings_button</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintTop_toBottomOf">@id/review_settings_button</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
<item name="android:layout_marginTop">@dimen/sc_spacing_xxxlarge</item>
- <item name="app:backgroundTint">@color/safety_center_button_info</item>
+ <item name="backgroundTint">@color/safety_center_button_info</item>
</style>
<style name="SafetyCenterStatusButton.PendingActionsRescan">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/review_settings_button</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintTop_toBottomOf">@id/review_settings_button</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
<item name="android:layout_marginTop">@dimen/sc_action_button_list_margin</item>
- <item name="app:backgroundTint">@color/sc_surface_dark</item>
- <item name="app:strokeWidth">@dimen/mtrl_btn_stroke_size</item>
- <item name="app:strokeColor">@color/safety_center_button_info</item>
+ <item name="backgroundTint">@color/sc_surface_dark</item>
+ <item name="strokeWidth">@dimen/mtrl_btn_stroke_size</item>
+ <item name="strokeColor">@color/safety_center_button_info</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="SafetyCenterStatusSafetyProtectionView">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/rescan_button</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintTop_toBottomOf">@id/rescan_button</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
<item name="android:layout_gravity">center</item>
<item name="android:paddingTop">@dimen/sc_spacing_xxlarge</item>
<item name="android:paddingBottom">@dimen/sc_spacing_xxlarge</item>
@@ -429,8 +428,19 @@
<item name="android:background">@android:color/transparent</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
+ </style>
+
+ <style name="SafetyCenterIssueAttributionTitle">
+ <item name="android:textAppearance">@style/TextAppearance.SafetyCenter.Body</item>
+ <item name="android:layout_marginTop">@dimen/sc_spacing_xxsmall</item>
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginEnd">@dimen/sc_spacing_xxxlarge</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintEnd_toStartOf">@id/issue_card_dismiss_btn</item>
</style>
<style name="SafetyCenterIssueTitle">
@@ -438,11 +448,11 @@
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_marginEnd">@dimen/sc_spacing_xxxlarge</item>
- <item name="app:layout_constraintHorizontal_bias">0</item>
- <item name="app:layout_goneMarginEnd">0dp</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
- <item name="app:layout_constraintEnd_toStartOf">@id/issue_card_dismiss_btn</item>
+ <item name="layout_constraintHorizontal_bias">0</item>
+ <item name="layout_goneMarginEnd">0dp</item>
+ <item name="layout_constraintTop_toBottomOf">@id/issue_card_attribution_title</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintEnd_toStartOf">@id/issue_card_dismiss_btn</item>
</style>
<style name="SafetyCenterIssueSubtitle">
@@ -450,8 +460,8 @@
<item name="android:layout_marginTop">@dimen/sc_spacing_xxsmall</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
- <item name="app:layout_constraintStart_toStartOf">@id/issue_card_title</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/issue_card_title</item>
+ <item name="layout_constraintStart_toStartOf">@id/issue_card_title</item>
+ <item name="layout_constraintTop_toBottomOf">@id/issue_card_title</item>
</style>
<style name="SafetyCenterIssueSummary">
@@ -459,8 +469,8 @@
<item name="android:layout_marginTop">@dimen/sc_spacing_large</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">wrap_content</item>
- <item name="app:layout_constraintStart_toStartOf">@id/issue_card_title</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/issue_card_subtitle</item>
+ <item name="layout_constraintStart_toStartOf">@id/issue_card_title</item>
+ <item name="layout_constraintTop_toBottomOf">@id/issue_card_subtitle</item>
</style>
<style name="SafetyCenterIssueActionButtonList"
@@ -469,18 +479,18 @@
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">0dp</item>
<item name="android:orientation">vertical</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/issue_card_summary</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintTop_toBottomOf">@id/issue_card_summary</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
</style>
<style name="SafetyCenterIssueSafetyProtectionSection">
<item name="android:gravity">center</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_width">0dp</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/issue_card_action_button_list</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintTop_toBottomOf">@id/issue_card_action_button_list</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
<item name="android:paddingTop">@dimen/sc_spacing_xxlarge</item>
<item name="android:paddingBottom">@dimen/sc_spacing_xxlarge</item>
</style>
@@ -489,17 +499,17 @@
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">0dp</item>
<item name="android:gravity">center</item>
- <item name="app:layout_constraintWidth_default">wrap</item>
- <item name="app:layout_constraintHeight_default">wrap</item>
- <item name="app:layout_constraintWidth_max">112dp</item>
- <item name="app:layout_constraintHeight_max">112dp</item>
- <item name="app:layout_constraintWidth_min">84dp</item>
- <item name="app:layout_constraintHeight_min">84dp</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintBottom_toTopOf">@id/resolved_issue_text</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
- <item name="app:layout_constraintVertical_chainStyle">packed</item>
+ <item name="layout_constraintWidth_default">wrap</item>
+ <item name="layout_constraintHeight_default">wrap</item>
+ <item name="layout_constraintWidth_max">112dp</item>
+ <item name="layout_constraintHeight_max">112dp</item>
+ <item name="layout_constraintWidth_min">84dp</item>
+ <item name="layout_constraintHeight_min">84dp</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintBottom_toTopOf">@id/resolved_issue_text</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintVertical_chainStyle">packed</item>
</style>
<style name="SafetyCenterIssueCardResolvedTitle">
@@ -511,10 +521,10 @@
<item name="android:gravity">center</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/resolved_issue_image</item>
- <item name="app:layout_constraintBottom_toBottomOf">parent</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintTop_toBottomOf">@id/resolved_issue_image</item>
+ <item name="layout_constraintBottom_toBottomOf">parent</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
</style>
<style name="SafetyCenterMoreIssues"
@@ -533,11 +543,11 @@
<item name="android:layout_marginEnd">@dimen/sc_spacing_xxlarge</item>
<item name="android:maxLines">2</item>
<item name="android:ellipsize">end</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintBottom_toBottomOf">parent</item>
- <item name="app:layout_constraintStart_toEndOf">@id/status_icon</item>
- <item name="app:layout_constraintEnd_toStartOf">@android:id/widget_frame</item>
- <item name="app:layout_constraintHorizontal_bias">0</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintBottom_toBottomOf">parent</item>
+ <item name="layout_constraintStart_toEndOf">@id/status_icon</item>
+ <item name="layout_constraintEnd_toStartOf">@android:id/widget_frame</item>
+ <item name="layout_constraintHorizontal_bias">0</item>
</style>
<style name="SafetyCenterMoreIssuesIcon"
@@ -545,9 +555,9 @@
<item name="android:layout_height">20dp</item>
<item name="android:layout_width">20dp</item>
<item name="android:gravity">center</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintBottom_toBottomOf">parent</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintBottom_toBottomOf">parent</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
</style>
<style name="SafetyCenterMoreIssuesWidgetFrame"
@@ -556,9 +566,9 @@
<item name="android:layout_width">wrap_content</item>
<item name="android:gravity">center_vertical</item>
<item name="android:orientation">vertical</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
- <item name="app:layout_constraintBottom_toBottomOf">parent</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintBottom_toBottomOf">parent</item>
</style>
<style name="SafetyCenterMoreIssuesWidget"
@@ -578,10 +588,10 @@
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_marginEnd">@dimen/sc_spacing_xxxsmall</item>
<item name="android:maxLines">1</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintBottom_toBottomOf">parent</item>
- <item name="app:layout_constraintEnd_toStartOf">@id/widget_icon</item>
- <item name="app:layout_constraintHorizontal_bias">0</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintBottom_toBottomOf">parent</item>
+ <item name="layout_constraintEnd_toStartOf">@id/widget_icon</item>
+ <item name="layout_constraintHorizontal_bias">0</item>
</style>
<style name="SafetyCenterMoreIssuesWidgetIcon"
@@ -589,9 +599,9 @@
<item name="android:layout_height">16dp</item>
<item name="android:layout_width">16dp</item>
<item name="android:gravity">center</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintBottom_toBottomOf">parent</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintBottom_toBottomOf">parent</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
<item name="android:scaleType">fitCenter</item>
<item name="android:tint">?android:attr/textColorPrimary</item>
</style>
diff --git a/PermissionController/res/values-v34/bools.xml b/PermissionController/res/values-v34/bools.xml
new file mode 100644
index 000000000..a0e3741a9
--- /dev/null
+++ b/PermissionController/res/values-v34/bools.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Copyright 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.
+-->
+
+<resources>
+ <bool name="is_at_least_u">true</bool>
+</resources>
diff --git a/PermissionController/res/values-v34/strings.xml b/PermissionController/res/values-v34/strings.xml
new file mode 100644
index 000000000..36e0e6e40
--- /dev/null
+++ b/PermissionController/res/values-v34/strings.xml
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- Permission Rationale - Start -->
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Title message shown for Permission Rationale dialog [CHAR LIMIT=50] -->
+ <string name="permission_rationale_title">More about allowing this access</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Title shown for Permission Rationale data sharing purposes section [CHAR LIMIT=50] -->
+ <string name="permission_rationale_purpose_title">Why the app may share data?</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Message shown to the user letting them know that data will be shared and for which
+ purposes. It will also display where the information was provided from via the install_source
+ placeholder, this will map to app store labels/names. Purposes will be comma delimited and are
+ one or many of the following: "app functionality", "analytics", "developer communications",
+ "advertising or marketing", "fraud prevention, security, and compliance", "personalization",
+ "account management". Example usage as follows: "This app developer stated to App Store that it
+ may share for app functionality, analytics, developer communications, advertising or marketing,
+ fraud prevention, security, and compliance, personalization, account management. This may be
+ updated in the future." [CHAR LIMIT=300] -->
+ <string name="permission_rationale_purpose_message">This app developer stated to <annotation id="link"><annotation id="install_source" example="App Store">%1$s</annotation></annotation> that it may share for <annotation id="purpose_list" example="purpose 1, purpose 2, purpose 3">%2$s</annotation>\nThis may be updated in the future.</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Message shown to the user letting them know that data will be shared and for which
+ purposes. It will also display that the information was provided via the device manufacturer
+ (company responsible for making and released the device). Purposes will be comma delimited and
+ are one or many of the following: "app functionality", "analytics", "developer communications",
+ "advertising or marketing", "fraud prevention, security, and compliance", "personalization",
+ "account management". Example usage as follows: "This app developer stated to the device
+ manufacturer that it may share for app functionality, analytics, developer communications,
+ advertising or marketing, fraud prevention, security, and compliance, personalization, account
+ management. This may be updated in the future." [CHAR LIMIT=300] -->
+ <string name="permission_rationale_purpose_default_source_message">This app developer stated to the device manufacturer that it may share for <xliff:g id="purpose_list" example="purpose 1, purpose 2, purpose 3">%1$s</xliff:g>\nThis may be updated in the future.</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Title shown for Permission Rationale third party description section [CHAR LIMIT=50] -->
+ <string name="permission_rationale_thirdparty_title">What are third parties?</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Message shown to the user that explains what a "third party" is [CHAR LIMIT=100] -->
+ <string name="permission_rationale_thirdparty_message">Third parties are other companies or organizations</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Title shown for Permission Rationale permission settings section [CHAR LIMIT=50] -->
+ <string name="permission_rationale_permission_settings_title">Change or revoke access any time</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Message shown to the user letting them where to change permission settings in the future [CHAR LIMIT=100] -->
+ <string name="permission_rationale_permission_settings_message">Go to your <annotation id="link">privacy settings</annotation>, under <annotation id="permission_name" example="location">%1$s</annotation></string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Title shown for Permission Rationale "learn more about data sharing" section [CHAR LIMIT=50] -->
+ <string name="permission_rationale_permission_learn_more_title"><annotation id="link">Learn more</annotation> about data sharing</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for app functionality. This may be updated in the future."
+ [CHAR LIMIT=30] -->
+ <string name="permission_rational_purpose_app_functionality">app functionality</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for analytics. This may be updated in the future."
+ [CHAR LIMIT=30] -->
+ <string name="permission_rational_purpose_analytics">analytics</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for developer communications. This may be updated in the future."
+ [CHAR LIMIT=50] -->
+ <string name="permission_rational_purpose_developer_communications">developer communications</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for advertising or marketing. This may be updated in the future."
+ [CHAR LIMIT=50] -->
+ <string name="permission_rational_purpose_advertising">advertising or marketing</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for fraud prevention, security, and compliance. This may be
+ updated in the future." [CHAR LIMIT=75] -->
+ <string name="permission_rational_purpose_fraud_prevention_security">fraud prevention, security, and compliance</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for personalization. This may be updated in the future."
+ [CHAR LIMIT=50] -->
+ <string name="permission_rational_purpose_personalization">personalization</string>
+
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Permission usage purpose shown for Permission Rationale. This will be used with the
+ permission_rationale_purpose_message and permission_rationale_purpose_default_source_message
+ strings. Example usage with this string as follows: "This app developer stated to the device
+ manufacturer that it may share for account management. This may be updated in the future."
+ [CHAR LIMIT=50] -->
+ <string name="permission_rational_purpose_account_management">account management</string>
+
+ <!-- Text for an app's permission rationale header [CHAR LIMIT=60]-->
+ <string name="app_permission_rationale_message">Data privacy</string>
+
+ <!-- Text for linking to an app's location permission rationale dialog. [CHAR LIMIT=60] -->
+ <string name="app_location_permission_rationale_title">Your location may be shared</string>
+
+ <!-- Description for the app's location permission rationale dialog content. [CHAR LIMIT=60] -->
+ <string name="app_location_permission_rationale_subtitle">This app declared it may share your location with third parties</string>
+
+ <!-- Permission Rationale - End -->
+
+ <!-- Safety Label Change Notifications Start -->
+
+ <!-- TODO(b/261914980): Update with final safety label change notifications strings. -->
+
+ <!-- Title for Data Sharing updates page, which displays updates from apps on their data sharing
+ practices. Also used as the title for Data Sharing updates page entry point from Safety Center.
+ [CHAR LIMIT=50] -->
+ <string name="data_sharing_updates_title">Data sharing updates</string>
+ <!-- Summary for Data Sharing updates page entry point in Safety Center. Clicking on the entry
+ will navigate to Data Sharing updates page, which displays updates from apps on their data
+ sharing practices. [CHAR LIMIT=100] -->
+ <string name="data_sharing_updates_summary">Review apps that changed the way they share location data.</string>
+ <!-- Subtitle for Data Sharing updates page, which displays updates from apps on their data
+ sharing practices. [CHAR LIMIT=200] -->
+ <string name="data_sharing_updates_subtitle">These apps have provided updates on data sharing practices. Review these updates and modify app permissions if necessary.</string>
+ <!-- Footer message for Data Sharing updates page, which displays updates from apps on their
+ data sharing practices. [CHAR LIMIT=400] -->
+ <string name="data_sharing_updates_footer_message">The developers of the apps listed here provided this information about their sharing practices and may update it over time.\nData privacy and security practices may vary based on your use, region, and age.</string>
+ <!-- Link for Data Sharing Help Center page, which will open a web page detailing what app data
+ sharing policies mean. [CHAR LIMIT=60] -->
+ <string name="learn_more_about_data_sharing">Learn more about data sharing</string>
+ <!-- Message indicating that an app now shares location data with third parties for the purpose
+ of advertising, when it earlier did not share location data for the purpose of advertising, but
+ did for other purposes. Third parties are other companies or organizations.
+ [CHAR LIMIT=60] -->
+ <string name="shares_location_for_advertising">Now shares location for advertising</string>
+ <!-- Message indicating that an app now shares location data with third parties for the purpose
+ of advertising, when it earlier did not share location data. Third parties are other companies
+ or organizations. [CHAR LIMIT=80] -->
+ <string name="shares_location_with_third_parties_for_advertising">Now shares location with third parties for advertising</string>
+ <!-- Message indicating that an app now shares location data with third parties, when it earlier
+ did not share location data. Third parties are other companies or organizations.
+ [CHAR LIMIT=60] -->
+ <string name="shares_location_with_third_parties">Now shares location with third parties</string>
+ <!-- Header for the number of updates in the last days. [CHAR LIMIT=40] -->
+ <string name="updated_in_last_days">{count, plural,
+ =0 {Updated in the last day}
+ =1 {Updated in the last day}
+ other {Updated in the last # days}
+ }</string>
+ <!-- Message indicating that no apps have provided recent updates. [CHAR LIMIT=80] -->
+ <string name="no_recent_updates">No apps have provided recent updates.</string>
+
+ <!-- Notification channel title for safety label changes notification [CHAR LIMIT=65] -->
+ <string name="safety_label_changes_notification_title">Safety label notification title</string>
+ <!-- Notification channel description for safety label changes notification [CHAR LIMIT=240] -->
+ <string name="safety_label_changes_notification_desc">Safety label notification description</string>
+
+ <!-- Safety Label Change Notifications End -->
+
+</resources>
diff --git a/PermissionController/res/values-vi/strings.xml b/PermissionController/res/values-vi/strings.xml
index 06a0681dd..b1dad400f 100644
--- a/PermissionController/res/values-vi/strings.xml
+++ b/PermissionController/res/values-vi/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Duy trì tùy chọn “Khi đang dùng ứng dụng”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Duy trì tùy chọn “Chỉ lần này”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Thông tin khác"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Cho phép truy cập vào toàn bộ ảnh"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Chọn ảnh"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Chọn thêm ảnh"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Không chọn thêm ảnh"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Vẫn không cho phép"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Đóng"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>/<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 ngày}other{# ngày}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 giờ}other{# giờ}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 phút}other{# phút}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 giây}other{# giây}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# ngày}other{# ngày}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# giờ}other{# giờ}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# phút}other{# phút}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# giây}other{# giây}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Mọi quyền"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Mọi lúc"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"7 ngày qua"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"24 giờ qua"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"1 giờ qua"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"15 phút qua"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"1 phút qua"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{# ngày qua}other{# ngày qua}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{# giờ qua}other{# giờ qua}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{# phút qua}other{# phút qua}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Không sử dụng quyền"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Lần truy cập gần đây nhất vào bất kỳ thời gian nào"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Lần truy cập gần đây nhất trong 7 ngày qua"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Tần suất sử dụng quyền trong 1 giờ trước"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Tần suất sử dụng quyền trong 15 phút trước"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Tần suất sử dụng quyền trong 1 phút trước"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Không sử dụng trong 24 giờ qua"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Không sử dụng trong 7 ngày qua"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Không sử dụng trong # ngày qua}other{Không sử dụng trong # ngày qua}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Không sử dụng trong # giờ qua}other{Không sử dụng trong # giờ qua}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 ứng dụng đã dùng}other{# ứng dụng đã dùng}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Xem tất cả trong Trang tổng quan"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Lọc theo: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Chỉ cho phép truy cập vào nội dung nghe nhìn"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Luôn cho phép"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Chỉ cho phép khi dùng ứng dụng"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Cho phép truy cập vào toàn bộ ảnh"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Cho phép truy cập vào ảnh đã chọn"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Luôn hỏi"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Không cho phép"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Vị trí chính xác"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Không được phép"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Xem thêm ứng dụng có thể truy cập tất cả các tệp"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 ngày}other{# ngày}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 giờ}other{# giờ}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 phút}other{# phút}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 giây}other{# giây}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# giờ}other{# giờ}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# phút}other{# phút}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# giây}other{# giây}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Lời nhắc về quyền"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 ứng dụng không dùng đến"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ứng dụng không dùng đến"</string>
@@ -337,7 +341,7 @@
<string name="no_permissions_allowed" msgid="6081976856354669209">"Chưa cấp quyền nào"</string>
<string name="no_permissions_denied" msgid="8159923922804043282">"Chưa từ chối quyền nào"</string>
<string name="no_apps_allowed" msgid="7718822655254468631">"Chưa cho phép ứng dụng nào"</string>
- <string name="no_apps_allowed_full" msgid="8011716991498934104">"Không ứng dụng nào được phép truy cập tất cả các tệp"</string>
+ <string name="no_apps_allowed_full" msgid="8011716991498934104">"Không cho phép ứng dụng nào đối với tất cả các tệp"</string>
<string name="no_apps_allowed_scoped" msgid="4908850477787659501">"Chỉ không cho phép ứng dụng nào đối với nội dung nghe nhìn"</string>
<string name="no_apps_denied" msgid="7663435886986784743">"Chưa từ chối ứng dụng nào"</string>
<string name="car_permission_selected" msgid="180837028920791596">"Đã chọn"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Đặt <xliff:g id="APP_NAME">%1$s</xliff:g> làm ứng dụng đi theo chỉ dẫn mặc định?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Không cần quyền"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> sẽ được phép tương tác với thông báo cũng như truy cập vào Điện thoại, SMS, Danh bạ và Lịch."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g> sẽ được phép tương tác với thông báo cũng như sử dụng các quyền đối với Điện thoại, SMS, Danh bạ, Micrô của bạn và Thiết bị ở gần bạn."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"<xliff:g id="APP_NAME">%1$s</xliff:g> sẽ được phép tương tác với thông báo cũng như truyền trực tuyến các ứng dụng đến thiết bị đã kết nối."</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g> sẽ được phép phát trực tuyến nội dung cho thiết bị ở gần."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Dịch vụ này sẽ chia sẻ ảnh, nội dung nghe nhìn và thông báo trên điện thoại của bạn với các thiết bị khác."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Ứng dụng mặc định hiện tại"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Không hỏi lại"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Đặt làm mặc định"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Cho phép &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; truy cập vào &lt;b&gt;ảnh, video, nhạc, âm thanh và các tệp khác&lt;/b&gt; trên thiết bị này?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Cho phép &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; truy cập vào nhạc và âm thanh trên thiết bị này?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Cho phép &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; truy cập vào ảnh và video trên thiết bị này?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Bạn có đồng ý cấp quyền truy cập thêm ảnh cho &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Cho phép &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ghi âm?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Ứng dụng này chỉ có thể ghi âm khi bạn đang dùng ứng dụng"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Cho phép &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ghi âm?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Hiện thông báo khi có ứng dụng truy cập vào văn bản, hình ảnh hoặc nội dung khác mà bạn đã sao chép"</string>
<string name="show_password_title" msgid="2877269286984684659">"Hiển thị mật khẩu"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Hiển thị các ký tự ngắn gọn khi bạn nhập"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Nhà phát triển nêu rõ ứng dụng này có thể chia sẻ dữ liệu <xliff:g id="PERMISSION_NAME">%s</xliff:g> với bên thứ ba"</string>
</resources>
diff --git a/PermissionController/res/values-w764dp-v33/styles.xml b/PermissionController/res/values-w764dp-v33/styles.xml
index a443f4e26..02ed655ad 100644
--- a/PermissionController/res/values-w764dp-v33/styles.xml
+++ b/PermissionController/res/values-w764dp-v33/styles.xml
@@ -26,56 +26,56 @@
<item name="android:layout_marginTop">@dimen/sc_spacing_xxsmall</item>
<item name="android:layout_marginStart">@dimen/sc_spacing_large</item>
<item name="android:layout_marginEnd">@dimen/sc_spacing_xxxlarge</item>
- <item name="app:layout_constraintStart_toEndOf">@id/status_image</item>
- <item name="app:layout_constraintEnd_toStartOf">@id/review_settings_button</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintStart_toEndOf">@id/status_image</item>
+ <item name="layout_constraintEnd_toStartOf">@id/review_settings_button</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
</style>
<style name="SafetyCenterStatusButton.ReviewSettings">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintStart_toEndOf">@id/status_title_and_summary</item>
- <item name="app:layout_constraintEnd_toStartOf">@id/rescan_button</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintStart_toEndOf">@id/status_title_and_summary</item>
+ <item name="layout_constraintEnd_toStartOf">@id/rescan_button</item>
<item name="android:layout_marginTop">@dimen/sc_spacing_xxsmall</item>
<item name="android:paddingStart">@dimen/sc_large_screen_button_padding</item>
<item name="android:paddingEnd">@dimen/sc_large_screen_button_padding</item>
- <item name="app:backgroundTint">@color/safety_center_button_info</item>
+ <item name="backgroundTint">@color/safety_center_button_info</item>
</style>
<style name="SafetyCenterStatusButton.Rescan">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintStart_toEndOf">@id/review_settings_button</item>
- <item name="app:layout_constraintEnd_toStartOf">@id/pending_actions_rescan_button</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintStart_toEndOf">@id/review_settings_button</item>
+ <item name="layout_constraintEnd_toStartOf">@id/pending_actions_rescan_button</item>
<item name="android:layout_marginTop">@dimen/sc_spacing_xxsmall</item>
<item name="android:paddingStart">@dimen/sc_large_screen_button_padding</item>
<item name="android:paddingEnd">@dimen/sc_large_screen_button_padding</item>
- <item name="app:backgroundTint">@color/safety_center_button_info</item>
+ <item name="backgroundTint">@color/safety_center_button_info</item>
</style>
<style name="SafetyCenterStatusButton.PendingActionsRescan">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toTopOf">parent</item>
- <item name="app:layout_constraintStart_toEndOf">@id/rescan_button</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintTop_toTopOf">parent</item>
+ <item name="layout_constraintStart_toEndOf">@id/rescan_button</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
<item name="android:layout_marginTop">@dimen/sc_spacing_xxsmall</item>
<item name="android:paddingStart">@dimen/sc_large_screen_button_padding</item>
<item name="android:paddingEnd">@dimen/sc_large_screen_button_padding</item>
- <item name="app:backgroundTint">@color/sc_surface_dark</item>
- <item name="app:strokeWidth">@dimen/mtrl_btn_stroke_size</item>
- <item name="app:strokeColor">@color/safety_center_button_info</item>
+ <item name="backgroundTint">@color/sc_surface_dark</item>
+ <item name="strokeWidth">@dimen/mtrl_btn_stroke_size</item>
+ <item name="strokeColor">@color/safety_center_button_info</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="SafetyCenterStatusSafetyProtectionView">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:layout_constraintTop_toBottomOf">@id/status_title_and_summary</item>
- <item name="app:layout_constraintStart_toStartOf">parent</item>
- <item name="app:layout_constraintEnd_toEndOf">parent</item>
+ <item name="layout_constraintTop_toBottomOf">@id/status_title_and_summary</item>
+ <item name="layout_constraintStart_toStartOf">parent</item>
+ <item name="layout_constraintEnd_toEndOf">parent</item>
<item name="android:layout_gravity">center</item>
<item name="android:paddingTop">@dimen/sc_spacing_xxlarge</item>
<item name="android:paddingBottom">@dimen/sc_spacing_xxlarge</item>
diff --git a/PermissionController/res/values-zh-rCN/strings.xml b/PermissionController/res/values-zh-rCN/strings.xml
index 08467a438..6582ac3f0 100644
--- a/PermissionController/res/values-zh-rCN/strings.xml
+++ b/PermissionController/res/values-zh-rCN/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"保留“在使用该应用期间”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"继续使用“仅限这一次”设置"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"更多信息"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"允许访问所有照片"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"选择照片"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"选择更多照片"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"不选择更多照片"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"仍然不允许"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"关闭"</string>
<string name="current_permission_template" msgid="7452035392573329375">"第 <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> 项权限(共 <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> 项)"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 天}other{# 天}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 小时}other{# 小时}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 分钟}other{# 分钟}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 秒}other{# 秒}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# 天}other{# 天}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# 小时}other{# 小时}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# 分钟}other{# 分钟}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# 秒}other{# 秒}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"不限权限"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"不限时间"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"过去 7 天"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"过去 24 小时"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"过去 1 小时"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"过去 15 分钟"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"过去 1 分钟"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{过去 # 天}other{过去 # 天}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{过去 # 小时}other{过去 # 小时}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{过去 # 分钟}other{过去 # 分钟}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"没有使用此权限的应用"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"最近使用的访问权限(不限时间)"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"最近使用的访问权限(过去 7 天内)"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"过去 1 小时内的权限使用频率"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"过去 15 分钟内的权限使用频率"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"过去 1 分钟内的权限使用频率"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"过去 24 小时内未使用"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"过去 7 天内未使用过"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{过去 # 天内未使用过}other{过去 # 天内未使用过}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{过去 # 小时内未使用过}other{过去 # 小时内未使用过}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{有 1 个应用使用过}other{有 # 个应用使用过}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"在信息中心查看全部详细信息"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"过滤条件:<xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"仅允许访问媒体文件"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"始终允许"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"仅在使用该应用时允许"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"允许访问所有照片"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"允许访问所选照片"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"每次都询问"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"不允许"</string>
<string name="precise_image_description" msgid="6349638632303619872">"确切位置"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"不允许"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"查看更多可以访问所有文件的应用"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 天}other{# 天}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 小时}other{# 小时}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 分钟}other{# 分钟}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 秒}other{# 秒}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# 小时}other{# 小时}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# 分钟}other{# 分钟}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# 秒}other{# 秒}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"权限提醒"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 个未使用的应用"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> 个未使用的应用"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"要将“<xliff:g id="APP_NAME">%1$s</xliff:g>”设为默认导航应用吗?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"无需任何权限"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g>将能与通知互动,并可访问电话、短信、通讯录和日历。"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"<xliff:g id="APP_NAME">%1$s</xliff:g>将能与您收到的通知交互,并可获权访问/使用您的电话、短信、通讯录、麦克风和附近的设备。"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"“<xliff:g id="APP_NAME">%1$s</xliff:g>”将能够与通知互动,并可将应用流式传输到已连接的设备。"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"<xliff:g id="APP_NAME">%1$s</xliff:g>将能够把内容流式传输到附近的设备。"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"此服务会将您手机中的照片、媒体内容和通知分享给其他设备。"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"当前默认应用"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"不再询问"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"设为默认应用"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"要允许“&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;”访问此设备上的&lt;b&gt;照片、视频、音乐、音频和其他文件&lt;/b&gt;吗?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;访问此设备上的音乐和音频吗?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;访问此设备上的照片和视频吗?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"要授予<xliff:g id="APP_NAME">%1$s</xliff:g>访问更多照片的权限吗?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"要允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;录音吗?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"此应用将只能在您使用它时录音"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"要允许&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;录音吗?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"系统会在应用访问您复制的文字、图片或其他内容时显示一条消息"</string>
<string name="show_password_title" msgid="2877269286984684659">"显示密码"</string>
<string name="show_password_summary" msgid="1110166488865981610">"输入时短暂显示字符"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"此应用已声明它可能会与第三方共享<xliff:g id="PERMISSION_NAME">%s</xliff:g>数据"</string>
</resources>
diff --git a/PermissionController/res/values-zh-rHK/strings.xml b/PermissionController/res/values-zh-rHK/strings.xml
index c353200a7..faa76f811 100644
--- a/PermissionController/res/values-zh-rHK/strings.xml
+++ b/PermissionController/res/values-zh-rHK/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"保持為「使用應用程式時」"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"保留「只在這次允許」"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"更多資料"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"允許存取所有相片"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"選取相片"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"選取更多相片"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"不選取更多相片"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"一律不允許"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"關閉"</string>
<string name="current_permission_template" msgid="7452035392573329375">"第 <xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> 個 (共 <xliff:g id="PERMISSION_COUNT">%2$s</xliff:g> 個)"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 天}other{# 天}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 小時}other{# 小時}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 分鐘}other{# 分鐘}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 秒}other{# 秒}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# 天}other{# 天}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# 小時}other{# 小時}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# 分鐘}other{# 分鐘}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# 秒}other{# 秒}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"任何權限"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"不限時間"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"過去 7 天"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"過去 24 小時"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"過去 1 小時"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"過去 15 分鐘"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"過去 1 分鐘"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{過去 # 日}other{過去 # 日}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{過去 # 小時}other{過去 # 小時}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{過去 # 分鐘}other{過去 # 分鐘}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"沒有應用程式使用要求的權限"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"任意時段內的最近一次存取"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"過去 7 天內的近期存取"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"過去 1 小時內的權限使用情況"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"過去 15 分鐘內的權限使用情況"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"過去 1 分鐘內的權限使用情況"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"過去 24 小時內未使用"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"過去 7 天內未使用"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{過去 # 天內並未使用}other{過去 # 天內並未使用}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{過去 # 小時內並未使用}other{過去 # 小時內並未使用}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{有 1 個應用程式使用過}other{有 # 個應用程式使用過}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"在「資訊主頁」查看全部"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"篩選條件:<xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"僅允許存取媒體"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"一律允許"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"僅在使用此應用程式時允許"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"允許存取所有相片"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"允許存取所選相片"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"每次都詢問"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"不允許"</string>
<string name="precise_image_description" msgid="6349638632303619872">"精確位置"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"不允許"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"顯示更多可存取所有檔案的應用程式"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 天}other{# 天}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 小時}other{# 小時}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 分鐘}other{# 分鐘}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 秒}other{# 秒}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# 小時}other{# 小時}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# 分鐘}other{# 分鐘}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# 秒}other{# 秒}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"權限提醒"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 個不使用的應用程式"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> 個不使用的應用程式"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"要將「<xliff:g id="APP_NAME">%1$s</xliff:g>」設定為預設導航應用程式嗎?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"無需任何權限"</string>
<string name="role_watch_description" msgid="267003778693177779">"<xliff:g id="APP_NAME">%1$s</xliff:g> 將可與您的通知互動,並可存取電話、短訊、聯絡人和日曆。"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」將可透過通知與您互動,並存取電話、短訊、通訊錄、麥克風和附近的裝置權限。"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」將可存取通知,並可在已連結的裝置上串流播放應用程式。"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容將可在附近的裝置上串流播放。"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"此服務會將您手機中的相片、媒體和通知與其他裝置共用。"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"目前預設"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"不要再詢問"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"設定為預設"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"要允許 &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; 存取此裝置上的&lt;b&gt;相片、影片、音樂、音訊和其他檔案&lt;/b&gt;嗎?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"要允許 &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; 存取此裝置上的音樂和音訊嗎?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"要允許 &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; 存取此裝置上的相片和影片嗎?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」存取更多相片嗎?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;b&gt;&lt;/b&gt;錄音嗎?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"此應用程式將只能在您使用期間錄音"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;b&gt;&lt;/b&gt;錄音嗎?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"系統會在應用程式存取您複製的文字、圖片或其他內容時顯示訊息"</string>
<string name="show_password_title" msgid="2877269286984684659">"顯示密碼"</string>
<string name="show_password_summary" msgid="1110166488865981610">"輸入時短暫顯示字元"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"此應用程式表明可能會與第三方分享<xliff:g id="PERMISSION_NAME">%s</xliff:g>資料"</string>
</resources>
diff --git a/PermissionController/res/values-zh-rTW/strings.xml b/PermissionController/res/values-zh-rTW/strings.xml
index 69328e0ca..1ca13ffb1 100644
--- a/PermissionController/res/values-zh-rTW/strings.xml
+++ b/PermissionController/res/values-zh-rTW/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"不要變更「應用程式使用期間」"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"保留「僅允許這一次」"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"更多資訊"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"允許存取所有相片"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"選取相片"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"選取更多相片"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"不要選取更多相片"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"仍不允許"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"關閉"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g>/<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{1 天}other{# 天}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{1 小時}other{# 小時}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{1 分鐘}other{# 分鐘}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{1 秒鐘}other{# 秒鐘}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{# 天}other{# 天}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{# 小時}other{# 小時}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{# 分鐘}other{# 分鐘}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{# 秒鐘}other{# 秒鐘}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"不限權限"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"不限時間"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"過去 7 天"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"過去 24 小時"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"過去 1 小時"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"過去 15 分鐘"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"過去 1 分鐘內"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{過去 # 天內}other{過去 # 天內}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{過去 # 小時內}other{過去 # 小時內}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{過去 # 分鐘內}other{過去 # 分鐘內}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"沒有使用此權限的應用程式"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"近期使用的存取權 (依時間順序)"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"過去 7 天內使用的存取權 (依時間順序)"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"過去 1 小時內各權限的使用頻率"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"過去 15 分鐘內各權限的使用頻率"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"過去 1 分鐘內各權限的使用頻率"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"過去 24 小時內未使用"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"過去 7 天內未使用"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{過去 # 天內未使用}other{過去 # 天內未使用}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{過去 # 小時內未使用}other{過去 # 小時內未使用}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{1 個應用程式使用過}other{# 個應用程式使用過}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"在資訊主頁查看所有詳細資料"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"篩選依據:<xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"只允許存取媒體檔案"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"一律允許"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"僅在使用該應用程式時允許"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"允許所有相片"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"允許選取的相片"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"每次都詢問"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"不允許"</string>
<string name="precise_image_description" msgid="6349638632303619872">"精確位置"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"不允許"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"查看可存取所有檔案的其他應用程式"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{1 天}other{# 天}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{1 小時}other{# 小時}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{1 分鐘}other{# 分鐘}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{1 秒鐘}other{# 秒鐘}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{# 小時}other{# 小時}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{# 分鐘}other{# 分鐘}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{# 秒鐘}other{# 秒鐘}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"權限提醒"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"1 個未使用的應用程式"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> 個未使用的應用程式"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"要將「<xliff:g id="APP_NAME">%1$s</xliff:g>」設為預設的導航應用程式嗎?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"無需任何權限"</string>
<string name="role_watch_description" msgid="267003778693177779">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」將可存取通知、電話、簡訊、聯絡人和日曆資料。"</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」將能透過通知與你互動,並取得電話、簡訊、聯絡人、麥克風和鄰近裝置權限。"</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」將可存取通知,並能夠在已連結的裝置上串流播放應用程式內容。"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容將可在鄰近裝置上串流播放。"</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"這項服務會將你手機中的相片、媒體和通知與其他裝置共用。"</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"目前的預設應用程式"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"不要再詢問"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"設為預設"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;b&gt;&lt;/b&gt;存取這部裝置上的&lt;b&gt;相片、影片、音樂、音訊和其他檔案&lt;/b&gt;嗎?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;b&gt;&lt;/b&gt;存取這部裝置上的音樂和音訊嗎?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;b&gt;&lt;/b&gt;存取這部裝置上的相片和影片嗎?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」&lt;b&gt;&lt;/b&gt;存取更多相片嗎?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」錄音嗎?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"這個應用程式只有在你使用時才能錄音"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"要允許「<xliff:g id="APP_NAME">%1$s</xliff:g>」錄音嗎?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"系統會在應用程式存取你複製的文字、圖片或其他內容時顯示通知訊息"</string>
<string name="show_password_title" msgid="2877269286984684659">"顯示密碼"</string>
<string name="show_password_summary" msgid="1110166488865981610">"輸入密碼時,短暫顯示剛輸入的字元"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"這個應用程式表示可能會將「<xliff:g id="PERMISSION_NAME">%s</xliff:g>」資料分享給第三方"</string>
</resources>
diff --git a/PermissionController/res/values-zu/strings.xml b/PermissionController/res/values-zu/strings.xml
index 092f5a05d..103410e40 100644
--- a/PermissionController/res/values-zu/strings.xml
+++ b/PermissionController/res/values-zu/strings.xml
@@ -32,6 +32,10 @@
<string name="grant_dialog_button_no_upgrade" msgid="8344732743633736625">"Gcina okuthi “Ngenkathi uhlelo lokusebenza lusebenza”"</string>
<string name="grant_dialog_button_no_upgrade_one_time" msgid="5125892775684968694">"Gcina “Kulesi sikhathi kuphela”"</string>
<string name="grant_dialog_button_more_info" msgid="213350268561945193">"Olunye ulwazi"</string>
+ <string name="grant_dialog_button_allow_all_photos" msgid="3688746146785304900">"Vumela Ukufinyelela kuzo zonke izithombe"</string>
+ <string name="grant_dialog_button_allow_selected_photos" msgid="4098620850512492892">"Khetha izithombe"</string>
+ <string name="grant_dialog_button_allow_more_selected_photos" msgid="2003524111894640605">"Khetha izithombe eziningi"</string>
+ <string name="grant_dialog_button_dont_allow_more_selected_photos" msgid="6811842813929146242">"Ungakhethi izithombe eziningi"</string>
<string name="grant_dialog_button_deny_anyway" msgid="7225905870668915151">"Ungavumeli noma kunjalo"</string>
<string name="grant_dialog_button_dismiss" msgid="1930399742250226393">"Vula"</string>
<string name="current_permission_template" msgid="7452035392573329375">"<xliff:g id="CURRENT_PERMISSION_INDEX">%1$s</xliff:g> kokungu-<xliff:g id="PERMISSION_COUNT">%2$s</xliff:g>"</string>
@@ -137,17 +141,15 @@
<string name="auto_permission_usage_timeline_summary" msgid="2713135806453218703">"<xliff:g id="ACCESS_TIME">%1$s</xliff:g> • <xliff:g id="SUMMARY_TEXT">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_2" msgid="1521763591164293683">"<xliff:g id="APP_NAME">%1$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%2$s</xliff:g>"</string>
<string name="history_preference_subtext_3" msgid="758761785983094351">"<xliff:g id="ATTRIBUTION_NAME">%1$s</xliff:g> • <xliff:g id="APP_NAME">%2$s</xliff:g> • <xliff:g id="TRUNCATED_TIME">%3$s</xliff:g>"</string>
- <string name="duration_used_days" msgid="8293010131040301793">"{count,plural, =1{usuku 1}one{izinsuku #}other{izinsuku #}}"</string>
- <string name="duration_used_hours" msgid="1128716208752263576">"{count,plural, =1{ihora 1}one{amahora #}other{amahora #}}"</string>
- <string name="duration_used_minutes" msgid="5335824115042576567">"{count,plural, =1{umzuzu 1}one{imizuzu #}other{imizuzu #}}"</string>
- <string name="duration_used_seconds" msgid="6543746449171675028">"{count,plural, =1{umzuzwana 1}one{imizuzwana #}other{imizuzwana #}}"</string>
+ <string name="duration_used_days" msgid="8238355545812998877">"{count,plural, =1{Usuku olungu-#}one{Izinsuku ezingu-#}other{Izinsuku ezingu-#}}"</string>
+ <string name="duration_used_hours" msgid="4983814806123370332">"{count,plural, =1{Ihora elingu-#}one{Amahora angu-#}other{Amahora angu-#}}"</string>
+ <string name="duration_used_minutes" msgid="1701379522897227819">"{count,plural, =1{Umzuzu ongu-#}one{Imizuzu engu-#}other{Imizuzu engu-#}}"</string>
+ <string name="duration_used_seconds" msgid="4067390990568727715">"{count,plural, =1{Isekhondi elingu-#}one{Amasekhondi angu-#}other{Amasekhondi angu-#}}"</string>
<string name="permission_usage_any_permission" msgid="6358023078298106997">"Noma iyiphi imvume"</string>
<string name="permission_usage_any_time" msgid="3802087027301631827">"Noma yisiphi isikhathi"</string>
- <string name="permission_usage_last_7_days" msgid="7386221251886130065">"Izinsuku zokugcina ezingu-7"</string>
- <string name="permission_usage_last_day" msgid="1512880889737305115">"Amahora angu-24 okugcina"</string>
- <string name="permission_usage_last_hour" msgid="3866005205535400264">"Ihora lokugcina elingu-1"</string>
- <string name="permission_usage_last_15_minutes" msgid="9077554653436200702">"Amaminithi angu-15 okugcina"</string>
- <string name="permission_usage_last_minute" msgid="7297055967335176238">"Iminithi lokugcina elingu-1"</string>
+ <string name="permission_usage_last_n_days" msgid="7882626467375714145">"{count,plural, =1{Usuku lokugcina olungu-#}one{Izinsuku zokugcina ezingu-#}other{Izinsuku zokugcina ezingu-#}}"</string>
+ <string name="permission_usage_last_n_hours" msgid="8490466053680267858">"{count,plural, =1{Ihora lokugcina elingu-#}one{Amahora okugcina angu-#}other{Amahora okugcina angu-#}}"</string>
+ <string name="permission_usage_last_n_minutes" msgid="7817864229878281983">"{count,plural, =1{Umzuzu wokugcina ongu-#}one{Imizuzu yokugcina engu-#}other{Imizuzu yokugcina engu-#}}"</string>
<string name="no_permission_usages" msgid="9119517454177289331">"Akukho ukusetshenziswa kwemvume"</string>
<string name="permission_usage_list_title_any_time" msgid="8718257027381592407">"Ukufinyelela kwakamuva kakhulu noma kunini"</string>
<string name="permission_usage_list_title_last_7_days" msgid="9048542342670890615">"Ukufinyelela kwakamuva kakhulu ezinsukwini zokugcina ezingu-7"</string>
@@ -161,8 +163,8 @@
<string name="permission_usage_bar_chart_title_last_hour" msgid="6571647509660009185">"Ukusetshenziswa kwembume kuhora lokugcina elingu-1"</string>
<string name="permission_usage_bar_chart_title_last_15_minutes" msgid="2743143675412824819">"Ukusetshenziswa kwemvume kumaminithi okugcina okungu-15"</string>
<string name="permission_usage_bar_chart_title_last_minute" msgid="820450867183487607">"Ukusetshenziswa kwemvume ngeminithi elingu-1 lokugcina"</string>
- <string name="permission_usage_preference_summary_not_used_24h" msgid="3087783232178611025">"Ayisetshenziswanga emahoreni angama-24 adlule"</string>
- <string name="permission_usage_preference_summary_not_used_7d" msgid="4592301300810120096">"Akusetshenziswanga ezinsukwini ezi-7 ezedlule"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days" msgid="4771868094611359651">"{count,plural, =1{Akusetshenziswanga osukwini olungu-# olwedlule}one{Akusetshenziswanga ezinsukwini ezingu-# ezedlule}other{Akusetshenziswanga ezinsukwini ezingu-# ezedlule}}"</string>
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours" msgid="3828973177433435742">"{count,plural, =1{Akusetshenziswanga ehoreni elingu-# eledlule}one{Akusetshenziswanga emahoreni angu-# adlule}other{Akusetshenziswanga emahoreni angu-# adlule}}"</string>
<string name="permission_usage_preference_label" msgid="8343167938128676378">"{count,plural, =1{Kusetshenziswe i-app e-1}one{Kusetshenziswe ama-app angu-#}other{Kusetshenziswe ama-app angu-#}}"</string>
<string name="permission_usage_view_details" msgid="6675335735468752787">"Bona konke kudeshibhodi"</string>
<string name="app_permission_usage_filter_label" msgid="7182861154638631550">"Kuhlungwe ngalokhu: <xliff:g id="PERM">%1$s</xliff:g>"</string>
@@ -188,6 +190,8 @@
<string name="app_permission_button_allow_media_only" msgid="2834282724426046154">"Vumela ukufinyelela kumidiya kuphela"</string>
<string name="app_permission_button_allow_always" msgid="4573292371734011171">"Vumela sonke isikhathi"</string>
<string name="app_permission_button_allow_foreground" msgid="1991570451498943207">"Vumela kuphela ngenkathi usebenzisa uhlelo lokusebenza"</string>
+ <string name="app_permission_button_allow_all_photos" msgid="914762549054270764">"Vumela zonke izithombe"</string>
+ <string name="app_permission_button_select_photos" msgid="1022930616634145364">"Vumela izithombe ezikhethiwe"</string>
<string name="app_permission_button_ask" msgid="3342950658789427">"Buza njalo"</string>
<string name="app_permission_button_deny" msgid="6016454069832050300">"Ungavumeli"</string>
<string name="precise_image_description" msgid="6349638632303619872">"Indawo eqondile"</string>
@@ -253,9 +257,9 @@
<string name="denied_header" msgid="903209608358177654">"Akuvumelekile"</string>
<string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Bona ama-app engeziwe akwazi ukufinyelela wonke amafayela"</string>
<string name="days" msgid="609563020985571393">"{count,plural, =1{usuku 1}one{izinsuku #}other{izinsuku #}}"</string>
- <string name="hours" msgid="3447767892295843282">"{count,plural, =1{ihora 1}one{amahora #}other{amahora #}}"</string>
- <string name="minutes" msgid="4408293038068503157">"{count,plural, =1{umzuzu 1}one{imizuzu #}other{imizuzu #}}"</string>
- <string name="seconds" msgid="5397771912131132690">"{count,plural, =1{umzuzwana 1}one{imizuzwana #}other{imizuzwana #}}"</string>
+ <string name="hours" msgid="7302866489666950038">"{count,plural, =1{Ihora elingu-#}one{Amahora angu-#}other{Amahora angu-#}}"</string>
+ <string name="minutes" msgid="4868414855445375753">"{count,plural, =1{Umzuzu ongu-#}one{Imizuzu engu-#}other{Imizuzu engu-#}}"</string>
+ <string name="seconds" msgid="5893958182059842734">"{count,plural, =1{Isekhondi engu-#}one{Amasekhondi angu-#}other{Amasekhondi angu-#}}"</string>
<string name="permission_reminders" msgid="6528257957664832636">"Izikhumbuzi zemvume"</string>
<string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"Uhlelo lokusebenza olungasetshenzisiwe olu-1"</string>
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"Izinhlelo zokusebenza ezingasetshenzisiwe ezingu-<xliff:g id="NUMBER_OF_APPS">%s</xliff:g>"</string>
@@ -394,8 +398,18 @@
<string name="role_automotive_navigation_request_title" msgid="7525693151489384300">"Setha i-<xliff:g id="APP_NAME">%1$s</xliff:g> njenge-app yakho yokuzulazula ezenzakalelayo?"</string>
<string name="role_automotive_navigation_request_description" msgid="7073023813249245540">"Azikho izimvume ezidingekayo"</string>
<string name="role_watch_description" msgid="267003778693177779">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> izovunyelwa ukuxhumana nezaziso zakho futhi ifinyelele izimvume Zefoni yakho, -SMS, Abathintwayo kanye Nekhalenda."</string>
+ <string name="role_companion_device_glasses_description" msgid="1775655849566169630">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> izovunyelwa ukuthi ihlanganyele nezaziso zakho futhi ifinyelele kufoni yakho, i-SMS, Oxhumana nabo, Imakrofoni Nezimvume zamadivayisi aseduze."</string>
<string name="role_app_streaming_description" msgid="7341638576226183992">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> izovunyelwa ukuthi isebenzisane nezaziso zakho futhi isakaze ama-app wakho kudivayisi exhunyiwe"</string>
+ <string name="role_companion_device_nearby_device_streaming_description" msgid="1177524993193492570">"I-<xliff:g id="APP_NAME">%1$s</xliff:g> izovunyelwa ukusakaza okuqukethwe kumadivayisi aseduze."</string>
<string name="role_companion_device_computer_description" msgid="416099879217066377">"Le sevisi yabelana ngezithombe zakho, imidiya, nezaziso, kusuka efonini yakho kuya kwamanye amadivayisi."</string>
+ <!-- no translation found for role_notes_label (7694668779088299905) -->
+ <skip />
+ <!-- no translation found for role_notes_short_label (6617887820096092689) -->
+ <skip />
+ <!-- no translation found for role_notes_description (8486216423668803751) -->
+ <skip />
+ <!-- no translation found for role_notes_search_keywords (7710756695666744631) -->
+ <skip />
<string name="request_role_current_default" msgid="738722892438247184">"Okuzenzakalelayo kwamanje"</string>
<string name="request_role_dont_ask_again" msgid="3556017886029520306">"Ungabuzi futhi"</string>
<string name="request_role_set_as_default" msgid="4253949643984172880">"Setha njengokuzenzekelayo"</string>
@@ -470,6 +484,7 @@
<string name="permgrouprequest_storage_pre_q" msgid="168130651144569428">"Vumela i-<xliff:g id="APP_NAME">%1$s</xliff:g> ukuba ifinyelele izithombe, amavidiyo, umculo, okulalelwayo, namanye amafayela kule divayisi?"</string>
<string name="permgrouprequest_read_media_aural" msgid="2593365397347577812">"Vumela i-<xliff:g id="APP_NAME">%1$s</xliff:g> ukuba ifinyelele umculo nokulalelwayo kule divayisi?"</string>
<string name="permgrouprequest_read_media_visual" msgid="5548780620779729975">"Vumela i-<xliff:g id="APP_NAME">%1$s</xliff:g> ukuba ifinyelele izithombe namavidiyo kule divayisi?"</string>
+ <string name="permgrouprequest_more_photos" msgid="4697813231897226261">"Nikeza i-<xliff:g id="APP_NAME">%1$s</xliff:g> ukufinyelela izithombe ezengeziwe?"</string>
<string name="permgrouprequest_microphone" msgid="2825208549114811299">"Vumela i-&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ukuthi irekhode umsindo?"</string>
<string name="permgrouprequestdetail_microphone" msgid="8510456971528228861">"Uhlelo lokusebenza luzokwazi ukurekhoda imisindo kuphela kuyilapho usebenzisa uhlelo lokusebenza"</string>
<string name="permgroupbackgroundrequest_microphone" msgid="8874462606796368183">"Vumela i-&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ukuthi irekhode umsindo?"</string>
@@ -580,4 +595,5 @@
<string name="show_clip_access_notification_summary" msgid="3532020182782112687">"Bonisa umlayezo uma ama-app wakho afinyelela umbhalo, izithombe, noma okunye okuqukethwe okukopishile"</string>
<string name="show_password_title" msgid="2877269286984684659">"Bonisa amaphasiwedi"</string>
<string name="show_password_summary" msgid="1110166488865981610">"Bonisa izinhlamvu kancane njengoba uthayipha"</string>
+ <string name="permission_rationale_message_template" msgid="4497650516269082051">"Le-app ithi ingabelana ngedatha ye-<xliff:g id="PERMISSION_NAME">%s</xliff:g> nezinkampani zangaphandle"</string>
</resources>
diff --git a/PermissionController/res/values/bools.xml b/PermissionController/res/values/bools.xml
index 17bb60cad..e97761843 100644
--- a/PermissionController/res/values/bools.xml
+++ b/PermissionController/res/values/bools.xml
@@ -18,4 +18,5 @@
<resources>
<bool name="is_at_least_t">false</bool>
+ <bool name="is_at_least_u">false</bool>
</resources>
diff --git a/PermissionController/res/values/overlayable.xml b/PermissionController/res/values/overlayable.xml
index 6e7973fb1..61ee28fb2 100644
--- a/PermissionController/res/values/overlayable.xml
+++ b/PermissionController/res/values/overlayable.xml
@@ -61,8 +61,34 @@
<item type="style" name="PermissionGrantButtonAllowOneTime" />
<item type="style" name="PermissionGrantButtonDeny" />
<item type="style" name="PermissionGrantButtonNoUpgrade" />
+
+ <item type="style" name="PermissionGrantPermissionRationaleContent" />
+ <item type="style" name="PermissionGrantPermissionRationaleIcon" />
+ <item type="style" name="PermissionGrantPermissionRationaleMessage" />
+ <item type="style" name="PermissionGrantPermissionRationaleMoreInfoIcon" />
+
<!-- END PERMISSION GRANT DIALOG -->
+ <!-- START PERMISSION RATIONALE DIALOG -->
+
+ <item type="style" name="PermissionRationaleScrollView" />
+ <item type="style" name="PermissionRationaleSingleton" />
+ <item type="style" name="PermissionRationaleDialog" />
+ <item type="style" name="PermissionRationaleContent" />
+
+ <item type="style" name="PermissionRationaleTitleContainer" />
+ <item type="style" name="PermissionRationaleTitleIcon" />
+ <item type="style" name="PermissionRationaleTitleMessage" />
+
+ <item type="style" name="PermissionRationaleSectionOuterContainer" />
+ <item type="style" name="PermissionRationaleSectionIcon" />
+ <item type="style" name="PermissionRationaleSectionInnerContainer" />
+ <item type="style" name="PermissionRationaleSectionTitle" />
+ <item type="style" name="PermissionRationaleSectionMessage" />
+
+ <item type="style" name="PermissionRationaleButtonContainer" />
+
+ <!-- END PERMISSION RATIONALE DIALOG -->
<!-- START PERMISSION REVIEW SCREEN -->
<item type="style" name="PermissionReview" />
@@ -101,6 +127,13 @@
<item type="style" name="LargeHeaderLink" />
<item type="style" name="LargeHeaderDivider" />
+ <item type="style" name="AppPermissionRationaleContainer"/>
+ <item type="style" name="AppPermissionRationaleContent"/>
+ <item type="style" name="AppPermissionRationaleTextContent"/>
+ <item type="style" name="AppPermissionRationaleTitle"/>
+ <item type="style" name="AppPermissionRationaleSubtitle"/>
+ <item type="style" name="AppPermissionRationaleIcon"/>
+
<item type="style" name="AppPermissionSelection" />
<item type="style" name="AppPermissionMessage" />
<item type="style" name="AppPermissionRadioButton" />
diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml
index 287f81865..7e3f5e803 100644
--- a/PermissionController/res/values/strings.xml
+++ b/PermissionController/res/values/strings.xml
@@ -67,6 +67,18 @@
<!-- Title for the dialog button to get more info about a permission. [CHAR LIMIT=15] -->
<string name="grant_dialog_button_more_info">More info</string>
+ <!-- Title for the dialog button to allow access to all photos. [CHAR LIMIT=60] -->
+ <string name="grant_dialog_button_allow_all_photos">Allow access to all photos</string>
+
+ <!-- Title for the dialog button to allow access to select photos to be shared. [CHAR LIMIT=60] -->
+ <string name="grant_dialog_button_allow_selected_photos">Select photos</string>
+
+ <!-- Title for the dialog button to allow access to select more photos to be shared. [CHAR LIMIT=60] -->
+ <string name="grant_dialog_button_allow_more_selected_photos">Select more photos</string>
+
+ <!-- Title for the dialog button to not allow access to select more photos to be shared. [CHAR LIMIT=60] -->
+ <string name="grant_dialog_button_dont_allow_more_selected_photos">Don\u2019t select more photos</string>
+
<!-- Title for the dialog button to deny a permission grant despite a warning of implications. [CHAR LIMIT=30] -->
<string name="grant_dialog_button_deny_anyway">Don\u2019t allow anyway</string>
@@ -412,25 +424,25 @@
<!-- Duration used for a permission in days -->
<string name="duration_used_days">{count, plural,
- =1 {1 day}
+ =1 {# day}
other {# days}
}</string>
<!-- Duration used for a permission in hours -->
<string name="duration_used_hours">{count, plural,
- =1 {1 hour}
+ =1 {# hour}
other {# hours}
}</string>
<!-- Duration used for a permission in minutes -->
<string name="duration_used_minutes">{count, plural,
- =1 {1 min}
+ =1 {# min}
other {# mins}
}</string>
<!-- Duration used for a permission in seconds -->
<string name="duration_used_seconds">{count, plural,
- =1 {1 sec}
+ =1 {# sec}
other {# secs}
}</string>
@@ -440,20 +452,23 @@
<!-- Description for showing permission accesses accessed any time [CHAR LIMIT=30] -->
<string name="permission_usage_any_time">Any time</string>
- <!-- Description for showing permissions accessed in the last 7 days [CHAR LIMIT=30] -->
- <string name="permission_usage_last_7_days">Last 7 days</string>
-
- <!-- Description for showing permissions accessed in the last day [CHAR LIMIT=30] -->
- <string name="permission_usage_last_day">Last 24 hours</string>
-
- <!-- Description for showing permissions accessed in the last hour [CHAR LIMIT=30] -->
- <string name="permission_usage_last_hour">Last 1 hour</string>
+ <!-- Description for showing permissions accessed in the last n days [CHAR LIMIT=30] -->
+ <string name="permission_usage_last_n_days">{count, plural,
+ =1 {Last # day}
+ other {Last # days}
+ }</string>
- <!-- Description for showing permissions accessed in the last 15 minutes [CHAR LIMIT=30] -->
- <string name="permission_usage_last_15_minutes">Last 15 minutes</string>
+ <!-- Description for showing permissions accessed in the last n hours [CHAR LIMIT=30] -->
+ <string name="permission_usage_last_n_hours">{count, plural,
+ =1 {Last # hour}
+ other {Last # hours}
+ }</string>
- <!-- Description for showing permissions accessed in the last minute [CHAR LIMIT=30] -->
- <string name="permission_usage_last_minute">Last 1 minute</string>
+ <!-- Description for showing permissions accessed in the last n minutes [CHAR LIMIT=30] -->
+ <string name="permission_usage_last_n_minutes">{count, plural,
+ =1 {Last # minute}
+ other {Last # minutes}
+ }</string>
<!-- Label when no apps have used the requested permissions [CHAR LIMIT=30] -->
<string name="no_permission_usages">No permission usages</string>
@@ -494,11 +509,17 @@
<!-- Label for the title of the permission bar chart showing how often the most common permissions are used [CHAR LIMIT=50] -->
<string name="permission_usage_bar_chart_title_last_minute">Permission usage in last 1 minute</string>
- <!-- Summary text if a permission usage is not used in past 24 hours [CHAR LIMIT=60] -->
- <string name="permission_usage_preference_summary_not_used_24h">Not used in past 24 hours</string>
+ <!-- Summary text if a permission usage is not used in past n days [CHAR LIMIT=60] -->
+ <string name="permission_usage_preference_summary_not_used_in_past_n_days">{count, plural,
+ =1 {Not used in past # day}
+ other {Not used in past # days}
+ }</string>
- <!-- Summary text if a permission usage is not used in past 7 days [CHAR LIMIT=60] -->
- <string name="permission_usage_preference_summary_not_used_7d">Not used in past 7 days</string>
+ <!-- Summary text if a permission usage is not used in past n hours [CHAR LIMIT=60] -->
+ <string name="permission_usage_preference_summary_not_used_in_past_n_hours">{count, plural,
+ =1 {Not used in past # hour}
+ other {Not used in past # hours}
+ }</string>
<!-- Label for the permission usage preference that shows how many apps have used various permissions [CHAR LIMIT=50] -->
<string name="permission_usage_preference_label">{count, plural,
@@ -584,6 +605,12 @@
<!-- Title for the dialog button to allow a permission grant only when the app is in the foreground. [CHAR LIMIT=60] -->
<string name="app_permission_button_allow_foreground">Allow only while using the app</string>
+ <!-- Title for the dialog button to allow the user to allow all photos. [CHAR LIMIT=60] -->
+ <string name="app_permission_button_allow_all_photos">Allow all photos</string>
+
+ <!-- Title for the dialog button to allow the user to select photos. [CHAR LIMIT=60] -->
+ <string name="app_permission_button_select_photos">Allow selected photos</string>
+
<!-- Title for the dialog button to require an app to ask for a permission next time they need it. [CHAR LIMIT=60] -->
<string name="app_permission_button_ask">Ask every time</string>
@@ -678,10 +705,14 @@
<string name="unused_apps_page_summary">If an app is unused for a few months:\n\n\u2022 Permissions are removed to protect your data\n\u2022 Notifications are stopped to save battery\n\u2022 Temporary files are removed to free up space\n\nTo allow permissions and notifications again, open the app.</string>
<!-- Summary for the screen on TV that shows all unused apps that have been hibernated [CHAR LIMIT=none] -->
- <string name="unused_apps_page_tv_summary">If an app is unused for a few months:\n\n\u2022 Permissions are removed to protect your data\n\u2022 Temporary files are removed to free up space\n\nTo allow permissions again, open the app.</string>
+ <string name="unused_apps_page_tv_summary">If an app is unused for a month:\n\n\u2022 Permissions are removed to protect your data\n\u2022 Temporary files are removed to free up space\n\nTo allow permissions again, open the app.</string>
<!-- Title for a category of apps that were last used several months ago [CHAR LIMIT=none] -->
- <string name="last_opened_category_title">Last opened more than <xliff:g id="number" example="3">%s</xliff:g> months ago</string>
+ <string name="last_opened_category_title"> {count, plural,
+ =1{Last opened more than # month ago}
+ other{Last opened more than # months ago}
+ }
+ </string>
<!-- Summary for showing when an app was last used [CHAR LIMIT=none] -->
<string name="last_opened_summary">App last opened on <xliff:g id="date" example="March 12, 2020">%s</xliff:g></string>
@@ -784,19 +815,19 @@
<!-- Time in hours -->
<string name="hours">{count, plural,
- =1 {1 hour}
+ =1 {# hour}
other {# hours}
}</string>
<!-- Time in minutes -->
<string name="minutes">{count, plural,
- =1 {1 minute}
+ =1 {# minute}
other {# minutes}
}</string>
<!-- Time in seconds -->
<string name="seconds">{count, plural,
- =1 {1 second}
+ =1 {# second}
other {# seconds}
}</string>
@@ -1182,12 +1213,29 @@
<!-- Description for the watch profile role. [CHAR LIMIT=NONE] -->
<string name="role_watch_description"><xliff:g id="app_name" example="Wear">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts and Calendar permissions.</string>
+ <!-- TODO(b/261147998): STOPSHIP update with finalized description string -->
+ <!-- Description for the glasses profile role. [CHAR LIMIT=NONE] -->
+ <string name="role_companion_device_glasses_description"><xliff:g id="app_name" example="GlassesApp">%1$s</xliff:g> will be allowed to interact with your notifications and access your Phone, SMS, Contacts, Microphone and Nearby devices permissions.</string>
+
<!-- Description for the app streaming profile role. [CHAR LIMIT=NONE] -->
<string name="role_app_streaming_description"><xliff:g id="app_name" example="Cross-Device Communciation">%1$s</xliff:g> will be allowed to interact with your notifications and stream your apps to the connected device.</string>
+ <!-- TODO(b/261147998): STOPSHIP update with finalized description string -->
+ <!-- Description for the nearby device streaming profile role. [CHAR LIMIT=NONE] -->
+ <string name="role_companion_device_nearby_device_streaming_description"><xliff:g id="app_name" example="NearbyStreamer">%1$s</xliff:g> will be allowed to stream content to nearby devices.</string>
+
<!-- Description for the companion device computer profile role. [CHAR LIMIT=NONE] -->
<string name="role_companion_device_computer_description">This service shares your photos, media, and notifications from your phone to other devices.</string>
+ <!-- Label for the NOTES role. [CHAR LIMIT=30] -->
+ <string name="role_notes_label">Default note taking app</string>
+ <!-- Short label for the NOTES role. [CHAR LIMIT=30] -->
+ <string name="role_notes_short_label">Note taking app</string>
+ <!-- Description for the NOTES role. [CHAR LIMIT=NONE] -->
+ <string name="role_notes_description">Apps that allow you to take notes</string>
+ <!-- Search keywords for the NOTES role. [CHAR LIMIT=NONE] -->
+ <string name="role_notes_search_keywords">notes</string>
+
<!-- Subtitle for the application that is the current default application [CHAR LIMIT=30] -->
<string name="request_role_current_default">Current default</string>
@@ -1425,6 +1473,10 @@ Allow <xliff:g id="app_name" example="Gmail">%4$s</xliff:g> to upload a bug repo
<string name="permgrouprequest_read_media_visual">Allow &lt;b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g>&lt;/b> to access photos and videos on this device?</string>
<!-- Message shown to the user when the apps requests permission from this group. If ever possible this should stay below 80 characters (assuming the parameters takes 20 characters). Don't abbreviate until the message reaches 120 characters though. [CHAR LIMIT=120] -->
+ <string name="permgrouprequest_more_photos">Grant
+ &lt;b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g>&lt;/b> access to more photos?</string>
+
+ <!-- Message shown to the user when the apps requests permission from this group. If ever possible this should stay below 80 characters (assuming the parameters takes 20 characters). Don't abbreviate until the message reaches 120 characters though. [CHAR LIMIT=120] -->
<string name="permgrouprequest_microphone">Allow
&lt;b><xliff:g id="app_name" example="Gmail">%1$s</xliff:g>&lt;/b> to record audio?</string>
<!-- Subtitle of the message shown to the user when the apps requests permission to use the microphone only while app is in foreground [CHAR LIMIT=150]-->
@@ -1682,4 +1734,13 @@ Allow <xliff:g id="app_name" example="Gmail">%4$s</xliff:g> to upload a bug repo
<!-- Summary for toggle controlling whether to show the first letter while typing passwords. [CHAR LIMIT=NONE] -->
<string name="show_password_summary">Display characters briefly as you type</string>
+ <!-- TODO(b/259279178): update with finalized permission rationale strings -->
+ <!-- Template for the permission rationale message when an app requests a permission. Third
+ parties are other organizations outside of the app developer. These could be companies or even
+ governmental organizations. But because we aren't able to be inclusive of all possibilities,
+ phrasing should be as generic as possible while still helping users understand they aren't just
+ sharing data with the developer company. [CHAR LIMIT=100] -->
+ <string name="permission_rationale_message_template">This app stated it may share
+ <xliff:g id="permission_name" example="location">%s</xliff:g> data with third parties</string>
+
</resources>
diff --git a/PermissionController/res/values/styles.xml b/PermissionController/res/values/styles.xml
index 3826a853d..39d515951 100644
--- a/PermissionController/res/values/styles.xml
+++ b/PermissionController/res/values/styles.xml
@@ -104,13 +104,20 @@
<item name="android:orientation">horizontal</item>
</style>
+ <style name="PermissionLocationAccuracyRadioGroupMaterial3">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginBottom">24dp</item>
+ <item name="android:gravity">center_horizontal</item>
+ <item name="android:orientation">horizontal</item>
+ </style>
+
<style name="PermissionLocationAccuracyRadioFine">
<item name="android:button">@null</item>
<item name="android:background">@null</item>
<item name="android:gravity">center_horizontal</item>
<item name="android:layout_marginEnd">16dp</item>
<item name="android:drawablePadding">8dp</item>
- <item name="android:gravity">center_horizontal</item>
</style>
<style name="PermissionLocationAccuracyRadioCoarse">
@@ -119,7 +126,6 @@
<item name="android:gravity">center_horizontal</item>
<item name="android:layout_marginStart">16dp</item>
<item name="android:drawablePadding">8dp</item>
- <item name="android:gravity">center_horizontal</item>
</style>
<style name="PermissionLocationAccuracyFineImageView">
@@ -129,6 +135,12 @@
<item name="android:layout_marginBottom">24dp</item>
</style>
+ <style name="PermissionLocationAccuracyFineImageViewMaterial3">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginBottom">24dp</item>
+ </style>
+
<style name="PermissionLocationAccuracyCoarseImageView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
@@ -136,6 +148,12 @@
<item name="android:layout_marginBottom">24dp</item>
</style>
+ <style name="PermissionLocationAccuracyCoarseImageViewMaterial3">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginBottom">24dp</item>
+ </style>
+
<style name="PermissionGrantButtonList">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
@@ -165,6 +183,44 @@
<item name="android:background">?android:attr/selectableItemBackground</item>
</style>
+ <style name="PermissionGrantPermissionRationaleContent">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginStart">24dp</item>
+ <item name="android:layout_marginEnd">24dp</item>
+ <item name="android:layout_marginBottom">24dp</item>
+ <item name="android:padding">12dp</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:background">@drawable/grant_dialog_permission_rationale_background</item>
+ </style>
+
+ <style name="PermissionGrantPermissionRationaleIcon">
+ <item name="android:layout_width">20dp</item>
+ <item name="android:layout_height">20dp</item>
+ <item name="android:layout_gravity">start|center_vertical</item>
+ <item name="android:scaleType">centerInside</item>
+ <item name="android:tint">?android:attr/textColorSecondary</item>
+ </style>
+
+ <style name="PermissionGrantPermissionRationaleMessage"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_weight">1</item>
+ <item name="android:layout_marginStart">12dp</item>
+ <item name="android:layout_marginEnd">12dp</item>
+ <item name="android:textSize">14sp</item>
+ </style>
+
+ <style name="PermissionGrantPermissionRationaleMoreInfoIcon">
+ <item name="android:layout_width">20dp</item>
+ <item name="android:layout_height">20dp</item>
+ <item name="android:layout_gravity">end|center_vertical</item>
+ <item name="android:scaleType">centerInside</item>
+ <item name="android:tint">?android:attr/textColorSecondary</item>
+ </style>
+
<!-- for use in overlays -->
<style name="PermissionGrantButtonAllow"
parent="@style/PermissionGrantButton"></style>
@@ -172,6 +228,14 @@
parent="@style/PermissionGrantButton"></style>
<style name="PermissionGrantButtonAllowOneTime"
parent="@style/PermissionGrantButton"></style>
+ <style name="PermissionGrantButtonAllowAllPhotos"
+ parent="@style/PermissionGrantButton"></style>
+ <style name="PermissionGrantButtonAllowMorePhotos"
+ parent="@style/PermissionGrantButton"></style>
+ <style name="PermissionGrantButtonAllowSelectedPhotos"
+ parent="@style/PermissionGrantButton"></style>
+ <style name="PermissionGrantButtonDontAllowMorePhotos"
+ parent="@style/PermissionGrantButton"></style>
<style name="PermissionGrantButtonDeny"
parent="@style/PermissionGrantButton"></style>
<style name="PermissionGrantButtonNoUpgrade"
@@ -187,9 +251,126 @@
parent="@style/PermissionGrantButton"></style>
<style name="PermissionGrantButtonNoUpgradeMaterial3"
parent="@style/PermissionGrantButton"></style>
+ <style name="PermissionGrantButtonAllowAllPhotosMaterial3"
+ parent="@style/PermissionGrantButton"></style>
+ <style name="PermissionGrantButtonAllowSelectedPhotosMaterial3"
+ parent="@style/PermissionGrantButton"></style>
+ <style name="PermissionGrantButtonAllowMorePhotosMaterial3"
+ parent="@style/PermissionGrantButton"></style>
+ <style name="PermissionGrantButtonDontAllowMorePhotosMaterial3"
+ parent="@style/PermissionGrantButton"></style>
<!-- END PERMISSION GRANT DIALOG -->
+ <!-- START PERMISSION RATIONALE DIALOG -->
+
+ <style name="PermissionRationaleScrollView">
+ <item name="android:scrollbars">none</item>
+ <item name="android:fillViewport">true</item>
+ <item name="android:clipChildren">false</item>
+ </style>
+
+ <style name="PermissionRationaleSingleton">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:gravity">center</item>
+ </style>
+
+ <style name="PermissionRationaleDialog">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:background">?android:attr/windowBackground</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:showDividers">middle</item>
+ </style>
+
+ <style name="PermissionRationaleContent">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:paddingTop">24dp</item>
+ <item name="android:paddingBottom">18dp</item>
+ <item name="android:paddingStart">24dp</item>
+ <item name="android:paddingEnd">24dp</item>
+ </style>
+
+ <style name="PermissionRationaleTitleContainer">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:gravity">center</item>
+ </style>
+
+ <style name="PermissionRationaleTitleIcon">
+ <item name="android:layout_width">32dp</item>
+ <item name="android:layout_height">32dp</item>
+ <item name="android:layout_marginBottom">16dp</item>
+ <item name="android:tint">?android:attr/textColorSecondary</item>
+ <item name="android:scaleType">centerInside</item>
+ </style>
+
+ <style name="PermissionRationaleTitleMessage"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">24sp</item>
+ <item name="android:lineHeight">32sp</item>
+ <item name="android:gravity">center</item>
+ </style>
+
+ <style name="PermissionRationaleSectionOuterContainer">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:layout_marginTop">16dp</item>
+ </style>
+
+ <style name="PermissionRationaleSectionIcon">
+ <item name="android:layout_width">20dp</item>
+ <item name="android:layout_height">20dp</item>
+ <item name="android:tint">?android:attr/textColorSecondary</item>
+ <item name="android:scaleType">centerInside</item>
+ </style>
+
+ <style name="PermissionRationaleSectionInnerContainer">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:layout_marginStart">16dp</item>
+ </style>
+
+ <style name="PermissionRationaleSectionTitle"
+ parent="@android:style/TextAppearance.DeviceDefault.Medium">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:lineHeight">20sp</item>
+ <item name="android:lineSpacingMultiplier">1.25</item>
+ </style>
+
+ <style name="PermissionRationaleSectionMessage"
+ parent="@android:style/TextAppearance.DeviceDefault">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">4dp</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:lineHeight">20sp</item>
+ <item name="android:lineSpacingMultiplier">1.25</item>
+ </style>
+
+ <style name="PermissionRationaleButtonContainer">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">32dp</item>
+ <item name="android:orientation">horizontal</item>
+ <item name="android:gravity">end</item>
+ </style>
+
+ <!-- END PERMISSION RATIONALE DIALOG -->
+
<!-- START PERMISSION REVIEW SCREEN -->
<style name="PermissionReview">
@@ -382,6 +563,55 @@
<item name="android:textDirection">locale</item>
</style>
+ <!-- APP PERMISSION RATIONALE CONTAINER -->
+ <style name="AppPermissionRationaleContainer">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:layout_marginBottom">24dp</item>
+ <item name="android:orientation">vertical</item>
+ </style>
+
+ <style name="AppPermissionRationaleContent">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginTop">16dp</item>
+ <item name="android:layout_marginBottom">24dp</item>
+ <item name="android:orientation">horizontal</item>
+ </style>
+
+ <style name="AppPermissionRationaleTextContent">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginStart">16dp</item>
+ <item name="android:orientation">vertical</item>
+ <item name="android:layout_weight">1</item>
+ </style>
+
+ <style name="AppPermissionRationaleTitle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textDirection">locale</item>
+ </style>
+
+ <style name="AppPermissionRationaleSubtitle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:textAppearance">?android:attr/textAppearanceSmall</item>
+ <item name="android:textDirection">locale</item>
+ </style>
+
+ <style name="AppPermissionRationaleIcon">
+ <item name="android:layout_width">20dp</item>
+ <item name="android:layout_height">20dp</item>
+ <item name="android:layout_gravity">start|center_vertical</item>
+ <item name="android:scaleType">centerInside</item>
+ <item name="android:tint">?android:attr/textColorSecondary</item>
+ </style>
+ <!-- END APP PERMISSION RATIONALE CONTAINER -->
+
<style name="AppPermissionRadioButton"
parent="@android:style/Widget.DeviceDefault.CompoundButton.RadioButton">
<item name="android:layout_width">match_parent</item>
@@ -1106,18 +1336,16 @@
<item name="android:paddingStart">?android:attr/listPreferredItemPaddingStart</item>
</style>
- <style name="WarningBannerCardView"
- xmlns:card_view="http://schemas.android.com/apk/res-auto"
- xmlns:app="http://schemas.android.com/apk/res-auto" >
+ <style name="WarningBannerCardView">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
- <item name="app:cardCornerRadius">20dp</item>
- <item name="app:cardBackgroundColor">@color/warning_surface</item>
- <item name="app:cardElevation">0dp</item>
- <item name="card_view:contentPaddingBottom">8dp</item>
- <item name="card_view:contentPaddingTop">20dp</item>
- <item name="card_view:contentPaddingLeft">20dp</item>
- <item name="card_view:contentPaddingRight">20dp</item>
+ <item name="cardCornerRadius">20dp</item>
+ <item name="cardBackgroundColor">@color/warning_surface</item>
+ <item name="cardElevation">0dp</item>
+ <item name="contentPaddingBottom">8dp</item>
+ <item name="contentPaddingTop">20dp</item>
+ <item name="contentPaddingLeft">20dp</item>
+ <item name="contentPaddingRight">20dp</item>
</style>
<style name="WarningBannerIcon">
diff --git a/PermissionController/res/values/themes.xml b/PermissionController/res/values/themes.xml
index 16ff3c15a..76196a050 100644
--- a/PermissionController/res/values/themes.xml
+++ b/PermissionController/res/values/themes.xml
@@ -57,8 +57,12 @@
<item name="android:windowIsTranslucent">true</item>
</style>
- <style name="GrantPermissions.Car">
- <item name="carUiActivity">true</item>
+ <style name="GrantPermissions.Car" parent="Theme.CarUi.NoToolbar">
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <!-- The following attributes change the behavior of the dialog, hence they should not be
+ themed -->
+ <item name="android:windowIsTranslucent">true</item>
</style>
<!-- Unused since R but exposed as overlayable. -->
@@ -101,7 +105,7 @@
<item name="android:background">@color/divider_color_primary</item>
</style>
- <style name="Theme.DeviceDefault.Dialog.Alert.DayNight" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+ <style name="Theme.DeviceDefault.Dialog.NoActionBar.DayNight" parent="@android:style/Theme.DeviceDefault.Light.Dialog.NoActionBar" />
<!-- Do not allow OEMs to overlay these themes.
diff --git a/PermissionController/res/xml-v34/app_data_sharing_updates.xml b/PermissionController/res/xml-v34/app_data_sharing_updates.xml
new file mode 100644
index 000000000..f73944dd7
--- /dev/null
+++ b/PermissionController/res/xml-v34/app_data_sharing_updates.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<!-- TODO(b/261666772): Extract to styles where necessary and update to final spec. -->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+ <Preference android:key="subtitle"/>
+ <PreferenceCategory android:key="last_period_updates" />
+ <com.android.permissioncontroller.permission.ui.handheld.v34.FooterWithLinkPreference
+ android:key="info_footer"
+ android:icon="@drawable/ic_info_outline"
+ android:selectable="false" />
+</PreferenceScreen> \ No newline at end of file
diff --git a/PermissionController/res/xml-v34/safety_center_subpage.xml b/PermissionController/res/xml-v34/safety_center_subpage.xml
new file mode 100644
index 000000000..8d15cda84
--- /dev/null
+++ b/PermissionController/res/xml-v34/safety_center_subpage.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <com.android.permissioncontroller.safetycenter.ui.ComparablePreferenceCategory
+ android:key="subpage_issue_group"
+ android:layout="@layout/preference_category_no_label"
+ app:selectable="false" />
+
+ <com.android.permissioncontroller.safetycenter.ui.ComparablePreferenceCategory
+ android:key="subpage_entry_group"
+ app:selectable="false" />
+</PreferenceScreen>
diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml
index 1a37f3538..f2cd53ebc 100644
--- a/PermissionController/res/xml/roles.xml
+++ b/PermissionController/res/xml/roles.xml
@@ -84,6 +84,7 @@
<permission name="android.permission.BLUETOOTH_ADVERTISE" minSdkVersion="31" />
<permission name="android.permission.BLUETOOTH_CONNECT" minSdkVersion="31" />
<permission name="android.permission.BLUETOOTH_SCAN" minSdkVersion="31" />
+ <permission name="android.permission.NEARBY_WIFI_DEVICES" minSdkVersion="33" />
</permission-set>
<permission-set name="notifications">
@@ -101,7 +102,8 @@
label="@string/role_assistant_label"
overrideUserWhenGranting="true"
requestable="false"
- shortLabel="@string/role_assistant_short_label">
+ shortLabel="@string/role_assistant_short_label"
+ uiBehavior="AssistantRoleUiBehavior">
<required-components>
<!-- Qualified components are determined int AssistantRoleBehavior. This comment here is
ignored and represents just a rough description
@@ -159,7 +161,8 @@
overrideUserWhenGranting="true"
requestDescription="@string/role_browser_request_description"
requestTitle="@string/role_browser_request_title"
- shortLabel="@string/role_browser_short_label">
+ shortLabel="@string/role_browser_short_label"
+ uiBehavior="BrowserRoleUiBehavior">
<!--
~ Required components matching is handled in BrowserRoleBehavior because it needs the
~ PackageManager.MATCH_ALL flag and other manual filtering, which cannot fit in our
@@ -201,7 +204,8 @@
requestDescription="@string/role_dialer_request_description"
requestTitle="@string/role_dialer_request_title"
searchKeywords="@string/role_dialer_search_keywords"
- shortLabel="@string/role_dialer_short_label">
+ shortLabel="@string/role_dialer_short_label"
+ uiBehavior="DialerRoleUiBehavior">
<required-components>
<activity>
<intent-filter>
@@ -287,7 +291,8 @@
requestDescription="@string/role_sms_request_description"
requestTitle="@string/role_sms_request_title"
searchKeywords="@string/role_sms_search_keywords"
- shortLabel="@string/role_sms_short_label">
+ shortLabel="@string/role_sms_short_label"
+ uiBehavior="SmsRoleUiBehavior">
<required-components>
<receiver permission="android.permission.BROADCAST_SMS">
<intent-filter>
@@ -377,7 +382,8 @@
requestTitle="@string/role_emergency_request_title"
searchKeywords="@string/role_emergency_search_keywords"
shortLabel="@string/role_emergency_short_label"
- systemOnly="true">
+ systemOnly="true"
+ uiBehavior="EmergencyRoleUiBehavior">
<required-components>
<activity>
<intent-filter>
@@ -407,7 +413,8 @@
requestDescription="@string/role_home_request_description"
requestTitle="@string/role_home_request_title"
searchKeywords="@string/role_home_search_keywords"
- shortLabel="@string/role_home_short_label">
+ shortLabel="@string/role_home_short_label"
+ uiBehavior="HomeRoleUiBehavior">
<!-- Also used by HomeRoleBehavior.getFallbackHolder(). -->
<required-components>
<activity>
@@ -600,6 +607,7 @@
<permission name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY" minSdkVersion="33" />
<permission name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
minSdkVersion="33" />
+ <permission name="android.permission.MANAGE_DEVICE_LOCK_STATE" minSdkVersion="34" />
</permissions>
</role>
@@ -1108,6 +1116,43 @@
</permissions>
</role>
+ <role
+ name="android.app.role.COMPANION_DEVICE_GLASSES"
+ behavior="CompanionDeviceGlassesRoleBehavior"
+ description="@string/role_companion_device_glasses_description"
+ exclusive="false"
+ minSdkVersion="34"
+ systemOnly="false"
+ visible="false">
+ <permissions>
+ <permission-set name="contacts" />
+ <permission-set name="microphone" />
+ <permission-set name="nearby_devices" />
+ <permission-set name="notifications" />
+ <permission-set name="phone" />
+ <permission-set name="sms" />
+ </permissions>
+ <app-op-permissions>
+ <app-op-permission name="android.permission.MANAGE_ONGOING_CALLS" />
+ </app-op-permissions>
+ </role>
+
+ <role
+ name="android.app.role.COMPANION_DEVICE_NEARBY_DEVICE_STREAMING"
+ allowBypassingQualification="true"
+ description="@string/role_companion_device_nearby_device_streaming_description"
+ exclusive="false"
+ minSdkVersion="34"
+ systemOnly="true"
+ visible="false">
+ <permissions>
+ <permission-set name="nearby_devices" />
+ <permission name="android.permission.CREATE_VIRTUAL_DEVICE" />
+ <permission name="android.permission.ADD_TRUSTED_DISPLAY" />
+ <permission name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY" />
+ </permissions>
+ </role>
+
<role
name="android.app.role.SYSTEM_SUPERVISION"
defaultHolders="config_systemSupervision"
@@ -1118,6 +1163,7 @@
visible="false" >
<permissions>
<permission name="android.permission.ACCESS_INSTANT_APPS"/>
+ <permission name="android.permission.KILL_UID" minSdkVersion="34"/>
<permission name="android.permission.SUSPEND_APPS"/>
<permission name="android.permission.SYSTEM_APPLICATION_OVERLAY"/>
</permissions>
@@ -1187,6 +1233,7 @@
<permission name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES" />
<permission name="android.permission.QUERY_ADMIN_POLICY" />
<permission name="android.permission.TRIGGER_LOST_MODE" />
+ <permission name="android.permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS" />
</permissions>
</role>
@@ -1340,4 +1387,76 @@
<permission name="android.permission.NET_TUNNELING" />
</permissions>
</role>
+
+ <!--
+ ~ A role assigned to the financing kiosk app
+ -->
+ <role
+ name="android.app.role.FINANCED_DEVICE_KIOSK"
+ exclusive="true"
+ minSdkVersion="34"
+ visible="false">
+ <permissions>
+ <permission name="android.permission.MANAGE_DEVICE_LOCK_STATE" />
+ </permissions>
+ </role>
+
+ <!---
+ ~ A role for the wear health service that handles health/fitness tracking features.
+ -->
+ <role
+ name="android.app.role.SYSTEM_WEAR_HEALTH_SERVICE"
+ behavior="SystemWearHealthServiceRoleBehavior"
+ defaultHolders="config_systemWearHealthService"
+ exclusive="true"
+ minSdkVersion="33"
+ static="true"
+ systemOnly="true"
+ visible="false">
+ <permissions>
+ <permission-set name="sensors" />
+ <permission-set name="location" />
+ <permission name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+ <permission name="android.permission.ACTIVITY_RECOGNITION" />
+ </permissions>
+ </role>
+
+ <!---
+ ~ A role for the package that responds to system notes actions.
+ -->
+ <role
+ name="android.app.role.NOTES"
+ behavior="NotesRoleBehavior"
+ defaultHolders="config_defaultNotes"
+ description="@string/role_notes_description"
+ exclusive="true"
+ label="@string/role_notes_label"
+ minSdkVersion="34"
+ overrideUserWhenGranting="true"
+ requestable="false"
+ searchKeywords="@string/role_notes_search_keywords"
+ shortLabel="@string/role_notes_short_label"
+ showNone="true">
+ <required-components>
+ <!-- Flag value is FLAG_SHOW_WHEN_LOCKED | FLAG_TURN_SCREEN_ON -->
+ <activity flags="0x1800000">
+ <intent-filter>
+ <action name="android.intent.action.CREATE_NOTE" />
+ </intent-filter>
+ </activity>
+ </required-components>
+ <preferred-activities>
+ <preferred-activity>
+ <!-- Flag value is FLAG_SHOW_WHEN_LOCKED | FLAG_TURN_SCREEN_ON -->
+ <activity flags="0x1800000">
+ <intent-filter>
+ <action name="android.intent.action.CREATE_NOTE" />
+ </intent-filter>
+ </activity>
+ <intent-filter>
+ <action name="android.intent.action.CREATE_NOTE" />
+ </intent-filter>
+ </preferred-activity>
+ </preferred-activities>
+ </role>
</roles>
diff --git a/PermissionController/res/xml/settings_button_with_divider_preference_widget.xml b/PermissionController/res/xml/settings_button_with_divider_preference_widget.xml
new file mode 100644
index 000000000..b9df7da7d
--- /dev/null
+++ b/PermissionController/res/xml/settings_button_with_divider_preference_widget.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<!-- TODO(b/261666772): Extract to styles where necessary and update to final spec. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+ <!-- TODO(b/261666772): Rename AutoRevokeDivider style to generic style. -->
+ <View
+ android:id="@+id/divider"
+ android:layout_width="1dp"
+ android:layout_height="24dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginEnd="16dp"
+ android:layout_gravity="end|center_vertical"
+ android:theme="@style/AutoRevokeDivider"/>
+
+ <ImageButton
+ android:id="@+id/settings_button"
+ style="@style/SettingsActionButton"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="12dp"
+ android:layout_gravity="end|center_vertical"
+ android:contentDescription="@string/settings"
+ android:src="@drawable/ic_settings_gear"/>
+
+</LinearLayout> \ No newline at end of file
diff --git a/PermissionController/role-controller/Android.bp b/PermissionController/role-controller/Android.bp
new file mode 100644
index 000000000..1a790b730
--- /dev/null
+++ b/PermissionController/role-controller/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "role-controller",
+ srcs: [
+ "java/**/*.java",
+ ],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
+ static_libs: [
+ "modules-utils-build_system",
+ ],
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ installable: false,
+ min_sdk_version: "30",
+ sdk_version: "system_current",
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java
index 66dd8ccb9..12710cfd3 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/AssistantRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.app.ActivityManager;
import android.app.role.RoleManager;
@@ -27,7 +27,6 @@ import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.os.Process;
import android.os.UserHandle;
-import android.provider.Settings;
import android.service.voice.VoiceInteractionService;
import android.util.ArraySet;
import android.util.AttributeSet;
@@ -37,8 +36,9 @@ import android.util.Xml;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.UserUtils;
import org.xmlpull.v1.XmlPullParserException;
@@ -78,33 +78,6 @@ public class AssistantRoleBehavior implements RoleBehavior {
return !UserUtils.isProfile(user, context);
}
- @Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
- @NonNull Context context) {
- return VisibilityMixin.isVisible("config_showDefaultAssistant", context);
- }
-
- @Nullable
- @Override
- public Intent getManageIntentAsUser(@NonNull Role role, @NonNull UserHandle user,
- @NonNull Context context) {
- boolean isAutomotive =
- context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
-
- if (isAutomotive) {
- return null;
- }
-
- return new Intent(Settings.ACTION_VOICE_INPUT_SETTINGS);
- }
-
- @Nullable
- @Override
- public CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
- @NonNull Context context) {
- return context.getString(R.string.assistant_confirmation_message);
- }
-
@Nullable
@Override
public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user,
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/AutomotiveRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/AutomotiveRoleBehavior.java
index 3596c7d91..6eedb8f4d 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/AutomotiveRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/AutomotiveRoleBehavior.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -22,6 +22,9 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+
/**
* Class for behavior of the Automotive role.
*/
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/BrowserRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java
index 9d4d9e08b..3b05accfd 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/BrowserRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import android.content.Intent;
@@ -29,10 +29,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
-import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.permission.utils.CollectionUtils;
-import com.android.permissioncontroller.role.utils.PackageUtils;
-import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.model.Permissions;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.CollectionUtils;
+import com.android.role.controller.util.PackageUtils;
+import com.android.role.controller.util.UserUtils;
import java.util.ArrayList;
import java.util.Arrays;
@@ -136,8 +138,8 @@ public class BrowserRoleBehavior implements RoleBehavior {
// #grantDefaultPermissionsToDefaultBrowser(java.lang.String, int)
if (SdkLevel.isAtLeastS()) {
if (PackageUtils.isSystemPackage(packageName, context)) {
- Permissions.grant(packageName, SYSTEM_BROWSER_PERMISSIONS, false, false, false,
- true, false, context);
+ Permissions.grant(packageName, SYSTEM_BROWSER_PERMISSIONS, false, false, true,
+ false, false, context);
}
}
}
@@ -146,15 +148,9 @@ public class BrowserRoleBehavior implements RoleBehavior {
public void revoke(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
if (SdkLevel.isAtLeastT()) {
if (PackageUtils.isSystemPackage(packageName, context)) {
- Permissions.revoke(packageName, SYSTEM_BROWSER_PERMISSIONS, false, true, false,
+ Permissions.revoke(packageName, SYSTEM_BROWSER_PERMISSIONS, true, false, false,
context);
}
}
}
-
- @Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
- @NonNull Context context) {
- return context.getResources().getBoolean(R.bool.config_showBrowserRole);
- }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/CompanionDeviceAppStreamingRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceAppStreamingRoleBehavior.java
index ca4af2355..131690fd7 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/CompanionDeviceAppStreamingRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceAppStreamingRoleBehavior.java
@@ -14,13 +14,15 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import androidx.annotation.NonNull;
-import com.android.permissioncontroller.role.utils.NotificationUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.NotificationUtils;
/**
* Class for behavior of the "App Streaming" Companion device profile role.
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/CompanionDeviceComputerRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceComputerRoleBehavior.java
index 1d9409f1f..c59d5e58d 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/CompanionDeviceComputerRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceComputerRoleBehavior.java
@@ -14,13 +14,15 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import androidx.annotation.NonNull;
-import com.android.permissioncontroller.role.utils.NotificationUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.NotificationUtils;
/**
* Class for behavior of the "Computer" Companion device profile role.
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceGlassesRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceGlassesRoleBehavior.java
new file mode 100644
index 000000000..2e4691b9c
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceGlassesRoleBehavior.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.behavior;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.NotificationUtils;
+
+/**
+ * Class for behavior of the "glasses" Companion device profile role.
+ */
+public class CompanionDeviceGlassesRoleBehavior implements RoleBehavior {
+
+ @Override
+ public void grant(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
+ NotificationUtils.grantNotificationAccessForPackage(context, packageName);
+ }
+
+ @Override
+ public void revoke(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
+ NotificationUtils.revokeNotificationAccessForPackage(context, packageName);
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/CompanionDeviceWatchRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceWatchRoleBehavior.java
index 75675fb00..233c0d92e 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/CompanionDeviceWatchRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/CompanionDeviceWatchRoleBehavior.java
@@ -14,13 +14,15 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import androidx.annotation.NonNull;
-import com.android.permissioncontroller.role.utils.NotificationUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.NotificationUtils;
/**
* Class for behavior of the "watch" Companion device profile role.
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/DevicePolicyManagementRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/DevicePolicyManagementRoleBehavior.java
index 8232847e0..648b7d198 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/DevicePolicyManagementRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/DevicePolicyManagementRoleBehavior.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
@@ -23,6 +23,9 @@ import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+
/**
* Class for behavior of the device policy management role.
*/
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/DialerRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java
index 36be7e88a..79c139cee 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/DialerRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java
@@ -14,25 +14,22 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.os.UserHandle;
-import android.telecom.TelecomManager;
import android.telephony.TelephonyManager;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.preference.Preference;
import com.android.modules.utils.build.SdkLevel;
-import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.role.controller.model.Permissions;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.PackageUtils;
import java.util.Arrays;
import java.util.List;
-import java.util.Objects;
/**
* Class for behavior of the dialer role.
@@ -58,33 +55,6 @@ public class DialerRoleBehavior implements RoleBehavior {
}
@Override
- public void prepareApplicationPreferenceAsUser(@NonNull Role role,
- @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
- @NonNull UserHandle user, @NonNull Context context) {
- TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
- String systemPackageName = telecomManager.getSystemDialerPackage();
- if (Objects.equals(applicationInfo.packageName, systemPackageName)) {
- preference.setSummary(R.string.default_app_system_default);
- } else {
- preference.setSummary(null);
- }
- }
-
- @Nullable
- @Override
- public CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
- @NonNull Context context) {
- return EncryptionUnawareConfirmationMixin.getConfirmationMessage(role, packageName,
- context);
- }
-
- @Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
- @NonNull Context context) {
- return context.getResources().getBoolean(R.bool.config_showDialerRole);
- }
-
- @Override
public void grant(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
if (SdkLevel.isAtLeastS()) {
if (PackageUtils.isSystemPackage(packageName, context)) {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/DocumentManagerRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/DocumentManagerRoleBehavior.java
index ce307fd82..ee7984274 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/DocumentManagerRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/DocumentManagerRoleBehavior.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import android.os.Process;
@@ -22,6 +22,9 @@ import android.util.Log;
import androidx.annotation.NonNull;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+
import java.util.Collections;
import java.util.List;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/EmergencyRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java
index d0b2bf42a..a54006bb5 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/EmergencyRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -25,7 +25,9 @@ import android.telephony.TelephonyManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.PackageUtils;
import java.util.List;
@@ -66,18 +68,4 @@ public class EmergencyRoleBehavior implements RoleBehavior {
}
return fallbackPackageInfo != null ? fallbackPackageInfo.packageName : null;
}
-
- @Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
- @NonNull Context context) {
- return VisibilityMixin.isVisible("config_showDefaultEmergency", context);
- }
-
- @Nullable
- @Override
- public CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
- @NonNull Context context) {
- return EncryptionUnawareConfirmationMixin.getConfirmationMessage(role, packageName,
- context);
- }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/HomeRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java
index af6acfeec..3254bc6e4 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/HomeRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java
@@ -14,31 +14,24 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
-import android.app.admin.DevicePolicyResources.Strings.DefaultAppSettings;
-import android.app.role.RoleManager;
-import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
-import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.preference.Preference;
-import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.permission.utils.CollectionUtils;
-import com.android.permissioncontroller.permission.utils.Utils;
-import com.android.permissioncontroller.role.ui.TwoTargetPreference;
-import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.model.Permissions;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.UserUtils;
import java.util.Arrays;
import java.util.List;
@@ -53,8 +46,6 @@ import java.util.Objects;
*/
public class HomeRoleBehavior implements RoleBehavior {
- private static final String LOG_TAG = HomeRoleBehavior.class.getSimpleName();
-
private static final List<String> AUTOMOTIVE_PERMISSIONS = Arrays.asList(
android.Manifest.permission.READ_CALL_LOG,
android.Manifest.permission.WRITE_CALL_LOG,
@@ -100,71 +91,10 @@ public class HomeRoleBehavior implements RoleBehavior {
return packageName;
}
- @Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
- @NonNull Context context) {
- return VisibilityMixin.isVisible("config_showDefaultHome", context);
- }
-
- @Override
- public void preparePreferenceAsUser(@NonNull Role role, @NonNull TwoTargetPreference preference,
- @NonNull UserHandle user, @NonNull Context context) {
- TwoTargetPreference.OnSecondTargetClickListener listener = null;
- RoleManager roleManager = context.getSystemService(RoleManager.class);
- String packageName = CollectionUtils.firstOrNull(roleManager.getRoleHoldersAsUser(
- role.getName(), user));
- if (packageName != null) {
- Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
- .setPackage(packageName)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- PackageManager userPackageManager = UserUtils.getUserContext(context, user)
- .getPackageManager();
- ResolveInfo resolveInfo = userPackageManager.resolveActivity(intent, 0);
- if (resolveInfo != null && resolveInfo.activityInfo != null
- && resolveInfo.activityInfo.exported) {
- listener = preference2 -> {
- try {
- context.startActivity(intent);
- } catch (ActivityNotFoundException e) {
- Log.e(LOG_TAG, "Cannot start activity for home app preferences", e);
- }
- };
- }
- }
- preference.setOnSecondTargetClickListener(listener);
- }
-
- @Override
- public boolean isApplicationVisibleAsUser(@NonNull Role role,
- @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user,
- @NonNull Context context) {
- // Home is not available for work profile, so we can just use the current user.
- return !isSettingsApplication(applicationInfo, context);
- }
-
- @Override
- public void prepareApplicationPreferenceAsUser(@NonNull Role role,
- @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
- @NonNull UserHandle user, @NonNull Context context) {
- boolean missingWorkProfileSupport = isMissingWorkProfileSupport(applicationInfo, context);
- preference.setEnabled(!missingWorkProfileSupport);
- preference.setSummary(missingWorkProfileSupport ? Utils.getEnterpriseString(context,
- DefaultAppSettings.HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE,
- R.string.home_missing_work_profile_support) : null);
- }
-
- private boolean isMissingWorkProfileSupport(@NonNull ApplicationInfo applicationInfo,
- @NonNull Context context) {
- boolean hasWorkProfile = UserUtils.getWorkProfile(context) != null;
- if (!hasWorkProfile) {
- return false;
- }
- boolean isWorkProfileSupported = applicationInfo.targetSdkVersion
- >= Build.VERSION_CODES.LOLLIPOP;
- return !isWorkProfileSupported;
- }
-
- private boolean isSettingsApplication(@NonNull ApplicationInfo applicationInfo,
+ /**
+ * Check if the application is a settings application
+ */
+ public static boolean isSettingsApplication(@NonNull ApplicationInfo applicationInfo,
@NonNull Context context) {
PackageManager packageManager = context.getPackageManager();
ResolveInfo resolveInfo = packageManager.resolveActivity(new Intent(
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/NotesRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/NotesRoleBehavior.java
new file mode 100644
index 000000000..14c189a0f
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/NotesRoleBehavior.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.behavior;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Build;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.UserUtils;
+
+/**
+ * Class for behavior of the Notes role.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class NotesRoleBehavior implements RoleBehavior {
+
+ @Override
+ public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ // Role should be enabled by OEMs.
+ Resources resources = context.getResources();
+ if (!resources.getBoolean(android.R.bool.config_enableDefaultNotes)) {
+ return false;
+ }
+
+ // Cloned profile shouldn't have a separate role.
+ if (UserUtils.isCloneProfile(user, context)) {
+ return false;
+ }
+
+ if (UserUtils.isManagedProfile(user, context)) {
+ // The role holder for work profile is separately controlled via config.
+ return resources.getBoolean(android.R.bool.config_enableDefaultNotesForWorkProfile);
+ }
+
+ return true;
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/SmsRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java
index 1515945c1..a9062c93b 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/SmsRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import android.os.Process;
@@ -26,10 +26,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.modules.utils.build.SdkLevel;
-import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.permission.utils.CollectionUtils;
-import com.android.permissioncontroller.role.utils.PackageUtils;
-import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.model.Permissions;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+import com.android.role.controller.util.CollectionUtils;
+import com.android.role.controller.util.PackageUtils;
+import com.android.role.controller.util.UserUtils;
import java.util.Arrays;
import java.util.List;
@@ -54,9 +56,16 @@ public class SmsRoleBehavior implements RoleBehavior {
@Override
public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user,
@NonNull Context context) {
- if (UserUtils.isProfile(user, context)) {
- return false;
+ if (SdkLevel.isAtLeastU()) {
+ if (UserUtils.isCloneProfile(user, context)) {
+ return false;
+ }
+ } else {
+ if (UserUtils.isProfile(user, context)) {
+ return false;
+ }
}
+
UserManager userManager = context.getSystemService(UserManager.class);
if (userManager.isRestrictedProfile(user)) {
return false;
@@ -86,20 +95,6 @@ public class SmsRoleBehavior implements RoleBehavior {
return CollectionUtils.firstOrNull(qualifyingPackageNames);
}
- @Nullable
- @Override
- public CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
- @NonNull Context context) {
- return EncryptionUnawareConfirmationMixin.getConfirmationMessage(role, packageName,
- context);
- }
-
- @Override
- public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
- @NonNull Context context) {
- return context.getResources().getBoolean(R.bool.config_showSmsRole);
- }
-
@Override
public void grant(@NonNull Role role, @NonNull String packageName, @NonNull Context context) {
if (SdkLevel.isAtLeastS() && PackageUtils.isSystemPackage(packageName, context)) {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/SystemShellRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemShellRoleBehavior.java
index 9476e2bae..c0cbe9c25 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/SystemShellRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemShellRoleBehavior.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -24,6 +24,9 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+
/**
* Class for behavior of the system shell role.
*/
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/SystemUiRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java
index c08232931..210c2313f 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/SystemUiRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemUiRoleBehavior.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -22,6 +22,9 @@ import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
import com.android.modules.utils.build.SdkLevel;
+import com.android.role.controller.model.AppOpPermissions;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
import java.util.Arrays;
import java.util.List;
diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemWearHealthServiceRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemWearHealthServiceRoleBehavior.java
new file mode 100644
index 000000000..d9f34291d
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/SystemWearHealthServiceRoleBehavior.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.behavior;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+
+/** The role behavior for System Wear Health Service. */
+public class SystemWearHealthServiceRoleBehavior implements RoleBehavior {
+ @Override
+ public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ // Role is only available on Watches.
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/TelevisionRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/TelevisionRoleBehavior.java
index b854e3e35..88021e62d 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/TelevisionRoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/TelevisionRoleBehavior.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.behavior;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -22,6 +22,9 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+
/**
* The base behaviour class for the roles available only on TV.
*/
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/AppOp.java b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java
index 700cf7fe7..3efa68bc9 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/AppOp.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.os.Process;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.role.controller.util.PackageUtils;
import java.util.Objects;
@@ -103,7 +104,8 @@ public class AppOp {
if (mMaxTargetSdkVersion == null) {
return true;
}
- ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, context);
+ ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
+ Process.myUserHandle(), context);
if (applicationInfo == null) {
return false;
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/AppOpPermissions.java b/PermissionController/role-controller/java/com/android/role/controller/model/AppOpPermissions.java
index 25d8dc942..f3f9b321e 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/AppOpPermissions.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/AppOpPermissions.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.app.AppOpsManager;
import android.content.Context;
@@ -24,8 +24,8 @@ import android.os.Build;
import androidx.annotation.NonNull;
-import com.android.permissioncontroller.permission.utils.ArrayUtils;
-import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.role.controller.util.ArrayUtils;
+import com.android.role.controller.util.PackageUtils;
/**
* App op permissions to be granted or revoke by a {@link Role}.
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/IntentFilterData.java b/PermissionController/role-controller/java/com/android/role/controller/model/IntentFilterData.java
index 0bf1ce585..90deebfc6 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/IntentFilterData.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/IntentFilterData.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.content.Intent;
import android.content.IntentFilter;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/Permission.java b/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java
index ae1b6d0e9..0c4a14574 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/Permission.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.os.Build;
@@ -60,9 +60,9 @@ public class Permission {
* @return whether this permission is available
*/
public boolean isAvailable() {
- // Workaround to match the value 33+ for T+ in roles.xml before SDK finalization.
- if (mMinSdkVersion >= 33) {
- return SdkLevel.isAtLeastT();
+ // Workaround to match the value 34+ for U+ in roles.xml before SDK finalization.
+ if (mMinSdkVersion >= 34) {
+ return SdkLevel.isAtLeastU();
} else {
return Build.VERSION.SDK_INT >= mMinSdkVersion;
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/PermissionSet.java b/PermissionController/role-controller/java/com/android/role/controller/model/PermissionSet.java
index 1568c5c74..72f127e1c 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/PermissionSet.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/PermissionSet.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import androidx.annotation.NonNull;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/Permissions.java b/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java
index 28146a814..f55a84c07 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/Permissions.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Permissions.java
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
-import android.Manifest;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -35,11 +34,9 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.permissioncontroller.permission.utils.ArrayUtils;
-import com.android.permissioncontroller.permission.utils.CollectionUtils;
-import com.android.permissioncontroller.permission.utils.PermissionMapping;
-import com.android.permissioncontroller.permission.utils.Utils;
-import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.role.controller.util.ArrayUtils;
+import com.android.role.controller.util.CollectionUtils;
+import com.android.role.controller.util.PackageUtils;
import java.util.ArrayList;
import java.util.List;
@@ -58,6 +55,8 @@ public class Permissions {
private static ArrayMap<String, List<String>> sBackgroundToForegroundPermissions;
private static final Object sForegroundBackgroundPermissionMappingsLock = new Object();
+ private static final ArrayMap<String, Boolean> sRestrictedPermissions = new ArrayMap<>();
+
/**
* Filter a list of permissions based on their SDK versions.
*
@@ -181,16 +180,12 @@ public class Permissions {
Set<String> whitelistedRestrictedPermissions = new ArraySet<>(
packageManager.getWhitelistedRestrictedPermissions(packageName,
PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM));
- List<String> smsPermissions = PermissionMapping.getPlatformPermissionNamesOfGroup(
- Manifest.permission_group.SMS);
- List<String> callLogPermissions = PermissionMapping.getPlatformPermissionNamesOfGroup(
- Manifest.permission_group.CALL_LOG);
int sortedPermissionsToGrantLength = sortedPermissionsToGrant.length;
for (int i = 0; i < sortedPermissionsToGrantLength; i++) {
String permission = sortedPermissionsToGrant[i];
- if ((smsPermissions.contains(permission) || callLogPermissions.contains(permission))
+ if (isRestrictedPermission(permission, context)
&& whitelistedRestrictedPermissions.add(permission)) {
packageManager.addWhitelistedRestrictedPermission(packageName, permission,
PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM);
@@ -433,7 +428,9 @@ public class Permissions {
PackageManager packageManager = context.getPackageManager();
Set<String> whitelistedRestrictedPermissions =
packageManager.getWhitelistedRestrictedPermissions(packageName,
- Utils.FLAGS_PERMISSION_WHITELIST_ALL);
+ PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
+ | PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
+ | PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER);
boolean permissionOrAppOpChanged = false;
@@ -584,7 +581,8 @@ public class Permissions {
static boolean isRuntimePermissionsSupported(@NonNull String packageName,
@NonNull Context context) {
- ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, context);
+ ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
+ Process.myUserHandle(), context);
if (applicationInfo == null) {
return false;
}
@@ -713,6 +711,34 @@ public class Permissions {
}
}
+ private static boolean isRestrictedPermission(@NonNull String permission,
+ @NonNull Context context) {
+ synchronized (sRestrictedPermissions) {
+ if (sRestrictedPermissions.containsKey(permission)) {
+ return sRestrictedPermissions.get(permission);
+ }
+ }
+
+ PackageManager packageManager = context.getPackageManager();
+ PermissionInfo permissionInfo = null;
+ try {
+ permissionInfo = packageManager.getPermissionInfo(permission, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(LOG_TAG, "Cannot get PermissionInfo for permission: " + permission);
+ }
+
+ // Don't expect that to be a transient error, so we can still cache the failed information.
+ boolean isRestrictedPermission = permissionInfo != null
+ && (permissionInfo.flags & (PermissionInfo.FLAG_SOFT_RESTRICTED
+ | PermissionInfo.FLAG_HARD_RESTRICTED)) != 0;
+
+ synchronized (sRestrictedPermissions) {
+ sRestrictedPermissions.put(permission, isRestrictedPermission);
+ }
+
+ return isRestrictedPermission;
+ }
+
private static void createForegroundBackgroundPermissionMappings(@NonNull Context context) {
List<String> permissions = new ArrayList<>();
sBackgroundToForegroundPermissions = new ArrayMap<>();
@@ -728,8 +754,8 @@ public class Permissions {
List<PermissionInfo> permissionInfos;
try {
- permissionInfos = Utils.getPermissionInfosForGroup(packageManager,
- permissionGroupInfo.name);
+ permissionInfos = packageManager.queryPermissionsByGroup(
+ permissionGroupInfo.name, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.e(LOG_TAG, "Cannot get permissions for group: " + permissionGroupInfo.name);
continue;
@@ -792,7 +818,8 @@ public class Permissions {
@Nullable
static Integer getAppOpMode(@NonNull String packageName, @NonNull String appOp,
@NonNull Context context) {
- ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, context);
+ ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
+ Process.myUserHandle(), context);
if (applicationInfo == null) {
return null;
}
@@ -820,7 +847,8 @@ public class Permissions {
if (currentMode != null && currentMode == mode) {
return false;
}
- ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, context);
+ ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
+ Process.myUserHandle(), context);
if (applicationInfo == null) {
Log.e(LOG_TAG, "Cannot get ApplicationInfo for package to set app op mode: "
+ packageName);
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/PreferredActivity.java b/PermissionController/role-controller/java/com/android/role/controller/model/PreferredActivity.java
index f03527b36..5b9c22b67 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/PreferredActivity.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/PreferredActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.content.ComponentName;
import android.content.Context;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredActivity.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java
index def0a6dbf..58c878e56 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredActivity.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredActivity.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.content.ComponentName;
import android.content.Context;
@@ -27,7 +27,7 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.util.UserUtils;
import java.util.List;
@@ -37,8 +37,9 @@ import java.util.List;
public class RequiredActivity extends RequiredComponent {
public RequiredActivity(@NonNull IntentFilterData intentFilterData, int minTargetSdkVersion,
- @Nullable String permission, int queryFlags, @NonNull List<RequiredMetaData> metaData) {
- super(intentFilterData, minTargetSdkVersion, permission, queryFlags, metaData);
+ int flags, @Nullable String permission, int queryFlags,
+ @NonNull List<RequiredMetaData> metaData) {
+ super(intentFilterData, minTargetSdkVersion, flags, permission, queryFlags, metaData);
}
@NonNull
@@ -58,6 +59,11 @@ public class RequiredActivity extends RequiredComponent {
resolveInfo.activityInfo.name);
}
+ @Override
+ protected int getComponentFlags(@NonNull ResolveInfo resolveInfo) {
+ return resolveInfo.activityInfo.flags;
+ }
+
@Nullable
@Override
protected String getComponentPermission(@NonNull ResolveInfo resolveInfo) {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredBroadcastReceiver.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java
index 1f756f618..945fda3c3 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredBroadcastReceiver.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredBroadcastReceiver.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.content.ComponentName;
import android.content.Context;
@@ -27,7 +27,7 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.util.UserUtils;
import java.util.List;
@@ -37,9 +37,9 @@ import java.util.List;
public class RequiredBroadcastReceiver extends RequiredComponent {
public RequiredBroadcastReceiver(@NonNull IntentFilterData intentFilterData,
- int minTargetSdkVersion, @Nullable String permission, int queryFlags,
+ int minTargetSdkVersion, int flags, @Nullable String permission, int queryFlags,
@NonNull List<RequiredMetaData> metaData) {
- super(intentFilterData, minTargetSdkVersion, permission, queryFlags, metaData);
+ super(intentFilterData, minTargetSdkVersion, flags, permission, queryFlags, metaData);
}
@NonNull
@@ -58,6 +58,11 @@ public class RequiredBroadcastReceiver extends RequiredComponent {
resolveInfo.activityInfo.name);
}
+ @Override
+ protected int getComponentFlags(@NonNull ResolveInfo resolveInfo) {
+ return resolveInfo.activityInfo.flags;
+ }
+
@Nullable
@Override
protected String getComponentPermission(@NonNull ResolveInfo resolveInfo) {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredComponent.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java
index 361727d59..ae6156e7f 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredComponent.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredComponent.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.content.ComponentName;
import android.content.Context;
@@ -56,6 +56,11 @@ public abstract class RequiredComponent {
private final int mMinTargetSdkVersion;
/**
+ * Optional flags required to be set on a component for match to succeed.
+ */
+ private final int mFlags;
+
+ /**
* Optional permission required on a component for match to succeed.
*
* @see android.content.pm.ActivityInfo#permission
@@ -78,9 +83,11 @@ public abstract class RequiredComponent {
private final List<RequiredMetaData> mMetaData;
public RequiredComponent(@NonNull IntentFilterData intentFilterData, int minTargetSdkVersion,
- @Nullable String permission, int queryFlags, @NonNull List<RequiredMetaData> metaData) {
+ int flags, @Nullable String permission, int queryFlags,
+ @NonNull List<RequiredMetaData> metaData) {
mIntentFilterData = intentFilterData;
mMinTargetSdkVersion = minTargetSdkVersion;
+ mFlags = flags;
mPermission = permission;
mQueryFlags = queryFlags;
mMetaData = metaData;
@@ -101,9 +108,9 @@ public abstract class RequiredComponent {
* @return whether this required component is available
*/
public boolean isAvailable() {
- // Workaround to match the value 33+ for T+ in roles.xml before SDK finalization.
- if (mMinTargetSdkVersion >= 33) {
- return SdkLevel.isAtLeastT();
+ // Workaround to match the value 34+ for U+ in roles.xml before SDK finalization.
+ if (mMinTargetSdkVersion >= 34) {
+ return SdkLevel.isAtLeastU();
} else {
return Build.VERSION.SDK_INT >= mMinTargetSdkVersion;
}
@@ -119,6 +126,10 @@ public abstract class RequiredComponent {
return isAvailable() && applicationInfo.targetSdkVersion >= mMinTargetSdkVersion;
}
+ public int getFlags() {
+ return mFlags;
+ }
+
@Nullable
public String getPermission() {
return mPermission;
@@ -169,13 +180,14 @@ public abstract class RequiredComponent {
if (packageName != null) {
intent.setPackage(packageName);
}
- int flags = mQueryFlags | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ int queryFlags = mQueryFlags | PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
boolean hasMetaData = !mMetaData.isEmpty();
if (hasMetaData) {
- flags |= PackageManager.GET_META_DATA;
+ queryFlags |= PackageManager.GET_META_DATA;
}
- List<ResolveInfo> resolveInfos = queryIntentComponentsAsUser(intent, flags, user, context);
+ List<ResolveInfo> resolveInfos = queryIntentComponentsAsUser(intent, queryFlags, user,
+ context);
ArraySet<String> componentPackageNames = new ArraySet<>();
List<ComponentName> componentNames = new ArrayList<>();
@@ -183,6 +195,13 @@ public abstract class RequiredComponent {
for (int resolveInfosIndex = 0; resolveInfosIndex < resolveInfosSize; resolveInfosIndex++) {
ResolveInfo resolveInfo = resolveInfos.get(resolveInfosIndex);
+ if (mFlags != 0) {
+ int componentFlags = getComponentFlags(resolveInfo);
+ if ((componentFlags & mFlags) != mFlags) {
+ continue;
+ }
+ }
+
if (mPermission != null) {
String componentPermission = getComponentPermission(resolveInfo);
if (!Objects.equals(componentPermission, mPermission)) {
@@ -248,6 +267,15 @@ public abstract class RequiredComponent {
protected abstract ComponentName getComponentComponentName(@NonNull ResolveInfo resolveInfo);
/**
+ * Get the flags that have been set on a component.
+ *
+ * @param resolveInfo the {@code ResolveInfo} of the component
+ *
+ * @return the flags that have been set on a component
+ */
+ protected abstract int getComponentFlags(@NonNull ResolveInfo resolveInfo);
+
+ /**
* Get the permission required to access a component.
*
* @param resolveInfo the {@code ResolveInfo} of the component
@@ -272,6 +300,7 @@ public abstract class RequiredComponent {
return "RequiredComponent{"
+ "mIntentFilterData=" + mIntentFilterData
+ ", mMinTargetSdkVersion=" + mMinTargetSdkVersion
+ + ", mFlags='" + mFlags + '\''
+ ", mPermission='" + mPermission + '\''
+ ", mQueryFlags=" + mQueryFlags
+ ", mMetaData=" + mMetaData
@@ -289,6 +318,7 @@ public abstract class RequiredComponent {
RequiredComponent that = (RequiredComponent) object;
return Objects.equals(mIntentFilterData, that.mIntentFilterData)
&& Objects.equals(mMinTargetSdkVersion, that.mMinTargetSdkVersion)
+ && mFlags == that.mFlags
&& Objects.equals(mPermission, that.mPermission)
&& mQueryFlags == that.mQueryFlags
&& Objects.equals(mMetaData, that.mMetaData);
@@ -296,7 +326,7 @@ public abstract class RequiredComponent {
@Override
public int hashCode() {
- return Objects.hash(mIntentFilterData, mMinTargetSdkVersion, mPermission, mQueryFlags,
- mMetaData);
+ return Objects.hash(mIntentFilterData, mMinTargetSdkVersion, mFlags, mPermission,
+ mQueryFlags, mMetaData);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredContentProvider.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java
index 76c82faf6..7b53a25bb 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredContentProvider.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredContentProvider.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.content.ComponentName;
import android.content.Context;
@@ -27,7 +27,7 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.util.UserUtils;
import java.util.List;
@@ -37,9 +37,9 @@ import java.util.List;
public class RequiredContentProvider extends RequiredComponent {
public RequiredContentProvider(@NonNull IntentFilterData intentFilterData,
- int minTargetSdkVersion, @Nullable String permission, int queryFlags,
+ int minTargetSdkVersion, int flags, @Nullable String permission, int queryFlags,
@NonNull List<RequiredMetaData> metaData) {
- super(intentFilterData, minTargetSdkVersion, permission, queryFlags, metaData);
+ super(intentFilterData, minTargetSdkVersion, flags, permission, queryFlags, metaData);
}
@NonNull
@@ -58,6 +58,11 @@ public class RequiredContentProvider extends RequiredComponent {
resolveInfo.providerInfo.name);
}
+ @Override
+ protected int getComponentFlags(@NonNull ResolveInfo resolveInfo) {
+ return resolveInfo.providerInfo.flags;
+ }
+
@Nullable
@Override
protected String getComponentPermission(@NonNull ResolveInfo resolveInfo) {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredMetaData.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredMetaData.java
index f7f701fc3..07b1a294d 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredMetaData.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredMetaData.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.os.Bundle;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredService.java b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java
index 72d1439c9..f27aae013 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/RequiredService.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RequiredService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.content.ComponentName;
import android.content.Context;
@@ -27,7 +27,7 @@ import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.util.UserUtils;
import java.util.List;
@@ -37,8 +37,9 @@ import java.util.List;
public class RequiredService extends RequiredComponent {
public RequiredService(@NonNull IntentFilterData intentFilterData, int minTargetSdkVersion,
- @Nullable String permission, int queryFlags, @NonNull List<RequiredMetaData> metaData) {
- super(intentFilterData, minTargetSdkVersion, permission, queryFlags, metaData);
+ int flags, @Nullable String permission, int queryFlags,
+ @NonNull List<RequiredMetaData> metaData) {
+ super(intentFilterData, minTargetSdkVersion, flags, permission, queryFlags, metaData);
}
@NonNull
@@ -56,6 +57,11 @@ public class RequiredService extends RequiredComponent {
return new ComponentName(resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
}
+ @Override
+ protected int getComponentFlags(@NonNull ResolveInfo resolveInfo) {
+ return resolveInfo.serviceInfo.flags;
+ }
+
@Nullable
@Override
protected String getComponentPermission(@NonNull ResolveInfo resolveInfo) {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/Role.java b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
index de6112e5e..5ea88c3ae 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/Role.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.app.ActivityManager;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
@@ -37,16 +36,12 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
-import androidx.preference.Preference;
import com.android.modules.utils.build.SdkLevel;
-import com.android.permissioncontroller.Constants;
-import com.android.permissioncontroller.permission.utils.CollectionUtils;
-import com.android.permissioncontroller.permission.utils.Utils;
-import com.android.permissioncontroller.role.ui.TwoTargetPreference;
-import com.android.permissioncontroller.role.utils.PackageUtils;
-import com.android.permissioncontroller.role.utils.RoleManagerCompat;
-import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.util.CollectionUtils;
+import com.android.role.controller.util.PackageUtils;
+import com.android.role.controller.util.RoleManagerCompat;
+import com.android.role.controller.util.UserUtils;
import java.util.ArrayList;
import java.util.Collections;
@@ -124,6 +119,11 @@ public class Role {
private final int mLabelResource;
/**
+ * The maximum SDK version for this role to be available.
+ */
+ private final int mMaxSdkVersion;
+
+ /**
* The minimum SDK version for this role to be available.
*/
private final int mMinSdkVersion;
@@ -218,16 +218,21 @@ public class Role {
@NonNull
private final List<PreferredActivity> mPreferredActivities;
+ @Nullable
+ private final String mUiBehaviorName;
+
public Role(@NonNull String name, boolean allowBypassingQualification,
@Nullable RoleBehavior behavior, @Nullable String defaultHoldersResourceName,
@StringRes int descriptionResource, boolean exclusive, boolean fallBackToDefaultHolder,
- @StringRes int labelResource, int minSdkVersion, boolean overrideUserWhenGranting,
- @StringRes int requestDescriptionResource, @StringRes int requestTitleResource,
- boolean requestable, @StringRes int searchKeywordsResource,
- @StringRes int shortLabelResource, boolean showNone, boolean statik, boolean systemOnly,
- boolean visible, @NonNull List<RequiredComponent> requiredComponents,
+ @StringRes int labelResource, int maxSdkVersion, int minSdkVersion,
+ boolean overrideUserWhenGranting, @StringRes int requestDescriptionResource,
+ @StringRes int requestTitleResource, boolean requestable,
+ @StringRes int searchKeywordsResource, @StringRes int shortLabelResource,
+ boolean showNone, boolean statik, boolean systemOnly, boolean visible,
+ @NonNull List<RequiredComponent> requiredComponents,
@NonNull List<Permission> permissions, @NonNull List<String> appOpPermissions,
- @NonNull List<AppOp> appOps, @NonNull List<PreferredActivity> preferredActivities) {
+ @NonNull List<AppOp> appOps, @NonNull List<PreferredActivity> preferredActivities,
+ @Nullable String uiBehaviorName) {
mName = name;
mAllowBypassingQualification = allowBypassingQualification;
mBehavior = behavior;
@@ -236,6 +241,7 @@ public class Role {
mExclusive = exclusive;
mFallBackToDefaultHolder = fallBackToDefaultHolder;
mLabelResource = labelResource;
+ mMaxSdkVersion = maxSdkVersion;
mMinSdkVersion = minSdkVersion;
mOverrideUserWhenGranting = overrideUserWhenGranting;
mRequestDescriptionResource = requestDescriptionResource;
@@ -252,6 +258,7 @@ public class Role {
mAppOpPermissions = appOpPermissions;
mAppOps = appOps;
mPreferredActivities = preferredActivities;
+ mUiBehaviorName = uiBehaviorName;
}
@NonNull
@@ -345,6 +352,11 @@ public class Role {
return mPreferredActivities;
}
+ @Nullable
+ public String getUiBehaviorName() {
+ return mUiBehaviorName;
+ }
+
/**
* Callback when this role is added to the system for the first time.
*
@@ -380,11 +392,12 @@ public class Role {
* @return whether this role is available based on SDK version
*/
boolean isAvailableBySdkVersion() {
- // Workaround to match the value 33+ for T+ in roles.xml before SDK finalization.
- if (mMinSdkVersion >= 33) {
- return SdkLevel.isAtLeastT();
+ // Workaround to match the value 34+ for U+ in roles.xml before SDK finalization.
+ if (mMinSdkVersion >= 34) {
+ return SdkLevel.isAtLeastU();
} else {
- return Build.VERSION.SDK_INT >= mMinSdkVersion;
+ return Build.VERSION.SDK_INT >= mMinSdkVersion
+ && Build.VERSION.SDK_INT <= mMaxSdkVersion;
}
}
@@ -487,7 +500,8 @@ public class Role {
return null;
}
} else {
- ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, context);
+ ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
+ Process.myUserHandle(), context);
if (applicationInfo == null) {
Log.w(LOG_TAG, "Cannot get ApplicationInfo for default holder: " + packageName);
return null;
@@ -513,7 +527,7 @@ public class Role {
*/
@Nullable
public String getFallbackHolder(@NonNull Context context) {
- if (isNoneHolderSelected(context)) {
+ if (!RoleManagerCompat.isRoleFallbackEnabledAsUser(this, Process.myUserHandle(), context)) {
return null;
}
if (mFallBackToDefaultHolder) {
@@ -526,110 +540,6 @@ public class Role {
}
/**
- * Check whether this role should be visible to user.
- *
- * @param user the user to check for
- * @param context the {@code Context} to retrieve system services
- *
- * @return whether this role should be visible to user
- */
- public boolean isVisibleAsUser(@NonNull UserHandle user, @NonNull Context context) {
- return mVisible && (mBehavior == null || mBehavior.isVisibleAsUser(this, user, context));
- }
-
- /**
- * Check whether this role should be visible to user, for current user.
- *
- * @param context the {@code Context} to retrieve system services
- *
- * @return whether this role should be visible to user.
- */
- public boolean isVisible(@NonNull Context context) {
- return isVisibleAsUser(Process.myUserHandle(), context);
- }
-
- /**
- * Get the {@link Intent} to manage this role, or {@code null} to use the default UI.
- *
- * @param user the user to manage this role for
- * @param context the {@code Context} to retrieve system services
- *
- * @return the {@link Intent} to manage this role, or {@code null} to use the default UI.
- */
- @Nullable
- public Intent getManageIntentAsUser(@NonNull UserHandle user, @NonNull Context context) {
- if (mBehavior != null) {
- return mBehavior.getManageIntentAsUser(this, user, context);
- }
- return null;
- }
-
- /**
- * Prepare a {@link Preference} for this role.
- *
- * @param preference the {@link Preference} for this role
- * @param user the user for this role
- * @param context the {@code Context} to retrieve system services
- */
- public void preparePreferenceAsUser(@NonNull TwoTargetPreference preference,
- @NonNull UserHandle user, @NonNull Context context) {
- if (mBehavior != null) {
- mBehavior.preparePreferenceAsUser(this, preference, user, context);
- }
- }
-
- /**
- * Check whether a qualifying application should be visible to user.
- *
- * @param applicationInfo the {@link ApplicationInfo} for the application
- * @param user the user for the application
- * @param context the {@code Context} to retrieve system services
- *
- * @return whether the qualifying application should be visible to user
- */
- public boolean isApplicationVisibleAsUser(@NonNull ApplicationInfo applicationInfo,
- @NonNull UserHandle user, @NonNull Context context) {
- if (mBehavior != null) {
- return mBehavior.isApplicationVisibleAsUser(this, applicationInfo, user, context);
- }
- return true;
- }
-
- /**
- * Prepare a {@link Preference} for an application.
- *
- * @param preference the {@link Preference} for the application
- * @param applicationInfo the {@link ApplicationInfo} for the application
- * @param user the user for the application
- * @param context the {@code Context} to retrieve system services
- */
- public void prepareApplicationPreferenceAsUser(@NonNull Preference preference,
- @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user,
- @NonNull Context context) {
- if (mBehavior != null) {
- mBehavior.prepareApplicationPreferenceAsUser(this, preference, applicationInfo, user,
- context);
- }
- }
-
- /**
- * Get the confirmation message for adding an application as a holder of this role.
- *
- * @param packageName the package name of the application to get confirmation message for
- * @param context the {@code Context} to retrieve system services
- *
- * @return the confirmation message, or {@code null} if no confirmation is needed
- */
- @Nullable
- public CharSequence getConfirmationMessage(@NonNull String packageName,
- @NonNull Context context) {
- if (mBehavior != null) {
- return mBehavior.getConfirmationMessage(this, packageName, context);
- }
- return null;
- }
-
- /**
* Check whether this role is allowed to bypass qualification, if enabled globally.
*
* @param context the {@code Context} to retrieve system services
@@ -663,7 +573,8 @@ public class Role {
return true;
}
- ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, context);
+ ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
+ Process.myUserHandle(), context);
if (applicationInfo == null) {
Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName);
return false;
@@ -974,7 +885,8 @@ public class Role {
+ Thread.currentThread().getStackTrace()[3].getMethodName()
+ "(" + mName + ")");
}
- ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, context);
+ ApplicationInfo applicationInfo = PackageUtils.getApplicationInfoAsUser(packageName,
+ Process.myUserHandle(), context);
if (applicationInfo == null) {
Log.w(LOG_TAG, "Cannot get ApplicationInfo for package: " + packageName);
return;
@@ -984,18 +896,6 @@ public class Role {
}
/**
- * Check whether the "none" role holder is selected.
- *
- * @param context the {@code Context} to retrieve system services
- *
- * @return whether the "none" role holder is selected
- */
- private boolean isNoneHolderSelected(@NonNull Context context) {
- return Utils.getDeviceProtectedSharedPreferences(context).getBoolean(
- Constants.IS_NONE_ROLE_HOLDER_SELECTED_KEY + mName, false);
- }
-
- /**
* Callback when a role holder (other than "none") was added.
*
* @param packageName the package name of the role holder
@@ -1004,9 +904,7 @@ public class Role {
*/
public void onHolderAddedAsUser(@NonNull String packageName, @NonNull UserHandle user,
@NonNull Context context) {
- Utils.getDeviceProtectedSharedPreferences(UserUtils.getUserContext(context, user)).edit()
- .remove(Constants.IS_NONE_ROLE_HOLDER_SELECTED_KEY + mName)
- .apply();
+ RoleManagerCompat.setRoleFallbackEnabledAsUser(this, true, user, context);
}
/**
@@ -1044,9 +942,7 @@ public class Role {
* @param context the {@code Context} to retrieve system services
*/
public void onNoneHolderSelectedAsUser(@NonNull UserHandle user, @NonNull Context context) {
- Utils.getDeviceProtectedSharedPreferences(UserUtils.getUserContext(context, user)).edit()
- .putBoolean(Constants.IS_NONE_ROLE_HOLDER_SELECTED_KEY + mName, true)
- .apply();
+ RoleManagerCompat.setRoleFallbackEnabledAsUser(this, false, user, context);
}
@Override
@@ -1060,6 +956,7 @@ public class Role {
+ ", mExclusive=" + mExclusive
+ ", mFallBackToDefaultHolder=" + mFallBackToDefaultHolder
+ ", mLabelResource=" + mLabelResource
+ + ", mMaxSdkVersion=" + mMaxSdkVersion
+ ", mMinSdkVersion=" + mMinSdkVersion
+ ", mOverrideUserWhenGranting=" + mOverrideUserWhenGranting
+ ", mRequestDescriptionResource=" + mRequestDescriptionResource
@@ -1076,6 +973,7 @@ public class Role {
+ ", mAppOpPermissions=" + mAppOpPermissions
+ ", mAppOps=" + mAppOps
+ ", mPreferredActivities=" + mPreferredActivities
+ + ", mUiBehaviorName=" + mUiBehaviorName
+ '}';
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/RoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
index e5402c2ed..f0c4fc018 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/RoleBehavior.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java
@@ -14,18 +14,13 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
import android.os.UserHandle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.preference.Preference;
-
-import com.android.permissioncontroller.role.ui.TwoTargetPreference;
import java.util.Collections;
import java.util.List;
@@ -65,56 +60,6 @@ public interface RoleBehavior {
}
/**
- * @see Role#isVisibleAsUser(UserHandle, Context)
- */
- default boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
- @NonNull Context context) {
- return true;
- }
-
- /**
- * @see Role#getManageIntentAsUser(UserHandle, Context)
- */
- @Nullable
- default Intent getManageIntentAsUser(@NonNull Role role, @NonNull UserHandle user,
- @NonNull Context context) {
- return null;
- }
-
- /**
- * @see Role#preparePreferenceAsUser(TwoTargetPreference, UserHandle, Context)
- */
- default void preparePreferenceAsUser(@NonNull Role role,
- @NonNull TwoTargetPreference preference, @NonNull UserHandle user,
- @NonNull Context context) {}
-
- /**
- * @see Role#isApplicationVisibleAsUser(ApplicationInfo, UserHandle, Context)
- */
- default boolean isApplicationVisibleAsUser(@NonNull Role role,
- @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user,
- @NonNull Context context) {
- return true;
- }
-
- /**
- * @see Role#prepareApplicationPreferenceAsUser(Preference, ApplicationInfo, UserHandle,
- * Context)
- */
- default void prepareApplicationPreferenceAsUser(@NonNull Role role,
- @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
- @NonNull UserHandle user, @NonNull Context context) {}
-
- /**
- * @see Role#getConfirmationMessage(String, Context)
- */
- @Nullable
- default CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
- @NonNull Context context) {
- return null;
- }
-
- /**
* @see Role#shouldAllowBypassingQualification(Context)
*/
@Nullable
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/RoleParser.java b/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java
index 3d5e2e89c..3784f9f01 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/RoleParser.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.app.AppOpsManager;
import android.content.Context;
@@ -31,7 +31,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.permissioncontroller.R;
+import com.android.role.controller.behavior.BrowserRoleBehavior;
import org.xmlpull.v1.XmlPullParserException;
@@ -41,6 +41,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import java.util.function.Function;
/**
* Parser for {@link Role} definitions.
@@ -48,6 +49,11 @@ import java.util.Objects;
@VisibleForTesting
public class RoleParser {
+ /**
+ * Function to retrieve the roles.xml resource from a context
+ */
+ public static volatile Function<Context, XmlResourceParser> sGetRolesXml;
+
private static final String LOG_TAG = RoleParser.class.getSimpleName();
private static final String TAG_ROLES = "roles";
@@ -80,6 +86,7 @@ public class RoleParser {
private static final String ATTRIBUTE_EXCLUSIVE = "exclusive";
private static final String ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER = "fallBackToDefaultHolder";
private static final String ATTRIBUTE_LABEL = "label";
+ private static final String ATTRIBUTE_MAX_SDK_VERSION = "maxSdkVersion";
private static final String ATTRIBUTE_MIN_SDK_VERSION = "minSdkVersion";
private static final String ATTRIBUTE_OVERRIDE_USER_WHEN_GRANTING = "overrideUserWhenGranting";
private static final String ATTRIBUTE_QUERY_FLAGS = "queryFlags";
@@ -91,7 +98,9 @@ public class RoleParser {
private static final String ATTRIBUTE_SHOW_NONE = "showNone";
private static final String ATTRIBUTE_STATIC = "static";
private static final String ATTRIBUTE_SYSTEM_ONLY = "systemOnly";
+ private static final String ATTRIBUTE_UI_BEHAVIOR = "uiBehavior";
private static final String ATTRIBUTE_VISIBLE = "visible";
+ private static final String ATTRIBUTE_FLAGS = "flags";
private static final String ATTRIBUTE_MIN_TARGET_SDK_VERSION = "minTargetSdkVersion";
private static final String ATTRIBUTE_PERMISSION = "permission";
private static final String ATTRIBUTE_PROHIBITED = "prohibited";
@@ -101,6 +110,9 @@ public class RoleParser {
private static final String ATTRIBUTE_MAX_TARGET_SDK_VERSION = "maxTargetSdkVersion";
private static final String ATTRIBUTE_MODE = "mode";
+ private static final String BEHAVIOR_PACKAGE_NAME = BrowserRoleBehavior.class.getPackage()
+ .getName();
+
private static final String MODE_NAME_ALLOWED = "allowed";
private static final String MODE_NAME_IGNORED = "ignored";
private static final String MODE_NAME_ERRORED = "errored";
@@ -137,7 +149,7 @@ public class RoleParser {
*/
@NonNull
public ArrayMap<String, Role> parse() {
- try (XmlResourceParser parser = mContext.getResources().getXml(R.xml.roles)) {
+ try (XmlResourceParser parser = sGetRolesXml.apply(mContext)) {
Pair<ArrayMap<String, PermissionSet>, ArrayMap<String, Role>> xml = parseXml(parser);
if (xml == null) {
return new ArrayMap<>();
@@ -297,8 +309,7 @@ public class RoleParser {
String behaviorClassSimpleName = getAttributeValue(parser, ATTRIBUTE_BEHAVIOR);
RoleBehavior behavior;
if (behaviorClassSimpleName != null) {
- String behaviorClassName = RoleParser.class.getPackage().getName() + '.'
- + behaviorClassSimpleName;
+ String behaviorClassName = BEHAVIOR_PACKAGE_NAME + '.' + behaviorClassSimpleName;
try {
behavior = (RoleBehavior) Class.forName(behaviorClassName).newInstance();
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
@@ -350,8 +361,17 @@ public class RoleParser {
boolean fallBackToDefaultHolder = getAttributeBooleanValue(parser,
ATTRIBUTE_FALL_BACK_TO_DEFAULT_HOLDER, false);
+ int maxSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MAX_SDK_VERSION,
+ Build.VERSION_CODES.CUR_DEVELOPMENT);
int minSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_SDK_VERSION,
Build.VERSION_CODES.BASE);
+ if (minSdkVersion > maxSdkVersion) {
+ throwOrLogMessage("minSdkVersion " + minSdkVersion
+ + " cannot be greater than maxSdkVersion " + maxSdkVersion + " for role: "
+ + name);
+ skipCurrentTag(parser);
+ return null;
+ }
boolean overrideUserWhenGranting = getAttributeBooleanValue(parser,
ATTRIBUTE_OVERRIDE_USER_WHEN_GRANTING, false);
@@ -398,6 +418,8 @@ public class RoleParser {
boolean systemOnly = getAttributeBooleanValue(parser, ATTRIBUTE_SYSTEM_ONLY, false);
+ String uiBehaviorName = getAttributeValue(parser, ATTRIBUTE_UI_BEHAVIOR);
+
List<RequiredComponent> requiredComponents = null;
List<Permission> permissions = null;
List<String> appOpPermissions = null;
@@ -478,10 +500,10 @@ public class RoleParser {
}
return new Role(name, allowBypassingQualification, behavior, defaultHoldersResourceName,
descriptionResource, exclusive, fallBackToDefaultHolder, labelResource,
- minSdkVersion, overrideUserWhenGranting, requestDescriptionResource,
+ maxSdkVersion, minSdkVersion, overrideUserWhenGranting, requestDescriptionResource,
requestTitleResource, requestable, searchKeywordsResource, shortLabelResource,
showNone, statik, systemOnly, visible, requiredComponents, permissions,
- appOpPermissions, appOps, preferredActivities);
+ appOpPermissions, appOps, preferredActivities, uiBehaviorName);
}
@NonNull
@@ -528,6 +550,7 @@ public class RoleParser {
@NonNull String name) throws IOException, XmlPullParserException {
int minTargetSdkVersion = getAttributeIntValue(parser, ATTRIBUTE_MIN_TARGET_SDK_VERSION,
Build.VERSION_CODES.BASE);
+ int flags = getAttributeIntValue(parser, ATTRIBUTE_FLAGS, 0);
String permission = getAttributeValue(parser, ATTRIBUTE_PERMISSION);
int queryFlags = getAttributeIntValue(parser, ATTRIBUTE_QUERY_FLAGS, 0);
IntentFilterData intentFilterData = null;
@@ -592,16 +615,16 @@ public class RoleParser {
}
switch (name) {
case TAG_ACTIVITY:
- return new RequiredActivity(intentFilterData, minTargetSdkVersion, permission,
- queryFlags, metaData);
+ return new RequiredActivity(intentFilterData, minTargetSdkVersion, flags,
+ permission, queryFlags, metaData);
case TAG_PROVIDER:
- return new RequiredContentProvider(intentFilterData, minTargetSdkVersion,
+ return new RequiredContentProvider(intentFilterData, minTargetSdkVersion, flags,
permission, queryFlags, metaData);
case TAG_RECEIVER:
- return new RequiredBroadcastReceiver(intentFilterData, minTargetSdkVersion,
+ return new RequiredBroadcastReceiver(intentFilterData, minTargetSdkVersion, flags,
permission, queryFlags, metaData);
case TAG_SERVICE:
- return new RequiredService(intentFilterData, minTargetSdkVersion, permission,
+ return new RequiredService(intentFilterData, minTargetSdkVersion, flags, permission,
queryFlags, metaData);
default:
throwOrLogMessage("Unknown tag <" + name + ">");
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/Roles.java b/PermissionController/role-controller/java/com/android/role/controller/model/Roles.java
index 6312b170e..5914ffbae 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/Roles.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/model/Roles.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.model;
+package com.android.role.controller.model;
import android.content.Context;
import android.util.ArrayMap;
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/ArrayUtils.java b/PermissionController/role-controller/java/com/android/role/controller/util/ArrayUtils.java
new file mode 100644
index 000000000..5ef531e9e
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/ArrayUtils.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import androidx.annotation.Nullable;
+
+import java.util.Objects;
+
+/**
+ * Array utilities.
+ */
+public final class ArrayUtils {
+ private ArrayUtils() { /* cannot be instantiated */ }
+
+ /**
+ * Checks if an array is null or has no elements.
+ *
+ * @param array the array to check for
+ *
+ * @return whether the array is null or has no elements.
+ */
+ public static <T> boolean isEmpty(@Nullable T[] array) {
+ return array == null || array.length == 0;
+ }
+
+ /**
+ * Checks that value is present as at least one of the elements of the array.
+ * @param array the array to check in
+ * @param value the value to check for
+ * @return true if the value is present in the array
+ */
+ public static <T> boolean contains(T[] array, T value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /**
+ * Return first index of {@code value} in {@code array}, or {@code -1} if
+ * not found.
+ */
+ public static <T> int indexOf(T[] array, T value) {
+ if (array == null) return -1;
+ for (int i = 0; i < array.length; i++) {
+ if (Objects.equals(array[i], value)) return i;
+ }
+ return -1;
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/CollectionUtils.java b/PermissionController/role-controller/java/com/android/role/controller/util/CollectionUtils.java
new file mode 100644
index 000000000..35d8e26ff
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/CollectionUtils.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import android.util.ArraySet;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility methods for dealing with {@link java.util.Collection}s.
+ */
+public final class CollectionUtils {
+
+ private CollectionUtils() {}
+
+ /**
+ * Return the first element of a list, or {@code null} if the list is {@code null} or empty.
+ *
+ * @param <T> the class of the elements of the list
+ * @param list the list to get the first element
+ *
+ * @return the first element of the list, or {@code null} if the list is {@code null} or empty
+ */
+ @Nullable
+ public static <T> T firstOrNull(@Nullable List<T> list) {
+ if (list == null || list.isEmpty()) {
+ return null;
+ }
+ return list.get(0);
+ }
+
+ /**
+ * Remove all values in the array set that do <b>not</b> exist in the given collection.
+ *
+ * @param <T> the class of the elements to retain and of the {@code ArraySet}
+ * @param arraySet the {@code ArraySet} whose elements are to be removed or retained
+ * @param valuesToRetain the values to be used to determine which elements to retain
+ *
+ * @return {@code true} if any values were removed from the array set, {@code false} otherwise.
+ *
+ * @see ArraySet#retainAll(java.util.Collection)
+ */
+ @SafeVarargs
+ public static <T> boolean retainAll(ArraySet<T> arraySet, T... valuesToRetain) {
+ boolean removed = false;
+ List<T> valuesToRetainList = Arrays.asList(valuesToRetain);
+ for (int i = arraySet.size() - 1; i >= 0; i--) {
+ if (!valuesToRetainList.contains(arraySet.valueAt(i))) {
+ arraySet.removeAt(i);
+ removed = true;
+ }
+ }
+ return removed;
+ }
+
+ /**
+ * Return a singleton list containing the element, or an empty list if the element is
+ * {@code null}.
+ *
+ * @param <T> the class of the element
+ * @param element the element to be put into the list
+ *
+ * @return a singleton list containing the element, or an empty list if the element is
+ * {@code null}.
+ */
+ @NonNull
+ public static <T> List<T> singletonOrEmpty(@Nullable T element) {
+ return element != null ? Collections.singletonList(element) : Collections.emptyList();
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/utils/NotificationUtils.java b/PermissionController/role-controller/java/com/android/role/controller/util/NotificationUtils.java
index 08607594c..3b11d7d92 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/utils/NotificationUtils.java
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/NotificationUtils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.role.utils;
+package com.android.role.controller.util;
import android.app.NotificationManager;
import android.content.ComponentName;
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/PackageUtils.java b/PermissionController/role-controller/java/com/android/role/controller/util/PackageUtils.java
new file mode 100644
index 000000000..4b127ad10
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/PackageUtils.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * Utility methods about application packages.
+ */
+public final class PackageUtils {
+
+ private PackageUtils() {}
+
+ /**
+ * Retrieve the {@link PackageInfo} of an application.
+ *
+ * @param packageName the package name of the application
+ * @param extraFlags the extra flags to pass to {@link PackageManager#getPackageInfo(String,
+ * int)}
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return the {@link PackageInfo} of the application, or {@code null} if not found
+ */
+ @Nullable
+ public static PackageInfo getPackageInfo(@NonNull String packageName, int extraFlags,
+ @NonNull Context context) {
+ PackageManager packageManager = context.getPackageManager();
+ try {
+ return packageManager.getPackageInfo(packageName, PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | extraFlags);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Retrieve if a package is a system package.
+ *
+ * @param packageName the name of the package
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return whether the package is a system package
+ */
+ public static boolean isSystemPackage(@NonNull String packageName, @NonNull Context context) {
+ return getPackageInfo(packageName, PackageManager.MATCH_SYSTEM_ONLY, context) != null;
+ }
+
+ /**
+ * Retrieve the {@link ApplicationInfo} of an application.
+ *
+ * @param packageName the package name of the application
+ * @param user the user of the application
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return the {@link ApplicationInfo} of the application, or {@code null} if not found
+ */
+ @Nullable
+ public static ApplicationInfo getApplicationInfoAsUser(@NonNull String packageName,
+ @NonNull UserHandle user, @NonNull Context context) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ PackageManager userPackageManager = userContext.getPackageManager();
+ try {
+ return userPackageManager.getApplicationInfo(packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/RoleManagerCompat.java b/PermissionController/role-controller/java/com/android/role/controller/util/RoleManagerCompat.java
new file mode 100644
index 000000000..8a6fd579c
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/RoleManagerCompat.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.role.controller.model.Role;
+
+/**
+ * Helper for accessing features in {@link RoleManager}.
+ */
+public class RoleManagerCompat {
+
+ /**
+ * Key in the generic shared preferences that stores if the user manually selected the "none"
+ * role holder for a role.
+ */
+ private static final String IS_NONE_ROLE_HOLDER_SELECTED_KEY = "is_none_role_holder_selected:";
+
+ /**
+ * Name of generic shared preferences file.
+ */
+ private static final String PREFERENCES_FILE = "preferences";
+
+ private RoleManagerCompat() {}
+
+ /**
+ * @see RoleManager#isBypassingRoleQualification()
+ */
+ public static boolean isBypassingRoleQualification(@NonNull RoleManager roleManager) {
+ if (SdkLevel.isAtLeastS()) {
+ return roleManager.isBypassingRoleQualification();
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Get a device protected storage based shared preferences. Avoid storing sensitive data in it.
+ *
+ * @param context the context to get the shared preferences
+ * @return a device protected storage based shared preferences
+ */
+ @NonNull
+ private static SharedPreferences getDeviceProtectedSharedPreferences(@NonNull Context context) {
+ if (!context.isDeviceProtectedStorage()) {
+ context = context.createDeviceProtectedStorageContext();
+ }
+ return context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Check whether the role has the fallback holder enabled.
+ *
+ * @return whether the "none" role holder is not selected
+ */
+ public static boolean isRoleFallbackEnabledAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ boolean isNoneHolderSelected = getDeviceProtectedSharedPreferences(userContext)
+ .getBoolean(IS_NONE_ROLE_HOLDER_SELECTED_KEY + role.getName(), false);
+ return !isNoneHolderSelected;
+ }
+
+ /**
+ * Set whether the role has fallback holder enabled.
+ *
+ */
+ public static void setRoleFallbackEnabledAsUser(@NonNull Role role,
+ boolean fallbackEnabled, @NonNull UserHandle user, @NonNull Context context) {
+ Context userContext = UserUtils.getUserContext(context, user);
+ if (fallbackEnabled) {
+ getDeviceProtectedSharedPreferences(userContext).edit()
+ .remove(IS_NONE_ROLE_HOLDER_SELECTED_KEY + role.getName())
+ .apply();
+ } else {
+ getDeviceProtectedSharedPreferences(userContext).edit()
+ .putBoolean(IS_NONE_ROLE_HOLDER_SELECTED_KEY + role.getName(), true)
+ .apply();
+ }
+ }
+}
diff --git a/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java b/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java
new file mode 100644
index 000000000..ac3b681a8
--- /dev/null
+++ b/PermissionController/role-controller/java/com/android/role/controller/util/UserUtils.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.role.controller.util;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.annotation.NonNull;
+
+/** Utility class to deal with Android users. */
+public final class UserUtils {
+
+ private UserUtils() {}
+
+ /**
+ * Check whether a user is a profile.
+ *
+ * @param user the user to check
+ * @param context the {@code Context} to retrieve system services
+ * @return whether the user is a profile
+ */
+ public static boolean isProfile(@NonNull UserHandle user, @NonNull Context context) {
+ return isManagedProfile(user, context) || isCloneProfile(user, context);
+ }
+
+ /**
+ * Check whether a user is a managed profile.
+ *
+ * @param user the user to check
+ * @param context the {@code Context} to retrieve system services
+ * @return whether the user is a managed profile
+ */
+ public static boolean isManagedProfile(@NonNull UserHandle user, @NonNull Context context) {
+ Context userContext = getUserContext(context, user);
+ UserManager userUserManager = userContext.getSystemService(UserManager.class);
+ return userUserManager.isManagedProfile(user.getIdentifier());
+ }
+
+ /**
+ * Check whether a user is a clone profile.
+ *
+ * @param user the user to check
+ * @param context the {@code Context} to retrieve system services
+ * @return whether the user is a clone profile
+ */
+ public static boolean isCloneProfile(@NonNull UserHandle user, @NonNull Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+ return false;
+ }
+ Context userContext = getUserContext(context, user);
+ UserManager userUserManager = userContext.getSystemService(UserManager.class);
+ return userUserManager.isCloneProfile();
+ }
+
+ /**
+ * Create a context for a user.
+ *
+ * @param context The context to clone
+ * @param user The user the new context should be for
+ *
+ * @return The context for the new user
+ */
+ @NonNull
+ public static Context getUserContext(@NonNull Context context, @NonNull UserHandle user) {
+ if (Process.myUserHandle().equals(user)) {
+ return context;
+ } else {
+ return context.createContextAsUser(user, 0);
+ }
+ }
+}
diff --git a/PermissionController/src/android/support/wearable/view/CircledImageView.java b/PermissionController/src/android/support/wearable/view/CircledImageView.java
deleted file mode 100644
index 915ba691e..000000000
--- a/PermissionController/src/android/support/wearable/view/CircledImageView.java
+++ /dev/null
@@ -1,602 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.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 androidx.wear.ble.view;
-
-import android.animation.ArgbEvaluator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Paint.Style;
-import android.graphics.RadialGradient;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Shader;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.permissioncontroller.R;
-
-import java.util.Objects;
-
-/**
- * An image view surrounded by a circle.
- */
-@TargetApi(Build.VERSION_CODES.LOLLIPOP)
-public class CircledImageView extends View {
-
- private static final ArgbEvaluator ARGB_EVALUATOR = new ArgbEvaluator();
-
- private Drawable mDrawable;
-
- private final RectF mOval;
- private final Paint mPaint;
-
- private ColorStateList mCircleColor;
-
- private float mCircleRadius;
- private float mCircleRadiusPercent;
-
- private float mCircleRadiusPressed;
- private float mCircleRadiusPressedPercent;
-
- private float mRadiusInset;
-
- private int mCircleBorderColor;
-
- private float mCircleBorderWidth;
- private float mProgress = 1f;
- private final float mShadowWidth;
-
- private float mShadowVisibility;
- private boolean mCircleHidden = false;
-
- private float mInitialCircleRadius;
-
- private boolean mPressed = false;
-
- private boolean mProgressIndeterminate;
- private ProgressDrawable mIndeterminateDrawable;
- private Rect mIndeterminateBounds = new Rect();
- private long mColorChangeAnimationDurationMs = 0;
-
- private float mImageCirclePercentage = 1f;
- private float mImageHorizontalOffcenterPercentage = 0f;
- private Integer mImageTint;
-
- private final Drawable.Callback mDrawableCallback = new Drawable.Callback() {
- @Override
- public void invalidateDrawable(Drawable drawable) {
- invalidate();
- }
-
- @Override
- public void scheduleDrawable(Drawable drawable, Runnable runnable, long l) {
- // Not needed.
- }
-
- @Override
- public void unscheduleDrawable(Drawable drawable, Runnable runnable) {
- // Not needed.
- }
- };
-
- private int mCurrentColor;
-
- private final AnimatorUpdateListener mAnimationListener = new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int color = (int) animation.getAnimatedValue();
- if (color != CircledImageView.this.mCurrentColor) {
- CircledImageView.this.mCurrentColor = color;
- CircledImageView.this.invalidate();
- }
- }
- };
-
- private ValueAnimator mColorAnimator;
-
- public CircledImageView(Context context) {
- this(context, null);
- }
-
- public CircledImageView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public CircledImageView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
-
- TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CircledImageView);
- mDrawable = a.getDrawable(R.styleable.CircledImageView_android_src);
-
- mCircleColor = a.getColorStateList(R.styleable.CircledImageView_circle_color);
- if (mCircleColor == null) {
- mCircleColor = ColorStateList.valueOf(android.R.color.darker_gray);
- }
-
- mCircleRadius = a.getDimension(
- R.styleable.CircledImageView_circle_radius, 0);
- mInitialCircleRadius = mCircleRadius;
- mCircleRadiusPressed = a.getDimension(
- R.styleable.CircledImageView_circle_radius_pressed, mCircleRadius);
- mCircleBorderColor = a.getColor(
- R.styleable.CircledImageView_circle_border_color, Color.BLACK);
- mCircleBorderWidth = a.getDimension(R.styleable.CircledImageView_circle_border_width, 0);
-
- if (mCircleBorderWidth > 0) {
- mRadiusInset += mCircleBorderWidth;
- }
-
- float circlePadding = a.getDimension(R.styleable.CircledImageView_circle_padding, 0);
- if (circlePadding > 0) {
- mRadiusInset += circlePadding;
- }
- mShadowWidth = a.getDimension(R.styleable.CircledImageView_shadow_width, 0);
-
- mImageCirclePercentage = a.getFloat(
- R.styleable.CircledImageView_image_circle_percentage, 0f);
-
- mImageHorizontalOffcenterPercentage = a.getFloat(
- R.styleable.CircledImageView_image_horizontal_offcenter_percentage, 0f);
-
- if (a.hasValue(R.styleable.CircledImageView_image_tint)) {
- mImageTint = a.getColor(R.styleable.CircledImageView_image_tint, 0);
- }
-
- mCircleRadiusPercent = a.getFraction(R.styleable.CircledImageView_circle_radius_percent,
- 1, 1, 0f);
-
- mCircleRadiusPressedPercent = a.getFraction(
- R.styleable.CircledImageView_circle_radius_pressed_percent, 1, 1,
- mCircleRadiusPercent);
-
- a.recycle();
-
- mOval = new RectF();
- mPaint = new Paint();
- mPaint.setAntiAlias(true);
-
- mIndeterminateDrawable = new ProgressDrawable();
- // {@link #mDrawableCallback} must be retained as a member, as Drawable callback
- // is held by weak reference, we must retain it for it to continue to be called.
- mIndeterminateDrawable.setCallback(mDrawableCallback);
-
- setWillNotDraw(false);
-
- setColorForCurrentState();
- }
-
- public void setCircleHidden(boolean circleHidden) {
- if (circleHidden != mCircleHidden) {
- mCircleHidden = circleHidden;
- invalidate();
- }
- }
-
-
- @Override
- protected boolean onSetAlpha(int alpha) {
- return true;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- int paddingLeft = getPaddingLeft();
- int paddingTop = getPaddingTop();
-
-
- float circleRadius = mPressed ? getCircleRadiusPressed() : getCircleRadius();
- if (mShadowWidth > 0 && mShadowVisibility > 0) {
- // First let's find the center of the view.
- mOval.set(paddingLeft, paddingTop, getWidth() - getPaddingRight(),
- getHeight() - getPaddingBottom());
- // Having the center, lets make the shadow start beyond the circled and possibly the
- // border.
- final float radius = circleRadius + mCircleBorderWidth +
- mShadowWidth * mShadowVisibility;
- mPaint.setColor(Color.BLACK);
- mPaint.setAlpha(Math.round(mPaint.getAlpha() * getAlpha()));
- mPaint.setStyle(Style.FILL);
- // TODO: precalc and pre-allocate this
- mPaint.setShader(new RadialGradient(mOval.centerX(), mOval.centerY(), radius,
- new int[]{Color.BLACK, Color.TRANSPARENT}, new float[]{0.6f, 1f},
- Shader.TileMode.MIRROR));
- canvas.drawCircle(mOval.centerX(), mOval.centerY(), radius, mPaint);
- mPaint.setShader(null);
- }
- if (mCircleBorderWidth > 0) {
- // First let's find the center of the view.
- mOval.set(paddingLeft, paddingTop, getWidth() - getPaddingRight(),
- getHeight() - getPaddingBottom());
- // Having the center, lets make the border meet the circle.
- mOval.set(mOval.centerX() - circleRadius, mOval.centerY() - circleRadius,
- mOval.centerX() + circleRadius, mOval.centerY() + circleRadius);
- mPaint.setColor(mCircleBorderColor);
- // {@link #Paint.setAlpha} is a helper method that just sets the alpha portion of the
- // color. {@link #Paint.setPaint} will clear any previously set alpha value.
- mPaint.setAlpha(Math.round(mPaint.getAlpha() * getAlpha()));
- mPaint.setStyle(Style.STROKE);
- mPaint.setStrokeWidth(mCircleBorderWidth);
-
- if (mProgressIndeterminate) {
- mOval.roundOut(mIndeterminateBounds);
- mIndeterminateDrawable.setBounds(mIndeterminateBounds);
- mIndeterminateDrawable.setRingColor(mCircleBorderColor);
- mIndeterminateDrawable.setRingWidth(mCircleBorderWidth);
- mIndeterminateDrawable.draw(canvas);
- } else {
- canvas.drawArc(mOval, -90, 360 * mProgress, false, mPaint);
- }
- }
- if (!mCircleHidden) {
- mOval.set(paddingLeft, paddingTop, getWidth() - getPaddingRight(),
- getHeight() - getPaddingBottom());
- // {@link #Paint.setAlpha} is a helper method that just sets the alpha portion of the
- // color. {@link #Paint.setPaint} will clear any previously set alpha value.
- mPaint.setColor(mCurrentColor);
- mPaint.setAlpha(Math.round(mPaint.getAlpha() * getAlpha()));
-
- mPaint.setStyle(Style.FILL);
- float centerX = mOval.centerX();
- float centerY = mOval.centerY();
-
- canvas.drawCircle(centerX, centerY, circleRadius, mPaint);
- }
-
- if (mDrawable != null) {
- mDrawable.setAlpha(Math.round(getAlpha() * 255));
-
- if (mImageTint != null) {
- mDrawable.setTint(mImageTint);
- }
- mDrawable.draw(canvas);
- }
-
- super.onDraw(canvas);
- }
-
- private void setColorForCurrentState() {
- int newColor = mCircleColor.getColorForState(getDrawableState(),
- mCircleColor.getDefaultColor());
- if (mColorChangeAnimationDurationMs > 0) {
- if (mColorAnimator != null) {
- mColorAnimator.cancel();
- } else {
- mColorAnimator = new ValueAnimator();
- }
- mColorAnimator.setIntValues(new int[] {
- mCurrentColor, newColor });
- mColorAnimator.setEvaluator(ARGB_EVALUATOR);
- mColorAnimator.setDuration(mColorChangeAnimationDurationMs);
- mColorAnimator.addUpdateListener(this.mAnimationListener);
- mColorAnimator.start();
- } else {
- if (newColor != mCurrentColor) {
- mCurrentColor = newColor;
- invalidate();
- }
- }
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-
- final float radius = getCircleRadius() + mCircleBorderWidth +
- mShadowWidth * mShadowVisibility;
- float desiredWidth = radius * 2;
- float desiredHeight = radius * 2;
-
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
-
- int width;
- int height;
-
- if (widthMode == MeasureSpec.EXACTLY) {
- width = widthSize;
- } else if (widthMode == MeasureSpec.AT_MOST) {
- width = (int) Math.min(desiredWidth, widthSize);
- } else {
- width = (int) desiredWidth;
- }
-
- if (heightMode == MeasureSpec.EXACTLY) {
- height = heightSize;
- } else if (heightMode == MeasureSpec.AT_MOST) {
- height = (int) Math.min(desiredHeight, heightSize);
- } else {
- height = (int) desiredHeight;
- }
-
- super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (mDrawable != null) {
- // Retrieve the sizes of the drawable and the view.
- final int nativeDrawableWidth = mDrawable.getIntrinsicWidth();
- final int nativeDrawableHeight = mDrawable.getIntrinsicHeight();
- final int viewWidth = getMeasuredWidth();
- final int viewHeight = getMeasuredHeight();
- final float imageCirclePercentage = mImageCirclePercentage > 0
- ? mImageCirclePercentage : 1;
-
- final float scaleFactor = Math.min(1f,
- Math.min(
- (float) nativeDrawableWidth != 0
- ? imageCirclePercentage * viewWidth / nativeDrawableWidth : 1,
- (float) nativeDrawableHeight != 0
- ? imageCirclePercentage
- * viewHeight / nativeDrawableHeight : 1));
-
- // Scale the drawable down to fit the view, if needed.
- final int drawableWidth = Math.round(scaleFactor * nativeDrawableWidth);
- final int drawableHeight = Math.round(scaleFactor * nativeDrawableHeight);
-
- // Center the drawable within the view.
- final int drawableLeft = (viewWidth - drawableWidth) / 2
- + Math.round(mImageHorizontalOffcenterPercentage * drawableWidth);
- final int drawableTop = (viewHeight - drawableHeight) / 2;
-
- mDrawable.setBounds(drawableLeft, drawableTop, drawableLeft + drawableWidth,
- drawableTop + drawableHeight);
- }
-
- super.onLayout(changed, left, top, right, bottom);
- }
-
- public void setImageDrawable(Drawable drawable) {
- if (drawable != mDrawable) {
- final Drawable existingDrawable = mDrawable;
- mDrawable = drawable;
-
- final boolean skipLayout = drawable != null
- && existingDrawable != null
- && existingDrawable.getIntrinsicHeight() == drawable.getIntrinsicHeight()
- && existingDrawable.getIntrinsicWidth() == drawable.getIntrinsicWidth();
-
- if (skipLayout) {
- mDrawable.setBounds(existingDrawable.getBounds());
- } else {
- requestLayout();
- }
-
- invalidate();
- }
- }
-
- public void setImageResource(int resId) {
- setImageDrawable(resId == 0 ? null : getContext().getDrawable(resId));
- }
-
- public void setImageCirclePercentage(float percentage) {
- float clamped = Math.max(0, Math.min(1, percentage));
- if (clamped != mImageCirclePercentage) {
- mImageCirclePercentage = clamped;
- invalidate();
- }
- }
-
- public void setImageHorizontalOffcenterPercentage(float percentage) {
- if (percentage != mImageHorizontalOffcenterPercentage) {
- mImageHorizontalOffcenterPercentage = percentage;
- invalidate();
- }
- }
-
- public void setImageTint(int tint) {
- if (tint != mImageTint) {
- mImageTint = tint;
- invalidate();
- }
- }
-
- public float getCircleRadius() {
- float radius = mCircleRadius;
- if (mCircleRadius <= 0 && mCircleRadiusPercent > 0) {
- radius = Math.max(getMeasuredHeight(), getMeasuredWidth()) * mCircleRadiusPercent;
- }
-
- return radius - mRadiusInset;
- }
-
- public float getCircleRadiusPercent() {
- return mCircleRadiusPercent;
- }
-
- public float getCircleRadiusPressed() {
- float radius = mCircleRadiusPressed;
-
- if (mCircleRadiusPressed <= 0 && mCircleRadiusPressedPercent > 0) {
- radius = Math.max(getMeasuredHeight(), getMeasuredWidth())
- * mCircleRadiusPressedPercent;
- }
-
- return radius - mRadiusInset;
- }
-
- public float getCircleRadiusPressedPercent() {
- return mCircleRadiusPressedPercent;
- }
-
- public void setCircleRadius(float circleRadius) {
- if (circleRadius != mCircleRadius) {
- mCircleRadius = circleRadius;
- invalidate();
- }
- }
-
- /**
- * Sets the radius of the circle to be a percentage of the largest dimension of the view.
- * @param circleRadiusPercent A {@code float} from 0 to 1 representing the radius percentage.
- */
- public void setCircleRadiusPercent(float circleRadiusPercent) {
- if (circleRadiusPercent != mCircleRadiusPercent) {
- mCircleRadiusPercent = circleRadiusPercent;
- invalidate();
- }
- }
-
- public void setCircleRadiusPressed(float circleRadiusPressed) {
- if (circleRadiusPressed != mCircleRadiusPressed) {
- mCircleRadiusPressed = circleRadiusPressed;
- invalidate();
- }
- }
-
- /**
- * Sets the radius of the circle to be a percentage of the largest dimension of the view when
- * pressed.
- * @param circleRadiusPressedPercent A {@code float} from 0 to 1 representing the radius
- * percentage.
- */
- public void setCircleRadiusPressedPercent(float circleRadiusPressedPercent) {
- if (circleRadiusPressedPercent != mCircleRadiusPressedPercent) {
- mCircleRadiusPressedPercent = circleRadiusPressedPercent;
- invalidate();
- }
- }
-
- @Override
- protected void drawableStateChanged() {
- super.drawableStateChanged();
- setColorForCurrentState();
- }
-
- public void setCircleColor(int circleColor) {
- setCircleColorStateList(ColorStateList.valueOf(circleColor));
- }
-
- public void setCircleColorStateList(ColorStateList circleColor) {
- if (!Objects.equals(circleColor, mCircleColor)) {
- mCircleColor = circleColor;
- setColorForCurrentState();
- invalidate();
- }
- }
-
- public ColorStateList getCircleColorStateList() {
- return mCircleColor;
- }
-
- public int getDefaultCircleColor() {
- return mCircleColor.getDefaultColor();
- }
-
- /**
- * Show the circle border as an indeterminate progress spinner.
- * The views circle border width and color must be set for this to have an effect.
- *
- * @param show true if the progress spinner is shown, false to hide it.
- */
- public void showIndeterminateProgress(boolean show) {
- mProgressIndeterminate = show;
- if (show) {
- mIndeterminateDrawable.startAnimation();
- } else {
- mIndeterminateDrawable.stopAnimation();
- }
- }
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if (visibility != View.VISIBLE) {
- showIndeterminateProgress(false);
- } else if (mProgressIndeterminate) {
- showIndeterminateProgress(true);
- }
- }
-
- public void setProgress(float progress) {
- if (progress != mProgress) {
- mProgress = progress;
- invalidate();
- }
- }
-
- /**
- * Set how much of the shadow should be shown.
- * @param shadowVisibility Value between 0 and 1.
- */
- public void setShadowVisibility(float shadowVisibility) {
- if (shadowVisibility != mShadowVisibility) {
- mShadowVisibility = shadowVisibility;
- invalidate();
- }
- }
-
- public float getInitialCircleRadius() {
- return mInitialCircleRadius;
- }
-
- public void setCircleBorderColor(int circleBorderColor) {
- mCircleBorderColor = circleBorderColor;
- }
-
- /**
- * Set the border around the circle.
- * @param circleBorderWidth Width of the border around the circle.
- */
- public void setCircleBorderWidth(float circleBorderWidth) {
- if (circleBorderWidth != mCircleBorderWidth) {
- mCircleBorderWidth = circleBorderWidth;
- invalidate();
- }
- }
-
- @Override
- public void setPressed(boolean pressed) {
- super.setPressed(pressed);
- if (pressed != mPressed) {
- mPressed = pressed;
- invalidate();
- }
- }
-
- public Drawable getImageDrawable() {
- return mDrawable;
- }
-
- /**
- * @return the milliseconds duration of the transition animation when the color changes.
- */
- public long getColorChangeAnimationDuration() {
- return mColorChangeAnimationDurationMs;
- }
-
- /**
- * @param mColorChangeAnimationDurationMs the milliseconds duration of the color change
- * animation. The color change animation will run if the color changes with {@link #setCircleColor}
- * or as a result of the active state changing.
- */
- public void setColorChangeAnimationDuration(long mColorChangeAnimationDurationMs) {
- this.mColorChangeAnimationDurationMs = mColorChangeAnimationDurationMs;
- }
-}
diff --git a/PermissionController/src/android/support/wearable/view/Gusterpolator.java b/PermissionController/src/android/support/wearable/view/Gusterpolator.java
deleted file mode 100644
index 6b17100fe..000000000
--- a/PermissionController/src/android/support/wearable/view/Gusterpolator.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.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 androidx.wear.ble.view;
-
-import android.animation.TimeInterpolator;
-import android.annotation.TargetApi;
-import android.os.Build;
-
-/**
- * Interpolator that uses a Bezier derived S shaped curve.
- * @hide
- */
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
-class Gusterpolator implements TimeInterpolator {
-
- /** An instance of {@link androidx.wear.ble.view.Gusterpolator}. */
- public static final Gusterpolator INSTANCE = new Gusterpolator();
-
- /**
- * To avoid users of this class creating multiple copies needlessly, the constructor is
- * private.
- */
- private Gusterpolator() {}
-
- /**
- * Lookup table values.
- * Generated using a Bezier curve from (0,0) to (1,1) with control points:
- * P0 (0,0)
- * P1 (0.4, 0)
- * P2 (0.2, 1.0)
- * P3 (1.0, 1.0)
- *
- * Values sampled with x at regular intervals between 0 and 1.
- */
- private static final float[] VALUES = new float[] {
- 0.0f, 0.0002f, 0.0009f, 0.0019f, 0.0036f, 0.0059f, 0.0086f, 0.0119f, 0.0157f, 0.0209f,
- 0.0257f, 0.0321f, 0.0392f, 0.0469f, 0.0566f, 0.0656f, 0.0768f, 0.0887f, 0.1033f, 0.1186f,
- 0.1349f, 0.1519f, 0.1696f, 0.1928f, 0.2121f, 0.237f, 0.2627f, 0.2892f, 0.3109f, 0.3386f,
- 0.3667f, 0.3952f, 0.4241f, 0.4474f, 0.4766f, 0.5f, 0.5234f, 0.5468f, 0.5701f, 0.5933f,
- 0.6134f, 0.6333f, 0.6531f, 0.6698f, 0.6891f, 0.7054f, 0.7214f, 0.7346f, 0.7502f, 0.763f,
- 0.7756f, 0.7879f, 0.8f, 0.8107f, 0.8212f, 0.8326f, 0.8415f, 0.8503f, 0.8588f, 0.8672f,
- 0.8754f, 0.8833f, 0.8911f, 0.8977f, 0.9041f, 0.9113f, 0.9165f, 0.9232f, 0.9281f, 0.9328f,
- 0.9382f, 0.9434f, 0.9476f, 0.9518f, 0.9557f, 0.9596f, 0.9632f, 0.9662f, 0.9695f, 0.9722f,
- 0.9753f, 0.9777f, 0.9805f, 0.9826f, 0.9847f, 0.9866f, 0.9884f, 0.9901f, 0.9917f, 0.9931f,
- 0.9944f, 0.9955f, 0.9964f, 0.9973f, 0.9981f, 0.9986f, 0.9992f, 0.9995f, 0.9998f, 1.0f, 1.0f
- };
-
- private static final float STEP_SIZE = 1.0f / (VALUES.length - 1);
-
- @Override
- public float getInterpolation(float input) {
- if (input >= 1.0f) {
- return 1.0f;
- }
-
- if (input <= 0f) {
- return 0f;
- }
-
- int position = Math.min(
- (int)(input * (VALUES.length - 1)),
- VALUES.length - 2);
-
- float quantized = position * STEP_SIZE;
- float difference = input - quantized;
- float weight = difference / STEP_SIZE;
-
- return VALUES[position] + weight * (VALUES[position + 1] - VALUES[position]);
- }
-}
diff --git a/PermissionController/src/android/support/wearable/view/ProgressDrawable.java b/PermissionController/src/android/support/wearable/view/ProgressDrawable.java
deleted file mode 100644
index 51ec8f07f..000000000
--- a/PermissionController/src/android/support/wearable/view/ProgressDrawable.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.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 androidx.wear.ble.view;
-
-import android.animation.ObjectAnimator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.Paint;
-import android.graphics.PixelFormat;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.Property;
-import android.view.animation.LinearInterpolator;
-
-/**
- * Drawable for showing an indeterminate progress indicator.
- *
- * TODO: When Material progress drawable is available in the support library stop using this.
- *
- * @hide
- */
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
-class ProgressDrawable extends Drawable {
-
- private static Property<ProgressDrawable, Integer> LEVEL =
- new Property<ProgressDrawable, Integer>(Integer.class, "level") {
- @Override
- public Integer get(ProgressDrawable drawable) {
- return drawable.getLevel();
- }
-
- @Override
- public void set(ProgressDrawable drawable, Integer value) {
- drawable.setLevel(value);
- drawable.invalidateSelf();
- }
- };
- /** Max level for a level drawable, as specified in developer docs for {@link Drawable}. */
- private static final int MAX_LEVEL = 10000;
-
- /** How many different sections are there, five gives us the material style star. **/
- private static final int NUMBER_OF_SEGMENTS = 5;
-
- private static final int LEVELS_PER_SEGMENT = MAX_LEVEL / NUMBER_OF_SEGMENTS;
- private static final float STARTING_ANGLE = -90f;
- private static final long ANIMATION_DURATION = 6000;
- private static final int FULL_CIRCLE = 360;
- private static final int MAX_SWEEP = 306;
- private static final int CORRECTION_ANGLE = FULL_CIRCLE - MAX_SWEEP;
- /** How far through each cycle does the bar stop growing and start shrinking, half way. **/
- private static final float GROW_SHRINK_RATIO = 0.5f;
- // TODO: replace this with BakedBezierInterpolator when its available in support library.
- private static final TimeInterpolator mInterpolator = Gusterpolator.INSTANCE;
-
- private final RectF mInnerCircleBounds = new RectF();
- private final Paint mPaint = new Paint();
- private final ObjectAnimator mAnimator;
- private float mCircleBorderWidth;
- private int mCircleBorderColor;
-
- public ProgressDrawable() {
- mPaint.setAntiAlias(true);
- mPaint.setStyle(Paint.Style.STROKE);
- mAnimator = ObjectAnimator.ofInt(this, LEVEL, 0, MAX_LEVEL);
- mAnimator.setRepeatCount(ValueAnimator.INFINITE);
- mAnimator.setRepeatMode(ValueAnimator.RESTART);
- mAnimator.setDuration(ANIMATION_DURATION);
- mAnimator.setInterpolator(new LinearInterpolator());
- }
-
- public void setRingColor(int color) {
- mCircleBorderColor = color;
- }
-
- public void setRingWidth(float width) {
- mCircleBorderWidth = width;
- }
-
- public void startAnimation() {
- mAnimator.start();
- }
-
- public void stopAnimation() {
- mAnimator.cancel();
- }
-
- @Override
- public void draw(Canvas canvas) {
- canvas.save();
- mInnerCircleBounds.set(getBounds());
- mInnerCircleBounds.inset(mCircleBorderWidth / 2.0f, mCircleBorderWidth / 2.0f);
- mPaint.setStrokeWidth(mCircleBorderWidth);
- mPaint.setColor(mCircleBorderColor);
-
- float sweepAngle = FULL_CIRCLE;
- boolean growing = false;
- float correctionAngle = 0;
- int level = getLevel();
-
- int currentSegment = level / LEVELS_PER_SEGMENT;
- int offset = currentSegment * LEVELS_PER_SEGMENT;
- float progress = (level - offset) / (float) LEVELS_PER_SEGMENT;
-
- growing = progress < GROW_SHRINK_RATIO;
- correctionAngle = CORRECTION_ANGLE * progress;
-
- if (growing) {
- sweepAngle = MAX_SWEEP * mInterpolator.getInterpolation(
- lerpInv(0f, GROW_SHRINK_RATIO, progress));
- } else {
- sweepAngle = MAX_SWEEP * (1.0f - mInterpolator.getInterpolation(
- lerpInv(GROW_SHRINK_RATIO, 1.0f, progress)));
- }
-
- sweepAngle = Math.max(1, sweepAngle);
-
- canvas.rotate(
- level * (1.0f / MAX_LEVEL) * 2 * FULL_CIRCLE + STARTING_ANGLE + correctionAngle,
- mInnerCircleBounds.centerX(),
- mInnerCircleBounds.centerY());
- canvas.drawArc(mInnerCircleBounds,
- growing ? 0 : MAX_SWEEP - sweepAngle,
- sweepAngle,
- false,
- mPaint);
- canvas.restore();
- }
-
- @Override
- public void setAlpha(int i) {
- // Not supported.
- }
-
- @Override
- public void setColorFilter(ColorFilter colorFilter) {
- // Not supported.
- }
-
- @Override
- public int getOpacity() {
- return PixelFormat.OPAQUE;
- }
-
- @Override
- protected boolean onLevelChange(int level) {
- return true; // Changing the level of this drawable does change its appearance.
- }
-
- /**
- * Returns the interpolation scalar (s) that satisfies the equation:
- * {@code value = }lerp(a, b, s)
- *
- * <p>If {@code a == b}, then this function will return 0.
- */
- private static float lerpInv(float a, float b, float value) {
- return a != b ? ((value - a) / (b - a)) : 0.0f;
- }
-}
diff --git a/PermissionController/src/android/support/wearable/view/SimpleAnimatorListener.java b/PermissionController/src/android/support/wearable/view/SimpleAnimatorListener.java
deleted file mode 100644
index c8fe58c43..000000000
--- a/PermissionController/src/android/support/wearable/view/SimpleAnimatorListener.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.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 androidx.wear.ble.view;
-
-import android.animation.Animator;
-import android.annotation.TargetApi;
-import android.os.Build;
-
-/**
- * Convenience class for listening for Animator events that implements the AnimatorListener
- * interface and allows extending only methods that are necessary.
- */
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
-public class SimpleAnimatorListener implements Animator.AnimatorListener {
-
- private boolean mWasCanceled;
-
- @Override
- public void onAnimationCancel(Animator animator) {
- mWasCanceled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animator) {
- if (!mWasCanceled) {
- onAnimationComplete(animator);
- }
- }
-
- @Override
- public void onAnimationRepeat(Animator animator) {
- }
-
- @Override
- public void onAnimationStart(Animator animator) {
- mWasCanceled = false;
- }
-
- /**
- * Called when the animation finishes. Not called if the animation was canceled.
- */
- public void onAnimationComplete(Animator animator) {
- }
-
- /**
- * Provides information if the animation was cancelled.
- * @return True if animation was cancelled.
- */
- public boolean wasCanceled() {
- return mWasCanceled;
- }
-
-}
diff --git a/PermissionController/src/android/support/wearable/view/WearableListView.java b/PermissionController/src/android/support/wearable/view/WearableListView.java
deleted file mode 100644
index 487ebfb13..000000000
--- a/PermissionController/src/android/support/wearable/view/WearableListView.java
+++ /dev/null
@@ -1,1388 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.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 androidx.wear.ble.view;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.PointF;
-import android.os.Build;
-import android.os.Handler;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.Property;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.widget.Scroller;
-
-import androidx.recyclerview.widget.LinearSmoothScroller;
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * An alternative version of ListView that is optimized for ease of use on small screen wearable
- * devices. It displays a vertically scrollable list of items, and automatically snaps to the
- * nearest item when the user stops scrolling.
- *
- * <p>
- * For a quick start, you will need to implement a subclass of {@link .Adapter},
- * which will create and bind your views to the {@link .ViewHolder} objects. If you want to add
- * more visual treatment to your views when they become the central items of the
- * WearableListView, have them implement the {@link .OnCenterProximityListener} interface.
- * </p>
- */
-@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
-public class WearableListView extends RecyclerView {
- @SuppressWarnings("unused")
- private static final String TAG = "WearableListView";
-
- private static final long FLIP_ANIMATION_DURATION_MS = 150;
- private static final long CENTERING_ANIMATION_DURATION_MS = 150;
-
- private static final float TOP_TAP_REGION_PERCENTAGE = .33f;
- private static final float BOTTOM_TAP_REGION_PERCENTAGE = .33f;
-
- // Each item will occupy one third of the height.
- private static final int THIRD = 3;
-
- private final int mMinFlingVelocity;
- private final int mMaxFlingVelocity;
-
- private boolean mMaximizeSingleItem;
- private boolean mCanClick = true;
- // WristGesture navigation signals are delivered as KeyEvents. Allow developer to disable them
- // for this specific View. It might be cleaner to simply have users re-implement onKeyDown().
- // TOOD: Finalize the disabling mechanism here.
- private boolean mGestureNavigationEnabled = true;
- private int mTapPositionX;
- private int mTapPositionY;
- private ClickListener mClickListener;
-
- private Animator mScrollAnimator;
- // This is a little hacky due to the fact that animator provides incremental values instead of
- // deltas and scrolling code requires deltas. We animate WearableListView directly and use this
- // field to calculate deltas. Obviously this means that only one scrolling algorithm can run at
- // a time, but I don't think it would be wise to have more than one running.
- private int mLastScrollChange;
-
- private SetScrollVerticallyProperty mSetScrollVerticallyProperty =
- new SetScrollVerticallyProperty();
-
- private final List<OnScrollListener> mOnScrollListeners = new ArrayList<OnScrollListener>();
-
- private final List<OnCentralPositionChangedListener> mOnCentralPositionChangedListeners =
- new ArrayList<OnCentralPositionChangedListener>();
-
- private OnOverScrollListener mOverScrollListener;
-
- private boolean mGreedyTouchMode;
-
- private float mStartX;
-
- private float mStartY;
-
- private float mStartFirstTop;
-
- private final int mTouchSlop;
-
- private boolean mPossibleVerticalSwipe;
-
- private int mInitialOffset = 0;
-
- private Scroller mScroller;
-
- // Top and bottom boundaries for tap checking. Need to recompute by calling computeTapRegions
- // before referencing.
- private final float[] mTapRegions = new float[2];
-
- private boolean mGestureDirectionLocked;
- private int mPreviousCentral = 0;
-
- // Temp variable for storing locations on screen.
- private final int[] mLocation = new int[2];
-
- // TODO: Consider clearing this when underlying data set changes. If the data set changes, you
- // can't safely assume that this pressed view is in the same place as it was before and it will
- // receive setPressed(false) unnecessarily. In theory it should be fine, but in practice we
- // have places like this: mIconView.setCircleColor(pressed ? mPressedColor : mSelectedColor);
- // This might set selected color on non selected item. Our logic should be: if you change
- // underlying data set, all best are off and you need to preserve the state; we will clear
- // this field. However, I am not willing to introduce this so late in C development.
- private View mPressedView = null;
-
- private final Runnable mPressedRunnable = new Runnable() {
- @Override
- public void run() {
- if (getChildCount() > 0) {
- mPressedView = getChildAt(findCenterViewIndex());
- mPressedView.setPressed(true);
- } else {
- Log.w(TAG, "mPressedRunnable: the children were removed, skipping.");
- }
- }
- };
-
- private final Runnable mReleasedRunnable = new Runnable() {
- @Override
- public void run() {
- releasePressedItem();
- }
- };
-
- private Runnable mNotifyChildrenPostLayoutRunnable = new Runnable() {
- @Override
- public void run() {
- notifyChildrenAboutProximity(false);
- }
- };
-
- private final AdapterDataObserver mObserver = new AdapterDataObserver() {
- @Override
- public void onChanged() {
- WearableListView.this.addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- WearableListView.this.removeOnLayoutChangeListener(this);
- if (WearableListView.this.getChildCount() > 0) {
- WearableListView.this.animateToCenter();
- }
- }
- });
- }
- };
-
- public WearableListView(Context context) {
- this(context, null);
- }
-
- public WearableListView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public WearableListView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- setHasFixedSize(true);
- setOverScrollMode(View.OVER_SCROLL_NEVER);
- setLayoutManager(new LayoutManager());
-
- final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- if (newState == RecyclerView.SCROLL_STATE_IDLE && getChildCount() > 0) {
- handleTouchUp(null, newState);
- }
- for (OnScrollListener listener : mOnScrollListeners) {
- listener.onScrollStateChanged(newState);
- }
- }
-
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- onScroll(dy);
- }
- };
- setOnScrollListener(onScrollListener);
-
- final ViewConfiguration vc = ViewConfiguration.get(context);
- mTouchSlop = vc.getScaledTouchSlop();
-
- mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
- mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
- }
-
- @Override
- public void setAdapter(RecyclerView.Adapter adapter) {
- RecyclerView.Adapter currentAdapter = getAdapter();
- if (currentAdapter != null) {
- currentAdapter.unregisterAdapterDataObserver(mObserver);
- }
-
- super.setAdapter(adapter);
-
- if (adapter != null) {
- adapter.registerAdapterDataObserver(mObserver);
- }
- }
-
- /**
- * @return the position of the center child's baseline; -1 if no center child exists or if
- * the center child does not return a valid baseline.
- */
- @Override
- public int getBaseline() {
- // No children implies there is no center child for which a baseline can be computed.
- if (getChildCount() == 0) {
- return super.getBaseline();
- }
-
- // Compute the baseline of the center child.
- final int centerChildIndex = findCenterViewIndex();
- final int centerChildBaseline = getChildAt(centerChildIndex).getBaseline();
-
- // If the center child has no baseline, neither does this list view.
- if (centerChildBaseline == -1) {
- return super.getBaseline();
- }
-
- return getCentralViewTop() + centerChildBaseline;
- }
-
- /**
- * @return true if the list is scrolled all the way to the top.
- */
- public boolean isAtTop() {
- if (getChildCount() == 0) {
- return true;
- }
-
- int centerChildIndex = findCenterViewIndex();
- View centerView = getChildAt(centerChildIndex);
- return getChildAdapterPosition(centerView) == 0 &&
- getScrollState() == RecyclerView.SCROLL_STATE_IDLE;
- }
-
- /**
- * Clears the state of the layout manager that positions list items.
- */
- public void resetLayoutManager() {
- setLayoutManager(new LayoutManager());
- }
-
- /**
- * Controls whether WearableListView should intercept all touch events and also prevent the
- * parent from receiving them.
- * @param greedy If true it will intercept all touch events.
- */
- public void setGreedyTouchMode(boolean greedy) {
- mGreedyTouchMode = greedy;
- }
-
- /**
- * By default the first element of the list is initially positioned in the center of the screen.
- * This method allows the developer to specify a different offset, e.g. to hide the
- * WearableListView before the user is allowed to use it.
- *
- * @param top How far the elements should be pushed down.
- */
- public void setInitialOffset(int top) {
- mInitialOffset = top;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (!isEnabled()) {
- return false;
- }
-
- if (mGreedyTouchMode && getChildCount() > 0) {
- int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_DOWN) {
- mStartX = event.getX();
- mStartY = event.getY();
- mStartFirstTop = getChildCount() > 0 ? getChildAt(0).getTop() : 0;
- mPossibleVerticalSwipe = true;
- mGestureDirectionLocked = false;
- } else if (action == MotionEvent.ACTION_MOVE && mPossibleVerticalSwipe) {
- handlePossibleVerticalSwipe(event);
- }
- getParent().requestDisallowInterceptTouchEvent(mPossibleVerticalSwipe);
- }
- return super.onInterceptTouchEvent(event);
- }
-
- private boolean handlePossibleVerticalSwipe(MotionEvent event) {
- if (mGestureDirectionLocked) {
- return mPossibleVerticalSwipe;
- }
- float deltaX = Math.abs(mStartX - event.getX());
- float deltaY = Math.abs(mStartY - event.getY());
- float distance = (deltaX * deltaX) + (deltaY * deltaY);
- // Verify that the distance moved in the combined x/y direction is at
- // least touch slop before determining the gesture direction.
- if (distance > (mTouchSlop * mTouchSlop)) {
- if (deltaX > deltaY) {
- mPossibleVerticalSwipe = false;
- }
- mGestureDirectionLocked = true;
- }
- return mPossibleVerticalSwipe;
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (!isEnabled()) {
- return false;
- }
-
- // super.onTouchEvent can change the state of the scroll, keep a copy so that handleTouchUp
- // can exit early if scrollState != IDLE when the touch event started.
- int scrollState = getScrollState();
- boolean result = super.onTouchEvent(event);
- if (getChildCount() > 0) {
- int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_DOWN) {
- handleTouchDown(event);
- } else if (action == MotionEvent.ACTION_UP) {
- handleTouchUp(event, scrollState);
- getParent().requestDisallowInterceptTouchEvent(false);
- } else if (action == MotionEvent.ACTION_MOVE) {
- if (Math.abs(mTapPositionX - (int) event.getX()) >= mTouchSlop ||
- Math.abs(mTapPositionY - (int) event.getY()) >= mTouchSlop) {
- releasePressedItem();
- mCanClick = false;
- }
- result |= handlePossibleVerticalSwipe(event);
- getParent().requestDisallowInterceptTouchEvent(mPossibleVerticalSwipe);
- } else if (action == MotionEvent.ACTION_CANCEL) {
- getParent().requestDisallowInterceptTouchEvent(false);
- mCanClick = true;
- }
- }
- return result;
- }
-
- private void releasePressedItem() {
- if (mPressedView != null) {
- mPressedView.setPressed(false);
- mPressedView = null;
- }
- Handler handler = getHandler();
- if (handler != null) {
- handler.removeCallbacks(mPressedRunnable);
- }
- }
-
- private void onScroll(int dy) {
- for (OnScrollListener listener : mOnScrollListeners) {
- listener.onScroll(dy);
- }
- notifyChildrenAboutProximity(true);
- }
-
- /**
- * Adds a listener that will be called when the content of the list view is scrolled.
- */
- public void addOnScrollListener(OnScrollListener listener) {
- mOnScrollListeners.add(listener);
- }
-
- /**
- * Removes listener for scroll events.
- */
- public void removeOnScrollListener(OnScrollListener listener) {
- mOnScrollListeners.remove(listener);
- }
-
- /**
- * Adds a listener that will be called when the central item of the list changes.
- */
- public void addOnCentralPositionChangedListener(OnCentralPositionChangedListener listener) {
- mOnCentralPositionChangedListeners.add(listener);
- }
-
- /**
- * Removes a listener that would be called when the central item of the list changes.
- */
- public void removeOnCentralPositionChangedListener(OnCentralPositionChangedListener listener) {
- mOnCentralPositionChangedListeners.remove(listener);
- }
-
- /**
- * Determines if navigation of list with wrist gestures is enabled.
- */
- public boolean isGestureNavigationEnabled() {
- return mGestureNavigationEnabled;
- }
-
- /**
- * Sets whether navigation of list with wrist gestures is enabled.
- */
- public void setEnableGestureNavigation(boolean enabled) {
- mGestureNavigationEnabled = enabled;
- }
-
- @Override /* KeyEvent.Callback */
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- // Respond to keycodes (at least originally generated and injected by wrist gestures).
- if (mGestureNavigationEnabled) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_NAVIGATE_PREVIOUS:
- fling(0, -mMinFlingVelocity);
- return true;
- case KeyEvent.KEYCODE_NAVIGATE_NEXT:
- fling(0, mMinFlingVelocity);
- return true;
- case KeyEvent.KEYCODE_NAVIGATE_IN:
- return tapCenterView();
- case KeyEvent.KEYCODE_NAVIGATE_OUT:
- // Returing false leaves the action to the container of this WearableListView
- // (e.g. finishing the activity containing this WearableListView).
- return false;
- }
- }
- return super.onKeyDown(keyCode, event);
- }
-
- /**
- * Simulate tapping the child view at the center of this list.
- */
- private boolean tapCenterView() {
- if (!isEnabled() || getVisibility() != View.VISIBLE) {
- return false;
- }
- int index = findCenterViewIndex();
- View view = getChildAt(index);
- ViewHolder holder = getChildViewHolder(view);
- if (mClickListener != null) {
- mClickListener.onClick(holder);
- return true;
- }
- return false;
- }
-
- private boolean checkForTap(MotionEvent event) {
- // No taps are accepted if this view is disabled.
- if (!isEnabled()) {
- return false;
- }
-
- float rawY = event.getRawY();
- int index = findCenterViewIndex();
- View view = getChildAt(index);
- ViewHolder holder = getChildViewHolder(view);
- computeTapRegions(mTapRegions);
- if (rawY > mTapRegions[0] && rawY < mTapRegions[1]) {
- if (mClickListener != null) {
- mClickListener.onClick(holder);
- }
- return true;
- }
- if (index > 0 && rawY <= mTapRegions[0]) {
- animateToMiddle(index - 1, index);
- return true;
- }
- if (index < getChildCount() - 1 && rawY >= mTapRegions[1]) {
- animateToMiddle(index + 1, index);
- return true;
- }
- if (index == 0 && rawY <= mTapRegions[0] && mClickListener != null) {
- // Special case: if the top third of the screen is empty and the touch event happens
- // there, we don't want to immediately disallow the parent from using it. We tell
- // parent to disallow intercept only after we locked a gesture. Before that he
- // might do something with the action.
- mClickListener.onTopEmptyRegionClick();
- return true;
- }
- return false;
- }
-
- private void animateToMiddle(int newCenterIndex, int oldCenterIndex) {
- if (newCenterIndex == oldCenterIndex) {
- throw new IllegalArgumentException(
- "newCenterIndex must be different from oldCenterIndex");
- }
- List<Animator> animators = new ArrayList<Animator>();
- View child = getChildAt(newCenterIndex);
- int scrollToMiddle = getCentralViewTop() - child.getTop();
- startScrollAnimation(animators, scrollToMiddle, FLIP_ANIMATION_DURATION_MS);
- }
-
- private void startScrollAnimation(List<Animator> animators, int scroll, long duration) {
- startScrollAnimation(animators, scroll, duration, 0);
- }
-
- private void startScrollAnimation(List<Animator> animators, int scroll, long duration,
- long delay) {
- startScrollAnimation(animators, scroll, duration, delay, null);
- }
-
- private void startScrollAnimation(
- int scroll, long duration, long delay, Animator.AnimatorListener listener) {
- startScrollAnimation(null, scroll, duration, delay, listener);
- }
-
- private void startScrollAnimation(List<Animator> animators, int scroll, long duration,
- long delay, Animator.AnimatorListener listener) {
- if (mScrollAnimator != null) {
- mScrollAnimator.cancel();
- }
-
- mLastScrollChange = 0;
- ObjectAnimator scrollAnimator = ObjectAnimator.ofInt(this, mSetScrollVerticallyProperty,
- 0, -scroll);
-
- if (animators != null) {
- animators.add(scrollAnimator);
- AnimatorSet animatorSet = new AnimatorSet();
- animatorSet.playTogether(animators);
- mScrollAnimator = animatorSet;
- } else {
- mScrollAnimator = scrollAnimator;
- }
- mScrollAnimator.setDuration(duration);
- if (listener != null) {
- mScrollAnimator.addListener(listener);
- }
- if (delay > 0) {
- mScrollAnimator.setStartDelay(delay);
- }
- mScrollAnimator.start();
- }
-
- @Override
- public boolean fling(int velocityX, int velocityY) {
- if (getChildCount() == 0) {
- return false;
- }
- // If we are flinging towards empty space (before first element or after last), we reuse
- // original flinging mechanism.
- final int index = findCenterViewIndex();
- final View child = getChildAt(index);
- int currentPosition = getChildPosition(child);
- if ((currentPosition == 0 && velocityY < 0) ||
- (currentPosition == getAdapter().getItemCount() - 1 && velocityY > 0)) {
- return super.fling(velocityX, velocityY);
- }
-
- if (Math.abs(velocityY) < mMinFlingVelocity) {
- return false;
- }
- velocityY = Math.max(Math.min(velocityY, mMaxFlingVelocity), -mMaxFlingVelocity);
-
- if (mScroller == null) {
- mScroller = new Scroller(getContext(), null, true);
- }
- mScroller.fling(0, 0, 0, velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE,
- Integer.MIN_VALUE, Integer.MAX_VALUE);
- int finalY = mScroller.getFinalY();
- int delta = finalY / (getPaddingTop() + getAdjustedHeight() / 2);
- if (delta == 0) {
- // If the fling would not be enough to change position, we increase it to satisfy user's
- // intent of switching current position.
- delta = velocityY > 0 ? 1 : -1;
- }
- int finalPosition = Math.max(
- 0, Math.min(getAdapter().getItemCount() - 1, currentPosition + delta));
- smoothScrollToPosition(finalPosition);
- return true;
- }
-
- public void smoothScrollToPosition(int position, RecyclerView.SmoothScroller smoothScroller) {
- LayoutManager layoutManager = (LayoutManager) getLayoutManager();
- layoutManager.setCustomSmoothScroller(smoothScroller);
- smoothScrollToPosition(position);
- layoutManager.clearCustomSmoothScroller();
- }
-
- @Override
- public ViewHolder getChildViewHolder(View child) {
- return (ViewHolder) super.getChildViewHolder(child);
- }
-
- /**
- * Adds a listener that will be called when the user taps on the WearableListView or its items.
- */
- public void setClickListener(ClickListener clickListener) {
- mClickListener = clickListener;
- }
-
- /**
- * Adds a listener that will be called when the user drags the top element below its allowed
- * bottom position.
- *
- * @hide
- */
- public void setOverScrollListener(OnOverScrollListener listener) {
- mOverScrollListener = listener;
- }
-
- private int findCenterViewIndex() {
- // TODO(gruszczy): This could be easily optimized, so that we stop looking when we the
- // distance starts growing again, instead of finding the closest. It would safe half of
- // the loop.
- int count = getChildCount();
- int index = -1;
- int closest = Integer.MAX_VALUE;
- int centerY = getCenterYPos(this);
- for (int i = 0; i < count; ++i) {
- final View child = getChildAt(i);
- int childCenterY = getTop() + getCenterYPos(child);
- final int distance = Math.abs(centerY - childCenterY);
- if (distance < closest) {
- closest = distance;
- index = i;
- }
- }
- if (index == -1) {
- throw new IllegalStateException("Can't find central view.");
- }
- return index;
- }
-
- private static int getCenterYPos(View v) {
- return v.getTop() + v.getPaddingTop() + getAdjustedHeight(v) / 2;
- }
-
- private void handleTouchUp(MotionEvent event, int scrollState) {
- if (mCanClick && event != null && checkForTap(event)) {
- Handler handler = getHandler();
- if (handler != null) {
- handler.postDelayed(mReleasedRunnable, ViewConfiguration.getTapTimeout());
- }
- return;
- }
-
- if (scrollState != RecyclerView.SCROLL_STATE_IDLE) {
- // We are flinging, so let's not start animations just yet. Instead we will start them
- // when the fling finishes.
- return;
- }
-
- if (isOverScrolling()) {
- mOverScrollListener.onOverScroll();
- } else {
- animateToCenter();
- }
- }
-
- private boolean isOverScrolling() {
- return getChildCount() > 0
- // If first view top was below the central top, it means it was never centered.
- // Don't allow overscroll, otherwise a simple touch (instead of a drag) will be
- // enough to trigger overscroll.
- && mStartFirstTop <= getCentralViewTop()
- && getChildAt(0).getTop() >= getTopViewMaxTop()
- && mOverScrollListener != null;
- }
-
- private int getTopViewMaxTop() {
- return getHeight() / 2;
- }
-
- private int getItemHeight() {
- // Round up so that the screen is fully occupied by 3 items.
- return getAdjustedHeight() / THIRD + 1;
- }
-
- /**
- * Returns top of the central {@code View} in the list when such view is fully centered.
- *
- * This is a more or a less a static value that you can use to align other views with the
- * central one.
- */
- public int getCentralViewTop() {
- return getPaddingTop() + getItemHeight();
- }
-
- /**
- * Automatically starts an animation that snaps the list to center on the element closest to the
- * middle.
- */
- public void animateToCenter() {
- final int index = findCenterViewIndex();
- final View child = getChildAt(index);
- final int scrollToMiddle = getCentralViewTop() - child.getTop();
- startScrollAnimation(scrollToMiddle, CENTERING_ANIMATION_DURATION_MS, 0,
- new SimpleAnimatorListener() {
- @Override
- public void onAnimationEnd(Animator animator) {
- if (!wasCanceled()) {
- mCanClick = true;
- }
- }
- });
- }
-
- /**
- * Animate the list so that the first view is back to its initial position.
- * @param endAction Action to execute when the animation is done.
- * @hide
- */
- public void animateToInitialPosition(final Runnable endAction) {
- final View child = getChildAt(0);
- final int scrollToMiddle = getCentralViewTop() + mInitialOffset - child.getTop();
- startScrollAnimation(scrollToMiddle, CENTERING_ANIMATION_DURATION_MS, 0,
- new SimpleAnimatorListener() {
- @Override
- public void onAnimationEnd(Animator animator) {
- if (endAction != null) {
- endAction.run();
- }
- }
- });
- }
-
- private void handleTouchDown(MotionEvent event) {
- if (mCanClick) {
- mTapPositionX = (int) event.getX();
- mTapPositionY = (int) event.getY();
- float rawY = event.getRawY();
- computeTapRegions(mTapRegions);
- if (rawY > mTapRegions[0] && rawY < mTapRegions[1]) {
- View view = getChildAt(findCenterViewIndex());
- if (view instanceof OnCenterProximityListener) {
- Handler handler = getHandler();
- if (handler != null) {
- handler.removeCallbacks(mReleasedRunnable);
- handler.postDelayed(mPressedRunnable, ViewConfiguration.getTapTimeout());
- }
- }
- }
- }
- }
-
- private void setScrollVertically(int scroll) {
- scrollBy(0, scroll - mLastScrollChange);
- mLastScrollChange = scroll;
- }
-
- private int getAdjustedHeight() {
- return getAdjustedHeight(this);
- }
-
- private static int getAdjustedHeight(View v) {
- return v.getHeight() - v.getPaddingBottom() - v.getPaddingTop();
- }
-
- private void computeTapRegions(float[] tapRegions) {
- mLocation[0] = mLocation[1] = 0;
- getLocationOnScreen(mLocation);
- int mScreenTop = mLocation[1];
- int height = getHeight();
- tapRegions[0] = mScreenTop + height * TOP_TAP_REGION_PERCENTAGE;
- tapRegions[1] = mScreenTop + height * (1 - BOTTOM_TAP_REGION_PERCENTAGE);
- }
-
- /**
- * Determines if, when there is only one item in the WearableListView, that the single item
- * is laid out so that it's height fills the entire WearableListView.
- */
- public boolean getMaximizeSingleItem() {
- return mMaximizeSingleItem;
- }
-
- /**
- * When set to true, if there is only one item in the WearableListView, it will fill the entire
- * WearableListView. When set to false, the default behavior will be used and the single item
- * will fill only a third of the screen.
- */
- public void setMaximizeSingleItem(boolean maximizeSingleItem) {
- mMaximizeSingleItem = maximizeSingleItem;
- }
-
- private void notifyChildrenAboutProximity(boolean animate) {
- LayoutManager layoutManager = (LayoutManager) getLayoutManager();
- int count = layoutManager.getChildCount();
-
- if (count == 0) {
- return;
- }
-
- int index = layoutManager.findCenterViewIndex();
- for (int i = 0; i < count; ++i) {
- final View view = layoutManager.getChildAt(i);
- ViewHolder holder = getChildViewHolder(view);
- holder.onCenterProximity(i == index, animate);
- }
- final int position = getChildViewHolder(getChildAt(index)).getPosition();
- if (position != mPreviousCentral) {
- for (OnScrollListener listener : mOnScrollListeners) {
- listener.onCentralPositionChanged(position);
- }
- for (OnCentralPositionChangedListener listener :
- mOnCentralPositionChangedListeners) {
- listener.onCentralPositionChanged(position);
- }
- mPreviousCentral = position;
- }
- }
-
- // TODO: Move this to a separate class, so it can't directly interact with the WearableListView.
- private class LayoutManager extends RecyclerView.LayoutManager {
- private int mFirstPosition;
-
- private boolean mPushFirstHigher;
-
- private int mAbsoluteScroll;
-
- private boolean mUseOldViewTop = true;
-
- private boolean mWasZoomedIn = false;
-
- private RecyclerView.SmoothScroller mSmoothScroller;
-
- private RecyclerView.SmoothScroller mDefaultSmoothScroller;
-
- // We need to have another copy of the same method, because this one uses
- // LayoutManager.getChildCount/getChildAt instead of View.getChildCount/getChildAt and
- // they return different values.
- private int findCenterViewIndex() {
- // TODO(gruszczy): This could be easily optimized, so that we stop looking when we the
- // distance starts growing again, instead of finding the closest. It would safe half of
- // the loop.
- int count = getChildCount();
- int index = -1;
- int closest = Integer.MAX_VALUE;
- int centerY = getCenterYPos(WearableListView.this);
- for (int i = 0; i < count; ++i) {
- final View child = getLayoutManager().getChildAt(i);
- int childCenterY = getTop() + getCenterYPos(child);
- final int distance = Math.abs(centerY - childCenterY);
- if (distance < closest) {
- closest = distance;
- index = i;
- }
- }
- if (index == -1) {
- throw new IllegalStateException("Can't find central view.");
- }
- return index;
- }
-
- @Override
- public void onLayoutChildren(RecyclerView.Recycler recycler, State state) {
- final int parentBottom = getHeight() - getPaddingBottom();
- // By default we assume this is the first run and the first element will be centered
- // with optional initial offset.
- int oldTop = getCentralViewTop() + mInitialOffset;
- // Here we handle any other situation where we relayout or we want to achieve a
- // specific layout of children.
- if (mUseOldViewTop && getChildCount() > 0) {
- // We are performing a relayout after we already had some children, because e.g. the
- // contents of an adapter has changed. First we want to check, if the central item
- // from before the layout is still here, because we want to preserve it.
- int index = findCenterViewIndex();
- int position = getPosition(getChildAt(index));
- if (position == NO_POSITION) {
- // Central item was removed. Let's find the first surviving item and use it
- // as an anchor.
- for (int i = 0, N = getChildCount(); index + i < N || index - i >= 0; ++i) {
- View child = getChildAt(index + i);
- if (child != null) {
- position = getPosition(child);
- if (position != NO_POSITION) {
- index = index + i;
- break;
- }
- }
- child = getChildAt(index - i);
- if (child != null) {
- position = getPosition(child);
- if (position != NO_POSITION) {
- index = index - i;
- break;
- }
- }
- }
- }
- if (position == NO_POSITION) {
- // None of the children survives the relayout, let's just use the top of the
- // first one.
- oldTop = getChildAt(0).getTop();
- int count = state.getItemCount();
- // Lets first make sure that the first position is not above the last element,
- // which can happen if elements were removed.
- while (mFirstPosition >= count && mFirstPosition > 0) {
- mFirstPosition--;
- }
- } else {
- // Some of the children survived the relayout. We will keep it in its place,
- // but go through previous children and maybe add them.
- if (!mWasZoomedIn) {
- // If we were previously zoomed-in on a single item, ignore this and just
- // use the default value set above. Reasoning: if we are still zoomed-in,
- // oldTop will be ignored when laying out the single child element. If we
- // are no longer zoomed in, then we want to position items using the top
- // of the single item as if the single item was not zoomed in, which is
- // equal to the default value.
- oldTop = getChildAt(index).getTop();
- }
- while (oldTop > getPaddingTop() && position > 0) {
- position--;
- oldTop -= getItemHeight();
- }
- if (position == 0 && oldTop > getCentralViewTop()) {
- // We need to handle special case where the first, central item was removed
- // and now the first element is hanging below, instead of being nicely
- // centered.
- oldTop = getCentralViewTop();
- }
- mFirstPosition = position;
- }
- } else if (mPushFirstHigher) {
- // We are trying to position elements ourselves, so we force position of the first
- // one.
- oldTop = getCentralViewTop() - getItemHeight();
- }
-
- performLayoutChildren(recycler, state, parentBottom, oldTop);
-
- // Since the content might have changed, we need to adjust the absolute scroll in case
- // some elements have disappeared or were added.
- if (getChildCount() == 0) {
- setAbsoluteScroll(0);
- } else {
- View child = getChildAt(findCenterViewIndex());
- setAbsoluteScroll(child.getTop() - getCentralViewTop() + getPosition(child) *
- getItemHeight());
- }
-
- mUseOldViewTop = true;
- mPushFirstHigher = false;
- }
-
- private void performLayoutChildren(Recycler recycler, State state, int parentBottom,
- int top) {
- detachAndScrapAttachedViews(recycler);
-
- if (mMaximizeSingleItem && state.getItemCount() == 1) {
- performLayoutOneChild(recycler, parentBottom);
- mWasZoomedIn = true;
- } else {
- performLayoutMultipleChildren(recycler, state, parentBottom, top);
- mWasZoomedIn = false;
- }
-
- if (getChildCount() > 0) {
- post(mNotifyChildrenPostLayoutRunnable);
- }
- }
-
- private void performLayoutOneChild(Recycler recycler, int parentBottom) {
- final int right = getWidth() - getPaddingRight();
- View v = recycler.getViewForPosition(getFirstPosition());
- addView(v, 0);
- measureZoomView(v);
- v.layout(getPaddingLeft(), getPaddingTop(), right, parentBottom);
- }
-
- private void performLayoutMultipleChildren(Recycler recycler, State state, int parentBottom,
- int top) {
- int bottom;
- final int left = getPaddingLeft();
- final int right = getWidth() - getPaddingRight();
- final int count = state.getItemCount();
- // If we are laying out children with center element being different than the first, we
- // need to start with previous child which appears half visible at the top.
- for (int i = 0; getFirstPosition() + i < count; i++, top = bottom) {
- if (top >= parentBottom) {
- break;
- }
- View v = recycler.getViewForPosition(getFirstPosition() + i);
- addView(v, i);
- measureThirdView(v);
- bottom = top + getItemHeight();
- v.layout(left, top, right, bottom);
- }
- }
-
- private void setAbsoluteScroll(int absoluteScroll) {
- mAbsoluteScroll = absoluteScroll;
- for (OnScrollListener listener : mOnScrollListeners) {
- listener.onAbsoluteScrollChange(mAbsoluteScroll);
- }
- }
-
- private void measureView(View v, int height) {
- final LayoutParams lp = (LayoutParams) v.getLayoutParams();
- final int widthSpec = getChildMeasureSpec(getWidth(),
- getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, lp.width,
- canScrollHorizontally());
- final int heightSpec = getChildMeasureSpec(getHeight(),
- getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin,
- height, canScrollVertically());
- v.measure(widthSpec, heightSpec);
- }
-
- private void measureThirdView(View v) {
- measureView(v, (int) (1 + (float) getHeight() / THIRD));
- }
-
- private void measureZoomView(View v) {
- measureView(v, getHeight());
- }
-
- @Override
- public RecyclerView.LayoutParams generateDefaultLayoutParams() {
- return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- }
-
- @Override
- public boolean canScrollVertically() {
- // Disable vertical scrolling when zoomed.
- return getItemCount() != 1 || !mWasZoomedIn;
- }
-
- @Override
- public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, State state) {
- // TODO(gruszczy): This code is shit, needs to be rewritten.
- if (getChildCount() == 0) {
- return 0;
- }
- int scrolled = 0;
- final int left = getPaddingLeft();
- final int right = getWidth() - getPaddingRight();
- if (dy < 0) {
- while (scrolled > dy) {
- final View topView = getChildAt(0);
- if (getFirstPosition() > 0) {
- final int hangingTop = Math.max(-topView.getTop(), 0);
- final int scrollBy = Math.min(scrolled - dy, hangingTop);
- scrolled -= scrollBy;
- offsetChildrenVertical(scrollBy);
- if (getFirstPosition() > 0 && scrolled > dy) {
- mFirstPosition--;
- View v = recycler.getViewForPosition(getFirstPosition());
- addView(v, 0);
- measureThirdView(v);
- final int bottom = topView.getTop();
- final int top = bottom - getItemHeight();
- v.layout(left, top, right, bottom);
- } else {
- break;
- }
- } else {
- mPushFirstHigher = false;
- int maxScroll = mOverScrollListener!= null ?
- getHeight() : getTopViewMaxTop();
- final int scrollBy = Math.min(-dy + scrolled, maxScroll - topView.getTop());
- scrolled -= scrollBy;
- offsetChildrenVertical(scrollBy);
- break;
- }
- }
- } else if (dy > 0) {
- final int parentHeight = getHeight();
- while (scrolled < dy) {
- final View bottomView = getChildAt(getChildCount() - 1);
- if (state.getItemCount() > mFirstPosition + getChildCount()) {
- final int hangingBottom =
- Math.max(bottomView.getBottom() - parentHeight, 0);
- final int scrollBy = -Math.min(dy - scrolled, hangingBottom);
- scrolled -= scrollBy;
- offsetChildrenVertical(scrollBy);
- if (scrolled < dy) {
- View v = recycler.getViewForPosition(mFirstPosition + getChildCount());
- final int top = getChildAt(getChildCount() - 1).getBottom();
- addView(v);
- measureThirdView(v);
- final int bottom = top + getItemHeight();
- v.layout(left, top, right, bottom);
- } else {
- break;
- }
- } else {
- final int scrollBy =
- Math.max(-dy + scrolled, getHeight() / 2 - bottomView.getBottom());
- scrolled -= scrollBy;
- offsetChildrenVertical(scrollBy);
- break;
- }
- }
- }
- recycleViewsOutOfBounds(recycler);
- setAbsoluteScroll(mAbsoluteScroll + scrolled);
- return scrolled;
- }
-
- @Override
- public void scrollToPosition(int position) {
- mUseOldViewTop = false;
- if (position > 0) {
- mFirstPosition = position - 1;
- mPushFirstHigher = true;
- } else {
- mFirstPosition = position;
- mPushFirstHigher = false;
- }
- requestLayout();
- }
-
- public void setCustomSmoothScroller(RecyclerView.SmoothScroller smoothScroller) {
- mSmoothScroller = smoothScroller;
- }
-
- public void clearCustomSmoothScroller() {
- mSmoothScroller = null;
- }
-
- public RecyclerView.SmoothScroller getDefaultSmoothScroller(RecyclerView recyclerView) {
- if (mDefaultSmoothScroller == null) {
- mDefaultSmoothScroller = new SmoothScroller(
- recyclerView.getContext(), this);
- }
- return mDefaultSmoothScroller;
- }
- @Override
- public void smoothScrollToPosition(RecyclerView recyclerView, State state,
- int position) {
- RecyclerView.SmoothScroller scroller = mSmoothScroller;
- if (scroller == null) {
- scroller = getDefaultSmoothScroller(recyclerView);
- }
- scroller.setTargetPosition(position);
- startSmoothScroll(scroller);
- }
-
- private void recycleViewsOutOfBounds(RecyclerView.Recycler recycler) {
- final int childCount = getChildCount();
- final int parentWidth = getWidth();
- // Here we want to use real height, so we don't remove views that are only visible in
- // padded section.
- final int parentHeight = getHeight();
- boolean foundFirst = false;
- int first = 0;
- int last = 0;
- for (int i = 0; i < childCount; i++) {
- final View v = getChildAt(i);
- if (v.hasFocus() || (v.getRight() >= 0 && v.getLeft() <= parentWidth &&
- v.getBottom() >= 0 && v.getTop() <= parentHeight)) {
- if (!foundFirst) {
- first = i;
- foundFirst = true;
- }
- last = i;
- }
- }
- for (int i = childCount - 1; i > last; i--) {
- removeAndRecycleViewAt(i, recycler);
- }
- for (int i = first - 1; i >= 0; i--) {
- removeAndRecycleViewAt(i, recycler);
- }
- if (getChildCount() == 0) {
- mFirstPosition = 0;
- } else if (first > 0) {
- mPushFirstHigher = true;
- mFirstPosition += first;
- }
- }
-
- public int getFirstPosition() {
- return mFirstPosition;
- }
-
- @Override
- public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
- RecyclerView.Adapter newAdapter) {
- removeAllViews();
- }
- }
-
- /**
- * Interface for receiving callbacks when WearableListView children become or cease to be the
- * central item.
- */
- public interface OnCenterProximityListener {
- /**
- * Called when this view becomes central item of the WearableListView.
- *
- * @param animate Whether you should animate your transition of the View to become the
- * central item. If false, this is the initial setting and you should
- * transition immediately.
- */
- void onCenterPosition(boolean animate);
-
- /**
- * Called when this view stops being the central item of the WearableListView.
- * @param animate Whether you should animate your transition of the View to being
- * non central item. If false, this is the initial setting and you should
- * transition immediately.
- */
- void onNonCenterPosition(boolean animate);
- }
-
- /**
- * Interface for listening for click events on WearableListView.
- */
- public interface ClickListener {
- /**
- * Called when the central child of the WearableListView is tapped.
- * @param view View that was clicked.
- */
- public void onClick(ViewHolder view);
-
- /**
- * Called when the user taps the top third of the WearableListView and no item is present
- * there. This can happen when you are in initial state and the first, top-most item of the
- * WearableListView is centered.
- */
- public void onTopEmptyRegionClick();
- }
-
- /**
- * @hide
- */
- public interface OnOverScrollListener {
- public void onOverScroll();
- }
-
- /**
- * Interface for listening to WearableListView content scrolling.
- */
- public interface OnScrollListener {
- /**
- * Called when the content is scrolled, reporting the relative scroll value.
- * @param scroll Amount the content was scrolled. This is a delta from the previous
- * position to the new position.
- */
- public void onScroll(int scroll);
-
- /**
- * Called when the content is scrolled, reporting the absolute scroll value.
- *
- * @deprecated BE ADVISED DO NOT USE THIS This might provide wrong values when contents
- * of a RecyclerView change.
- *
- * @param scroll Absolute scroll position of the content inside the WearableListView.
- */
- @Deprecated
- public void onAbsoluteScrollChange(int scroll);
-
- /**
- * Called when WearableListView's scroll state changes.
- *
- * @param scrollState The updated scroll state. One of {@link #SCROLL_STATE_IDLE},
- * {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}.
- */
- public void onScrollStateChanged(int scrollState);
-
- /**
- * Called when the central item of the WearableListView changes.
- *
- * @param centralPosition Position of the item in the Adapter.
- */
- public void onCentralPositionChanged(int centralPosition);
- }
-
- /**
- * A listener interface that can be added to the WearableListView to get notified when the
- * central item is changed.
- */
- public interface OnCentralPositionChangedListener {
- /**
- * Called when the central item of the WearableListView changes.
- *
- * @param centralPosition Position of the item in the Adapter.
- */
- void onCentralPositionChanged(int centralPosition);
- }
-
- /**
- * Base class for adapters providing data for the WearableListView. For details refer to
- * RecyclerView.Adapter.
- */
- public static abstract class Adapter extends RecyclerView.Adapter<ViewHolder> {
- }
-
- private static class SmoothScroller extends LinearSmoothScroller {
-
- private static final float MILLISECONDS_PER_INCH = 100f;
-
- private final LayoutManager mLayoutManager;
-
- public SmoothScroller(Context context, WearableListView.LayoutManager manager) {
- super(context);
- mLayoutManager = manager;
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- }
-
- // TODO: (mindyp): when flinging, return the dydt that triggered the fling.
- @Override
- protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
- return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
- }
-
- @Override
- public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int
- snapPreference) {
- // Snap to center.
- return (boxStart + boxEnd) / 2 - (viewStart + viewEnd) / 2;
- }
-
- @Override
- public PointF computeScrollVectorForPosition(int targetPosition) {
- if (targetPosition < mLayoutManager.getFirstPosition()) {
- return new PointF(0, -1);
- } else {
- return new PointF(0, 1);
- }
- }
- }
-
- /**
- * Wrapper around items displayed in the list view. {@link .Adapter} must return objects that
- * are instances of this class. Consider making the wrapped View implement
- * {@link .OnCenterProximityListener} if you want to receive a callback when it becomes or
- * ceases to be the central item in the WearableListView.
- */
- public static class ViewHolder extends RecyclerView.ViewHolder {
- public ViewHolder(View itemView) {
- super(itemView);
- }
-
- /**
- * Called when the wrapped view is becoming or ceasing to be the central item of the
- * WearableListView.
- *
- * Retained as protected for backwards compatibility.
- *
- * @hide
- */
- protected void onCenterProximity(boolean isCentralItem, boolean animate) {
- if (!(itemView instanceof OnCenterProximityListener)) {
- return;
- }
- OnCenterProximityListener item = (OnCenterProximityListener) itemView;
- if (isCentralItem) {
- item.onCenterPosition(animate);
- } else {
- item.onNonCenterPosition(animate);
- }
- }
- }
-
- private class SetScrollVerticallyProperty extends Property<WearableListView, Integer> {
- public SetScrollVerticallyProperty() {
- super(Integer.class, "scrollVertically");
- }
-
- @Override
- public Integer get(WearableListView wearableListView) {
- return wearableListView.mLastScrollChange;
- }
-
- @Override
- public void set(WearableListView wearableListView, Integer value) {
- wearableListView.setScrollVertically(value);
- }
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/Constants.java b/PermissionController/src/com/android/permissioncontroller/Constants.java
index ae399f0c3..9efa2295b 100644
--- a/PermissionController/src/com/android/permissioncontroller/Constants.java
+++ b/PermissionController/src/com/android/permissioncontroller/Constants.java
@@ -22,6 +22,7 @@ import androidx.annotation.RequiresApi;
import com.android.permissioncontroller.hibernation.HibernationJobService;
import com.android.permissioncontroller.permission.service.PermissionEventCleanupJobService;
+import com.android.permissioncontroller.permission.service.v34.SafetyLabelChangesJobService;
/**
* App-global constants
@@ -70,14 +71,25 @@ public class Constants {
*/
public static final int PERIODIC_ACCESSIBILITY_CHECK_JOB_ID = 6;
- /**
- * ID for Safety Centers delayed job scheduled after boot and after Safety Center is enabled
- *
- * @see
+ /**
+ * ID for Safety Centers periodic background refresh job, scheduled after boot and after Safety
+ * Center is enabled, in {@link
* com.android.permissioncontroller.safetycenter.service.SafetyCenterBackgroundRefreshJobService
+ * }.
*/
public static final int SAFETY_CENTER_BACKGROUND_REFRESH_JOB_ID = 7;
+ /**
+ * ID for the periodic job in
+ * {@link SafetyLabelChangesJobService}.
+ */
+ public static final int PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID = 8;
+
+ /**
+ * ID for the on-demand, but delayed job in
+ * {@link SafetyLabelChangesJobService}.
+ */
+ public static final int SAFETY_LABEL_CHANGES_JOB_ID = 9;
/**
* Name of file to containing the packages we already showed a notification for.
@@ -118,6 +130,12 @@ public class Constants {
public static final int ACCESSIBILITY_CHECK_NOTIFICATION_ID = 4;
/**
+ * ID for notification shown by
+ * {@link SafetyLabelChangesJobService}.
+ */
+ public static final int SAFETY_LABEL_CHANGES_NOTIFICATION_ID = 5;
+
+ /**
* String action for navigating to the auto revoke screen.
*/
public static final String ACTION_MANAGE_AUTO_REVOKE = "manageAutoRevoke";
@@ -141,8 +159,9 @@ public class Constants {
* Channel of the notifications shown by
* {@link com.android.permissioncontroller.permission.service.LocationAccessCheck},
* {@link com.android.permissioncontroller.privacysources.NotificationListenerCheck},
- * {@link com.android.permissioncontroller.hibernation.HibernationPolicyKt}, and
- * {@link com.android.permissioncontroller.auto.DrivingDecisionReminderService}
+ * {@link com.android.permissioncontroller.hibernation.HibernationPolicyKt},
+ * {@link com.android.permissioncontroller.auto.DrivingDecisionReminderService}, and
+ * {@link SafetyLabelChangesJobService}
*/
public static final String PERMISSION_REMINDER_CHANNEL_ID = "permission reminders";
diff --git a/PermissionController/src/com/android/permissioncontroller/DeviceUtils.java b/PermissionController/src/com/android/permissioncontroller/DeviceUtils.java
index 60caf888c..ec3642d1d 100644
--- a/PermissionController/src/com/android/permissioncontroller/DeviceUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/DeviceUtils.java
@@ -18,12 +18,10 @@ package com.android.permissioncontroller;
import android.content.Context;
import android.content.pm.PackageManager;
-import android.content.res.Configuration;
public class DeviceUtils {
public static boolean isTelevision(Context context) {
- int uiMode = context.getResources().getConfiguration().uiMode;
- return (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
}
public static boolean isWear(final Context context) {
diff --git a/PermissionController/src/com/android/permissioncontroller/PermissionController.proto b/PermissionController/src/com/android/permissioncontroller/PermissionController.proto
index 429909b71..02e51ce2d 100644
--- a/PermissionController/src/com/android/permissioncontroller/PermissionController.proto
+++ b/PermissionController/src/com/android/permissioncontroller/PermissionController.proto
@@ -17,7 +17,7 @@ syntax = "proto2";
package com.android.permissioncontroller;
option java_outer_classname = "PermissionControllerProto";
-import "packages/modules/Permission/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.proto";
+import "permission/service/AutoRevokePermissions.proto";
message PermissionControllerDumpProto {
optional permission.service.AutoRevokePermissionsDumpProto autoRevoke = 1;
diff --git a/PermissionController/src/com/android/permissioncontroller/PermissionControllerApplication.java b/PermissionController/src/com/android/permissioncontroller/PermissionControllerApplication.java
index bc7a1d051..68495ce1e 100644
--- a/PermissionController/src/com/android/permissioncontroller/PermissionControllerApplication.java
+++ b/PermissionController/src/com/android/permissioncontroller/PermissionControllerApplication.java
@@ -27,11 +27,14 @@ import android.view.accessibility.AccessibilityManager;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.Utils;
import com.android.permissioncontroller.privacysources.SafetyCenterAccessibilityListener;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
+import com.android.permissioncontroller.role.model.RoleParserInitializer;
import com.android.permissioncontroller.role.ui.SpecialAppAccessListActivity;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
public final class PermissionControllerApplication extends Application {
@@ -44,10 +47,14 @@ public final class PermissionControllerApplication extends Application {
sInstance = this;
PackageItemInfo.forceSafeLabels();
+ RoleParserInitializer.initialize();
updateSpecialAppAccessListActivityEnabledState();
if (SdkLevel.isAtLeastT()) {
addAccessibilityListener();
}
+ if (Utils.isHealthPermissionUiEnabled()) {
+ KotlinUtils.INSTANCE.addHealthPermissions(this);
+ }
}
/**
@@ -64,7 +71,7 @@ public final class PermissionControllerApplication extends Application {
for (int i = 0; i < rolesSize; i++) {
Role role = roles.valueAt(i);
- if (!role.isAvailable(this) || !role.isVisible(this)) {
+ if (!role.isAvailable(this) || !RoleUiBehaviorUtils.isVisible(role, this)) {
continue;
}
if (!role.isExclusive()) {
diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
index 22c3353ff..3c0c981dd 100644
--- a/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
+++ b/PermissionController/src/com/android/permissioncontroller/hibernation/HibernationPolicy.kt
@@ -181,7 +181,7 @@ fun cancelUnusedAppsNotification(context: Context) {
fun rescanAndPushDataToSafetyCenter(
context: Context,
sessionId: Long,
- safetyEvent: SafetyEvent
+ safetyEvent: SafetyEvent,
) {
val safetyCenterManager: SafetyCenterManager =
context.getSystemService(SafetyCenterManager::class.java)!!
@@ -337,7 +337,7 @@ class HibernationBroadcastReceiver : BroadcastReceiver() {
*/
@MainThread
private suspend fun getAppsToHibernate(
- context: Context
+ context: Context,
): Map<UserHandle, List<LightPackageInfo>> {
val now = System.currentTimeMillis()
val startTimeOfUnusedAppTracking = getStartTimeOfUnusedAppTracking(context.sharedPreferences)
@@ -484,7 +484,7 @@ private fun List<UsageStats>.lastTimePackageUsed(pkgName: String): Long {
*/
suspend fun isPackageHibernationExemptBySystem(
pkg: LightPackageInfo,
- user: UserHandle
+ user: UserHandle,
): Boolean {
if (!LauncherPackagesLiveData.getInitializedValue().contains(pkg.packageName)) {
if (DEBUG_HIBERNATION_POLICY) {
@@ -505,6 +505,14 @@ suspend fun isPackageHibernationExemptBySystem(
return true
}
+ if (pkg.uid == Process.SYSTEM_UID){
+ if (DEBUG_HIBERNATION_POLICY) {
+ DumpableLog.i(LOG_TAG,
+ "Exempted ${pkg.packageName} - Package shares system uid")
+ }
+ return true
+ }
+
val context = PermissionControllerApplication.get()
if (context.getSystemService(DevicePolicyManager::class.java)!!.isDeviceManaged) {
// TODO(b/237065504): Use proper system API to check if the device is financed in U.
@@ -544,6 +552,15 @@ suspend fun isPackageHibernationExemptBySystem(
return true
}
+ val emergencyRoleHolders = context.getSystemService(android.app.role.RoleManager::class.java)!!
+ .getRoleHolders(RoleManager.ROLE_EMERGENCY)
+ if (emergencyRoleHolders.contains(pkg.packageName)) {
+ if (DEBUG_HIBERNATION_POLICY) {
+ DumpableLog.i(LOG_TAG, "Exempted ${pkg.packageName} - emergency app")
+ }
+ return true
+ }
+
if (SdkLevel.isAtLeastS()) {
val hasInstallOrUpdatePermissions =
context.checkPermission(
@@ -598,7 +615,7 @@ suspend fun isPackageHibernationExemptBySystem(
*/
suspend fun isPackageHibernationExemptByUser(
context: Context,
- pkg: LightPackageInfo
+ pkg: LightPackageInfo,
): Boolean {
val packageName = pkg.packageName
val packageUid = pkg.uid
diff --git a/PermissionController/src/com/android/permissioncontroller/hibernation/OWNERS b/PermissionController/src/com/android/permissioncontroller/hibernation/OWNERS
new file mode 100644
index 000000000..21ab3aa28
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/hibernation/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/core/java/android/apphibernation/OWNERS
diff --git a/PermissionController/src/com/android/permissioncontroller/incident/ReportDetails.java b/PermissionController/src/com/android/permissioncontroller/incident/ReportDetails.java
index 95fd2516f..d19ef460a 100644
--- a/PermissionController/src/com/android/permissioncontroller/incident/ReportDetails.java
+++ b/PermissionController/src/com/android/permissioncontroller/incident/ReportDetails.java
@@ -100,7 +100,7 @@ public class ReportDetails {
final IncidentHeaderProto header = incident.getHeader(i);
if (header.hasReason()) {
final String reason = header.getReason();
- if (reason != null && reason.length() > 0) {
+ if (reason.length() > 0) {
result.add(reason);
}
}
@@ -122,9 +122,6 @@ public class ReportDetails {
final int setsCount = section.getSetsCount();
for (int i = 0; i < setsCount; i++) {
final RestrictedImageSetProto set = section.getSets(i);
- if (set == null) {
- continue;
- }
final int imageCount = set.getImagesCount();
for (int j = 0; j < imageCount; j++) {
// Hard cap on number of images, as a guardrail.
@@ -135,18 +132,12 @@ public class ReportDetails {
}
final RestrictedImageProto image = set.getImages(j);
- if (image == null) {
- continue;
- }
final String mimeType = image.getMimeType();
if (!("image/jpeg".equals(mimeType)
|| "image/png".equals(mimeType))) {
throw new ParseException("Unsupported image type " + mimeType);
}
final ByteString bytes = image.getImageData();
- if (bytes == null) {
- continue;
- }
final byte[] buf = bytes.toByteArray();
if (buf.length == 0) {
continue;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING
index e9b30d20a..b65eb6710 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/permission/TEST_MAPPING
@@ -5,6 +5,24 @@
"options": [
{
"include-filter": "android.permission.cts.BackgroundPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.LocationAccessCheckTest"
+ },
+ {
+ "include-filter": "android.permission.cts.NotificationListenerCheckTest"
+ },
+ {
+ "include-filter": "android.permission.cts.OneTimePermissionTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionControllerTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PlatformPermissionGroupMappingTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
},
@@ -12,16 +30,46 @@
"name": "CtsHibernationTestCases",
"options": [
{
- "include-filter": "android.hibernation.cts.AutoRevokeTest"
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
}
],
- "presubmit-large": [
+ "mainline-presubmit": [
{
- "name": "CtsPermission3TestCases",
+ "name": "CtsPermissionTestCases[com.google.android.permission.apex]",
"options": [
{
+ "include-filter": "android.permission.cts.BackgroundPermissionsTest"
+ },
+ {
+ "include-filter": "android.permission.cts.LocationAccessCheckTest"
+ },
+ {
+ "include-filter": "android.permission.cts.NotificationListenerCheckTest"
+ },
+ {
+ "include-filter": "android.permission.cts.OneTimePermissionTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PermissionControllerTest"
+ },
+ {
+ "include-filter": "android.permission.cts.PlatformPermissionGroupMappingTest"
+ },
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsHibernationTestCases[com.google.android.permission.apex]",
+ "options": [
+ // TODO(b/238677038): This test currently fails on R base image
+ {
+ "exclude-filter": "android.hibernation.cts.AutoRevokeTest#testUnusedApp_uninstallApp"
+ },
+ {
"exclude-annotation": "android.platform.test.annotations.FlakyTest"
}
]
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/LightInstallSourceInfoLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/LightInstallSourceInfoLiveData.kt
new file mode 100644
index 000000000..543d8eae2
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/LightInstallSourceInfoLiveData.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.data
+
+import android.app.Application
+import android.content.Context
+import android.content.pm.InstallSourceInfo
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.util.Log
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.model.livedatatypes.LightInstallSourceInfo
+import com.android.permissioncontroller.permission.model.livedatatypes.LightInstallSourceInfo.Companion.UNKNOWN_INSTALL_SOURCE
+import com.android.permissioncontroller.permission.utils.Utils
+import kotlinx.coroutines.Job
+
+/**
+ * [LightInstallSourceInfo] [LiveData] for the specified package
+ *
+ * @param app current Application
+ * @param packageName name of the package to get InstallSourceInfo for
+ * @param user The user of the package
+ */
+class LightInstallSourceInfoLiveData
+private constructor(
+ private val app: Application,
+ private val packageName: String,
+ private val user: UserHandle
+) : SmartAsyncMediatorLiveData<LightInstallSourceInfo>(),
+ PackageBroadcastReceiver.PackageBroadcastListener {
+
+ override fun onActive() {
+ super.onActive()
+ PackageBroadcastReceiver.addChangeCallback(packageName, this)
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+ PackageBroadcastReceiver.removeChangeCallback(packageName, this)
+ }
+
+ /**
+ * Callback from the PackageBroadcastReceiver
+ *
+ * @param packageName the name of the package which was updated.
+ */
+ override fun onPackageUpdate(packageName: String) {
+ update()
+ }
+
+ override suspend fun loadDataAndPostValue(job: Job) {
+ if (job.isCancelled) {
+ return
+ }
+
+ val lightInstallSourceInfo: LightInstallSourceInfo =
+ try {
+ val userContext = Utils.getUserContext(app, user)
+ LightInstallSourceInfo(
+ getInstallSourceInfo(userContext, packageName).installingPackageName)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(LOG_TAG, "InstallSourceInfo for $packageName not found")
+ SafetyLabelInfoLiveData.invalidateSingle(packageName to user)
+ UNKNOWN_INSTALL_SOURCE
+ }
+ postValue(lightInstallSourceInfo)
+ }
+
+ companion object :
+ DataRepositoryForPackage<Pair<String, UserHandle>, LightInstallSourceInfoLiveData>() {
+ private val LOG_TAG = LightInstallSourceInfoLiveData::class.java.simpleName
+
+ override fun newValue(key: Pair<String, UserHandle>): LightInstallSourceInfoLiveData {
+ return LightInstallSourceInfoLiveData(
+ PermissionControllerApplication.get(), key.first, key.second)
+ }
+
+ /** Returns the [InstallSourceInfo] for the given package */
+ @Throws(PackageManager.NameNotFoundException::class)
+ private fun getInstallSourceInfo(context: Context, packageName: String): InstallSourceInfo {
+ return context.packageManager.getInstallSourceInfo(packageName)
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/LightPackageInfoLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/LightPackageInfoLiveData.kt
index 101ac7a6c..4326a2c7b 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/LightPackageInfoLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/LightPackageInfoLiveData.kt
@@ -22,6 +22,7 @@ import android.os.UserHandle
import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.Observer
+import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
import com.android.permissioncontroller.permission.utils.Utils
@@ -94,8 +95,11 @@ class LightPackageInfoLiveData private constructor(
return
}
postValue(try {
- LightPackageInfo(context.packageManager.getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS))
+ var flags = PackageManager.GET_PERMISSIONS
+ if (SdkLevel.isAtLeastS()) {
+ flags = flags or PackageManager.GET_ATTRIBUTIONS
+ }
+ LightPackageInfo(context.packageManager.getPackageInfo(packageName, flags))
} catch (e: PackageManager.NameNotFoundException) {
Log.w(LOG_TAG, "Package \"$packageName\" not found")
invalidateSingle(packageName to user)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/PackageBroadcastReceiver.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/PackageBroadcastReceiver.kt
index 9dc16e306..c66e7a7d6 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/PackageBroadcastReceiver.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/PackageBroadcastReceiver.kt
@@ -161,6 +161,8 @@ object PackageBroadcastReceiver : BroadcastReceiver() {
HibernationSettingStateLiveData.invalidateAllForPackage(packageName)
LightAppPermGroupLiveData.invalidateAllForPackage(packageName)
AppPermGroupUiInfoLiveData.invalidateAllForPackage(packageName)
+ SafetyLabelInfoLiveData.invalidateAllForPackage(packageName)
+ LightInstallSourceInfoLiveData.invalidateAllForPackage(packageName)
}
}
@@ -176,4 +178,4 @@ object PackageBroadcastReceiver : BroadcastReceiver() {
*/
fun onPackageUpdate(packageName: String)
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesUiInfoLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesUiInfoLiveData.kt
index 0318144e4..5d91ebfda 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesUiInfoLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/PermGroupsPackagesUiInfoLiveData.kt
@@ -18,6 +18,8 @@ package com.android.permissioncontroller.permission.data
import android.app.Application
import android.app.role.RoleManager
+import android.os.Handler
+import android.os.Looper
import android.os.UserHandle
import androidx.lifecycle.LiveData
import com.android.permissioncontroller.PermissionControllerApplication
@@ -34,11 +36,24 @@ import com.android.permissioncontroller.permission.utils.Utils
*/
class PermGroupsPackagesUiInfoLiveData(
private val app: Application,
- groupNamesLiveData: LiveData<List<String>>
+ private val groupNamesLiveData: LiveData<List<String>>
) : SmartUpdateMediatorLiveData<
@kotlin.jvm.JvmSuppressWildcards Map<String, PermGroupPackagesUiInfo?>>() {
private val SYSTEM_SHELL = "android.app.role.SYSTEM_SHELL"
+ private val STAGGER_LOAD_TIME_MS = 50L
+
+ // Optimization: This LiveData relies on a large number of other ones. Enough that they can
+ // cause loading issues when they all become active at once. If this value is true, then it will
+ // slowly load data from all source LiveDatas, spacing loads them STAGGER_LOAD_TIME_MS apart
+ var loadStaggered: Boolean = false
+
+ // If we are returning from a particular permission group page, then that particular group is
+ // the one most likely to change. If so, then it will be prioritized in the load order.
+ var firstLoadGroup: String? = null
+
+ private val handler: Handler = Handler(Looper.getMainLooper())
+
/**
* Map<permission group name, PermGroupUiLiveDatas>
*/
@@ -139,4 +154,39 @@ class PermGroupsPackagesUiInfoLiveData(
}
value = allPackageData.toMap()
}
+
+ // Schedule a staggered loading of individual permission group livedatas
+ private fun scheduleStaggeredGroupLoad() {
+ if (groupNamesLiveData.value != null) {
+ if (firstLoadGroup in groupNames) {
+ addLiveDataDelayed(firstLoadGroup!!, 0)
+ }
+ for ((idx, groupName) in groupNames.withIndex()) {
+ if (groupName != firstLoadGroup) {
+ addLiveDataDelayed(groupName, idx * STAGGER_LOAD_TIME_MS)
+ }
+ }
+ }
+ }
+
+ private fun addLiveDataDelayed(groupName: String, delayTimeMs: Long) {
+ val liveData = SinglePermGroupPackagesUiInfoLiveData[groupName]
+ permGroupPackagesLiveDatas[groupName] = liveData
+ handler.postDelayed( { addSource(liveData) { update() } }, delayTimeMs)
+ }
+
+ override fun onActive() {
+ super.onActive()
+ if (loadStaggered && permGroupPackagesLiveDatas.isEmpty()) {
+ scheduleStaggeredGroupLoad()
+ }
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+ if (loadStaggered) {
+ permGroupPackagesLiveDatas.forEach { (_, liveData) -> removeSource(liveData) }
+ permGroupPackagesLiveDatas.clear()
+ }
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelInfoLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelInfoLiveData.kt
new file mode 100644
index 000000000..9e7865b4f
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/SafetyLabelInfoLiveData.kt
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.data
+
+import android.app.Application
+import android.content.pm.PackageManager
+import android.os.PersistableBundle
+import android.os.UserHandle
+import android.util.Log
+import com.android.permission.safetylabel.DataCategoryConstants
+import com.android.permission.safetylabel.DataLabelConstants
+import com.android.permission.safetylabel.DataTypeConstants
+import com.android.permission.safetylabel.SafetyLabel
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.model.livedatatypes.SafetyLabelInfo
+import com.android.permissioncontroller.permission.utils.KotlinUtils.isPlaceholderSafetyLabelDataEnabled
+import kotlinx.coroutines.Job
+
+/**
+ * [SafetyLabelInfo] [LiveData] for the specified package
+ *
+ * @param app current Application
+ * @param packageName name of the package to get SafetyLabel information for
+ * @param user The user of the package
+ */
+class SafetyLabelInfoLiveData
+private constructor(
+ private val app: Application,
+ private val packageName: String,
+ private val user: UserHandle
+) :
+ SmartAsyncMediatorLiveData<SafetyLabelInfo>(),
+ PackageBroadcastReceiver.PackageBroadcastListener {
+
+ private val lightInstallSourceInfoLiveData = LightInstallSourceInfoLiveData[packageName, user]
+
+ init {
+ addSource(lightInstallSourceInfoLiveData) { update() }
+
+ update()
+ }
+
+ override fun onActive() {
+ super.onActive()
+ PackageBroadcastReceiver.addChangeCallback(packageName, this)
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+ PackageBroadcastReceiver.removeChangeCallback(packageName, this)
+ }
+
+ /**
+ * Callback from the PackageBroadcastReceiver
+ *
+ * @param packageName the name of the package which was updated.
+ */
+ override fun onPackageUpdate(packageName: String) {
+ update()
+ }
+
+ override suspend fun loadDataAndPostValue(job: Job) {
+ if (job.isCancelled) {
+ return
+ }
+
+ if (lightInstallSourceInfoLiveData.isStale) {
+ return
+ }
+
+ // TODO(b/261607291): Add support preinstall apps that provide SafetyLabel. Installing
+ // package is null until updated from an app store
+ val installSourcePackageName = lightInstallSourceInfoLiveData.value?.installingPackageName
+ if (installSourcePackageName == null) {
+ postValue(SafetyLabelInfo.UNAVAILABLE)
+ return
+ }
+
+ val safetyLabelInfo: SafetyLabelInfo =
+ try {
+ val metadataBundle: PersistableBundle = getAppMetadata()
+ val safetyLabel: SafetyLabel? =
+ SafetyLabel.getSafetyLabelFromMetadata(metadataBundle)
+ if (safetyLabel != null) {
+ SafetyLabelInfo(safetyLabel, installSourcePackageName)
+ } else {
+ SafetyLabelInfo.UNAVAILABLE
+ }
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(LOG_TAG, "SafetyLabel for $packageName not found")
+ invalidateSingle(packageName to user)
+ SafetyLabelInfo.UNAVAILABLE
+ }
+ postValue(safetyLabelInfo)
+ }
+
+ private fun getAppMetadata(): PersistableBundle {
+ return if (isPlaceholderSafetyLabelDataEnabled()) {
+ placeholderMetadataBundle()
+ } else {
+ app.packageManager.getAppMetadata(packageName)
+ }
+ }
+
+ private fun placeholderMetadataBundle(): PersistableBundle {
+ val approximateLocationBundle =
+ PersistableBundle().apply { putIntArray("purposes", (1..7).toList().toIntArray()) }
+
+ val locationBundle =
+ PersistableBundle().apply {
+ putPersistableBundle(
+ DataTypeConstants.LOCATION_APPROX_LOCATION, approximateLocationBundle)
+ }
+
+ val dataSharedBundle =
+ PersistableBundle().apply {
+ putPersistableBundle(DataCategoryConstants.CATEGORY_LOCATION, locationBundle)
+ }
+
+ val dataLabelBundle =
+ PersistableBundle().apply {
+ putPersistableBundle(DataLabelConstants.DATA_USAGE_SHARED, dataSharedBundle)
+ }
+
+ val safetyLabelBundle =
+ PersistableBundle().apply { putPersistableBundle("data_labels", dataLabelBundle) }
+
+ return PersistableBundle().apply {
+ putPersistableBundle("safety_labels", safetyLabelBundle)
+ }
+ }
+
+ companion object :
+ DataRepositoryForPackage<Pair<String, UserHandle>, SafetyLabelInfoLiveData>() {
+ private val LOG_TAG = SafetyLabelInfoLiveData::class.java.simpleName
+
+ override fun newValue(key: Pair<String, UserHandle>): SafetyLabelInfoLiveData {
+ return SafetyLabelInfoLiveData(
+ PermissionControllerApplication.get(), key.first, key.second)
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
index c74567d52..cca266721 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
@@ -36,8 +36,8 @@ import kotlinx.coroutines.launch
*
* @param isStaticVal Whether or not this LiveData value is expected to change
*/
-abstract class SmartUpdateMediatorLiveData<T>(private val isStaticVal: Boolean = false)
- : MediatorLiveData<T>(), DataRepository.InactiveTimekeeper {
+abstract class SmartUpdateMediatorLiveData<T>(private val isStaticVal: Boolean = false) :
+ MediatorLiveData<T>(), DataRepository.InactiveTimekeeper {
companion object {
const val DEBUG_UPDATES = false
@@ -146,8 +146,9 @@ abstract class SmartUpdateMediatorLiveData<T>(private val isStaticVal: Boolean =
}
try {
super.addSource(source, onChanged)
- } catch (other: IllegalStateException) {
- throw other.apply { initCause(exception) }
+ } catch (ex: IllegalStateException) {
+ val other = ex as java.lang.Throwable
+ throw other.initCause(exception)
}
}
}
@@ -179,7 +180,7 @@ abstract class SmartUpdateMediatorLiveData<T>(private val isStaticVal: Boolean =
have: MutableMap<K, V>,
getLiveDataFun: (K) -> V,
onUpdateFun: ((K) -> Unit)? = null
- ) : Pair<Set<K>, Set<K>>{
+ ): Pair<Set<K>, Set<K>>{
// Ensure the map is correct when method returns
val (toAdd, toRemove) = KotlinUtils.getMapAndListDifferences(desired, have)
for (key in toAdd) {
@@ -252,4 +253,4 @@ abstract class SmartUpdateMediatorLiveData<T>(private val isStaticVal: Boolean =
},
isInitialized = { isInitialized && (staleOk || !isStale) })
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt
new file mode 100644
index 000000000..d311f30fd
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightHistoricalPackageOpsLiveData.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.data.v31
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.HISTORY_FLAG_DISCRETE
+import android.app.AppOpsManager.HistoricalOps
+import android.app.AppOpsManager.HistoricalOpsRequest
+import android.app.AppOpsManager.OP_FLAG_SELF
+import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED
+import android.app.Application
+import android.os.UserHandle
+import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData
+import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicReference
+import kotlinx.coroutines.Job
+
+/**
+ * LiveData class tracking [LightHistoricalPackageOps] for all packages on the device and for the
+ * provided app ops.
+ *
+ * App ops data is retrieved from [AppOpsManager] and is updated whenever app ops data changes are
+ * heard.
+ */
+class AllLightHistoricalPackageOpsLiveData(app: Application, val opNames: Set<String>) :
+ SmartAsyncMediatorLiveData<Map<Pair<String, UserHandle>, LightHistoricalPackageOps>>(),
+ AppOpsManager.OnOpActiveChangedListener,
+ AppOpsManager.OnOpNotedListener,
+ AppOpsManager.OnOpChangedListener {
+
+ private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
+
+ override fun onActive() {
+ super.onActive()
+
+ opNames.forEach { opName ->
+ // TODO(b/262035952): We watch each active op individually as startWatchingActive only
+ // registers the callback if all ops are valid. Fix this behavior so if one op is
+ // invalid it doesn't affect the other ops.
+ try {
+ appOpsManager.startWatchingActive(arrayOf(opName), { it.run() }, this)
+ } catch (ignored: IllegalArgumentException) {
+ // Older builds may not support all requested app ops.
+ }
+
+ try {
+ appOpsManager.startWatchingMode(opName, /* all packages */ null, this)
+ } catch (ignored: IllegalArgumentException) {
+ // Older builds may not support all requested app ops.
+ }
+ }
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+
+ appOpsManager.stopWatchingActive(this)
+ appOpsManager.stopWatchingMode(this)
+ }
+
+ override suspend fun loadDataAndPostValue(job: Job) {
+ if (job.isCancelled) {
+ return
+ }
+
+ val allLightHistoricalPackageOps =
+ mutableMapOf<Pair<String, UserHandle>, LightHistoricalPackageOps>()
+
+ val endTimeMillis = System.currentTimeMillis()
+ // TODO(b/257317733): Consider setting timeframe according to whether we are displaying 7
+ // days data.
+ val beginTimeMillis = endTimeMillis - TimeUnit.DAYS.toMillis(7)
+
+ // TODO(b/257317733): The following AppOpsManager call has been copied from legacy code in
+ // PermissionUsages.
+ // Rewrite it once we have confidence in the rest of the LiveData pattern for the Permission
+ // Usage Details page. Can we use a coroutine here?
+ val historicalOpsRef = AtomicReference<HistoricalOps>()
+ val latch = CountDownLatch(1)
+
+ val request =
+ HistoricalOpsRequest.Builder(beginTimeMillis, endTimeMillis)
+ .setFlags(OP_FLAG_SELF or OP_FLAG_TRUSTED_PROXIED)
+ .setHistoryFlags(HISTORY_FLAG_DISCRETE or HISTORY_FLAG_GET_ATTRIBUTION_CHAINS)
+ .build()
+ appOpsManager.getHistoricalOps(request, { obj: Runnable -> obj.run() }) { ops: HistoricalOps
+ ->
+ historicalOpsRef.set(ops)
+ latch.countDown()
+ }
+
+ try {
+ latch.await(5, TimeUnit.DAYS)
+ } catch (ignored: InterruptedException) {}
+ val historicalOps = historicalOpsRef.get()
+
+ for (i in 0 until historicalOps.uidCount) {
+ val historicalUidOps = historicalOps.getUidOpsAt(i)
+ val userHandle = UserHandle.getUserHandleForUid(historicalUidOps.uid)
+ for (j in 0 until historicalUidOps.packageCount) {
+ val historicalPackageOps = historicalUidOps.getPackageOpsAt(j)
+ allLightHistoricalPackageOps[Pair(historicalPackageOps.packageName, userHandle)] =
+ LightHistoricalPackageOps(historicalPackageOps, userHandle, opNames)
+ }
+ }
+
+ postValue(allLightHistoricalPackageOps)
+ }
+
+ override fun onOpChanged(op: String?, packageName: String?) {
+ update()
+ }
+
+ override fun onOpActiveChanged(op: String, uid: Int, packageName: String, active: Boolean) {
+ update()
+ }
+
+ override fun onOpNoted(
+ code: String,
+ uid: Int,
+ packageName: String,
+ attributionTag: String?,
+ flags: Int,
+ result: Int
+ ) {
+ update()
+ }
+
+ /** Companion object for [AllLightHistoricalPackageOpsLiveData]. */
+ companion object {
+ // TODO(b/257317733): Add
+ // http://cs/android-internal/frameworks/base/core/java/android/app/AppOpsManager.java?rcl=07feded3186ec30d8da280d85fb68933fc628a31&l=4506 to API.
+ const val HISTORY_FLAG_GET_ATTRIBUTION_CHAINS = 1 shl 2
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightPackageOpsLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightPackageOpsLiveData.kt
new file mode 100644
index 000000000..d0b44f622
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/v31/AllLightPackageOpsLiveData.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.data.v31
+
+import android.app.AppOpsManager
+import android.app.Application
+import android.os.UserHandle
+import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData
+import com.android.permissioncontroller.permission.data.StandardPermGroupNamesLiveData
+import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightPackageOps
+import com.android.permissioncontroller.permission.utils.PermissionMapping
+import kotlinx.coroutines.Job
+
+/**
+ * LiveData class tracking [LightPackageOps] for all packages on the device and for all system
+ * permission groups' ops.
+ *
+ * App ops data is retrieved from [AppOpsManager] and is updated whenever app ops data changes are
+ * heard.
+ */
+class AllLightPackageOpsLiveData(app: Application) :
+ SmartAsyncMediatorLiveData<Map<Pair<String, UserHandle>, LightPackageOps>>(),
+ AppOpsManager.OnOpActiveChangedListener,
+ AppOpsManager.OnOpNotedListener,
+ AppOpsManager.OnOpChangedListener {
+
+ private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
+ private var opNames: Set<String> = getOpNames(StandardPermGroupNamesLiveData.value)
+
+ init {
+ addSource(StandardPermGroupNamesLiveData) {
+ opNames = getOpNames(it)
+ update()
+ }
+ }
+
+ override fun onActive() {
+ super.onActive()
+
+ try {
+ appOpsManager.startWatchingActive(opNames.toTypedArray(), { it.run() }, this)
+ } catch (ignored: IllegalArgumentException) {
+ // Older builds may not support all requested app ops.
+ }
+
+ opNames.forEach {
+ try {
+ appOpsManager.startWatchingMode(it, /* all packages */ null, this)
+ } catch (ignored: IllegalArgumentException) {
+ // Older builds may not support all requested app ops.
+ }
+ }
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+
+ appOpsManager.stopWatchingActive(this)
+ appOpsManager.stopWatchingMode(this)
+ }
+
+ override suspend fun loadDataAndPostValue(job: Job) {
+ val packageOpsList =
+ try {
+ appOpsManager.getPackagesForOps(opNames.toTypedArray())
+ } catch (e: NullPointerException) {
+ // Older builds may not support all requested app ops.
+ emptyList<AppOpsManager.PackageOps>()
+ }
+
+ postValue(
+ packageOpsList.associateBy(
+ { Pair(it.packageName, UserHandle.getUserHandleForUid(it.uid)) },
+ { LightPackageOps(opNames, it) }))
+ }
+
+ override fun onOpChanged(op: String?, packageName: String?) {
+ update()
+ }
+
+ override fun onOpActiveChanged(op: String, uid: Int, packageName: String, active: Boolean) {
+ update()
+ }
+
+ override fun onOpNoted(
+ code: String,
+ uid: Int,
+ packageName: String,
+ attributionTag: String?,
+ flags: Int,
+ result: Int
+ ) {
+ update()
+ }
+
+ /** Returns all op names for all permissions in a list of permission groups. */
+ private fun getOpNames(permissionGroupNames: List<String>?) =
+ permissionGroupNames
+ ?.flatMap { group -> PermissionMapping.getPlatformPermissionNamesOfGroup(group) }
+ ?.mapNotNull { permName -> AppOpsManager.permissionToOp(permName) }
+ ?.toSet()
+ ?: setOf()
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/v34/AppDataSharingUpdatesLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/v34/AppDataSharingUpdatesLiveData.kt
new file mode 100644
index 000000000..8fb04dd96
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/v34/AppDataSharingUpdatesLiveData.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.data.v34
+
+import android.app.Application
+import android.os.Build
+import androidx.annotation.RequiresApi
+import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData
+import com.android.permissioncontroller.permission.model.v34.AppDataSharingUpdate
+import com.android.permissioncontroller.permission.model.v34.AppDataSharingUpdate.Companion.LOCATION_CATEGORY
+import com.android.permissioncontroller.permission.model.v34.AppDataSharingUpdate.Companion.buildUpdateIfSignificantChange
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.DataCategory
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.DataLabel
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.SafetyLabel
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistoryPersistence
+import java.time.Instant
+import java.time.ZonedDateTime
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.Job
+
+/** LiveData for [AppDataSharingUpdate]s. */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class AppDataSharingUpdatesLiveData(val app: Application) :
+ SmartAsyncMediatorLiveData<List<AppDataSharingUpdate>>() {
+
+ override suspend fun loadDataAndPostValue(job: Job) {
+ // TODO(b/261660881): This code serves to enable testing business logic in CTS. Remove when
+ // package add broadcasts can invoke writing of safety labels to persistence.
+ writeTestSafetyLabelsToPersistence()
+
+ val appSafetyLabelDiffsFromPersistence =
+ AppsSafetyLabelHistoryPersistence.getAppSafetyLabelDiffs(
+ Instant.now()
+ .minusSeconds(TimeUnit.DAYS.toSeconds(DATA_SHARING_UPDATE_PERIOD_DAYS)),
+ AppsSafetyLabelHistoryPersistence.getSafetyLabelHistoryFile(app.applicationContext))
+ val updatesFromPersistence =
+ appSafetyLabelDiffsFromPersistence.mapNotNull { it.buildUpdateIfSignificantChange() }
+
+ postValue(updatesFromPersistence)
+ }
+
+ /** Writes safety labels for a test app to safety label history. */
+ private fun writeTestSafetyLabelsToPersistence() {
+ val historyFile =
+ AppsSafetyLabelHistoryPersistence.getSafetyLabelHistoryFile(app.applicationContext)
+ AppsSafetyLabelHistoryPersistence.clear(historyFile)
+ AppsSafetyLabelHistoryPersistence.recordSafetyLabel(
+ SAFETY_LABEL_TEST_PACKAGE_V1, historyFile)
+ AppsSafetyLabelHistoryPersistence.recordSafetyLabel(
+ SAFETY_LABEL_TEST_PACKAGE_V2, historyFile)
+ }
+
+ /** Companion object for [AppDataSharingUpdatesLiveData]. */
+ companion object {
+ private const val PLACEHOLDER_PACKAGE_NAME_1 = "com.android.systemui"
+ private const val PLACEHOLDER_PACKAGE_NAME_2 = "com.android.bluetooth"
+ private const val TEST_PACKAGE_NAME = "android.permission3.cts.usepermission"
+ private const val PLACEHOLDER_SAFETY_LABEL_UPDATES_FLAG =
+ "placeholder_safety_label_updates_flag"
+ private const val DATA_SHARING_UPDATE_PERIOD_DAYS: Long = 30
+
+ private val SAFETY_LABEL_TEST_PACKAGE_V1 =
+ SafetyLabel(
+ AppsSafetyLabelHistory.AppInfo(TEST_PACKAGE_NAME),
+ ZonedDateTime.now().minusDays(5).toInstant(),
+ DataLabel(mapOf(LOCATION_CATEGORY to DataCategory(false))))
+
+ private val SAFETY_LABEL_TEST_PACKAGE_V2 =
+ SafetyLabel(
+ AppsSafetyLabelHistory.AppInfo(TEST_PACKAGE_NAME),
+ ZonedDateTime.now().minusDays(2).toInstant(),
+ DataLabel(mapOf(LOCATION_CATEGORY to DataCategory(true))))
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java b/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java
index 555ba8d51..eed017d0f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/AppPermissionGroup.java
@@ -24,6 +24,9 @@ import static android.app.AppOpsManager.MODE_FOREGROUND;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.healthconnect.HealthPermissions.HEALTH_PERMISSION_GROUP;
+
+import static com.android.permissioncontroller.permission.utils.Utils.isHealthPermissionUiEnabled;
import android.Manifest;
import android.app.ActivityManager;
@@ -337,6 +340,8 @@ public final class AppPermissionGroup implements Comparable<AppPermissionGroup>
& PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0;
final String appOp = PLATFORM_PACKAGE_NAME.equals(requestedPermissionInfo.packageName)
+ || (isHealthPermissionUiEnabled() && HEALTH_PERMISSION_GROUP.equals(
+ requestedPermissionInfo.group))
? AppOpsManager.permissionToOp(requestedPermissionInfo.name) : null;
final boolean appOpAllowed;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightInstallSourceInfo.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightInstallSourceInfo.kt
new file mode 100644
index 000000000..68fdf8739
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightInstallSourceInfo.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.model.livedatatypes
+
+/**
+ * A lighter version of the system's InstallSourceInfo class, containing select information about
+ * the install source.
+ *
+ * @param installingPackageName The package name of the install source (usually the app store)
+ */
+class LightInstallSourceInfo(val installingPackageName: String?) {
+
+ companion object {
+ val UNKNOWN_INSTALL_SOURCE = LightInstallSourceInfo(null)
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt
index 182de1a59..e9cb8700a 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPackageInfo.kt
@@ -18,9 +18,11 @@ package com.android.permissioncontroller.permission.model.livedatatypes
import android.app.Application
import android.content.pm.ApplicationInfo
+import android.content.pm.Attribution
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.UserHandle
+import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.permission.utils.Utils
/**
@@ -46,28 +48,38 @@ data class LightPackageInfo(
val isInstantApp: Boolean,
val enabled: Boolean,
val appFlags: Int,
- val firstInstallTime: Long
+ val firstInstallTime: Long,
+ val areAttributionsUserVisible: Boolean,
+ val attributionTagsToLabels: Map<String, Int>
) {
- constructor(pI: PackageInfo) : this(pI.packageName,
+ constructor(
+ pI: PackageInfo
+ ) : this(
+ pI.packageName,
pI.permissions?.map { perm -> LightPermInfo(perm) } ?: emptyList(),
pI.requestedPermissions?.toList() ?: emptyList(),
pI.requestedPermissionsFlags?.toList() ?: emptyList(),
- pI.applicationInfo.uid, pI.applicationInfo.targetSdkVersion,
- pI.applicationInfo.isInstantApp, pI.applicationInfo.enabled, pI.applicationInfo.flags,
- pI.firstInstallTime)
+ pI.applicationInfo.uid,
+ pI.applicationInfo.targetSdkVersion,
+ pI.applicationInfo.isInstantApp,
+ pI.applicationInfo.enabled,
+ pI.applicationInfo.flags,
+ pI.firstInstallTime,
+ if (SdkLevel.isAtLeastS()) pI.applicationInfo.areAttributionsUserVisible() else false,
+ if (SdkLevel.isAtLeastS()) buildAttributionTagsToLabelsMap(pI.attributions) else emptyMap())
- /**
- * Permissions which are granted according to the [requestedPermissionsFlags]
- */
- val grantedPermissions: List<String> get() {
- val grantedPermissions = mutableListOf<String>()
- for (i in 0 until requestedPermissions.size) {
- if ((requestedPermissionsFlags[i] and PackageInfo.REQUESTED_PERMISSION_GRANTED) != 0) {
- grantedPermissions.add(requestedPermissions[i])
+ /** Permissions which are granted according to the [requestedPermissionsFlags] */
+ val grantedPermissions: List<String>
+ get() {
+ val grantedPermissions = mutableListOf<String>()
+ for (i in 0 until requestedPermissions.size) {
+ if ((requestedPermissionsFlags[i] and PackageInfo.REQUESTED_PERMISSION_GRANTED) !=
+ 0) {
+ grantedPermissions.add(requestedPermissions[i])
+ }
}
+ return grantedPermissions
}
- return grantedPermissions
- }
/**
* Gets the ApplicationInfo for this package from the system. Can be expensive if called too
@@ -75,34 +87,41 @@ data class LightPackageInfo(
*
* @param app The current application, which will be used to get the ApplicationInfo
*
- * @return The ApplicationInfo corresponding to this package, with this UID, or null, if no
- * such package exists
+ * @return The ApplicationInfo corresponding to this package, with this UID, or null, if no such
+ * package exists
*/
fun getApplicationInfo(app: Application): ApplicationInfo? {
try {
val userContext = Utils.getUserContext(app, UserHandle.getUserHandleForUid(uid))
return userContext.packageManager.getApplicationInfo(packageName, 0)
- } catch (e: PackageManager.NameNotFoundException) {
- }
+ } catch (e: PackageManager.NameNotFoundException) {}
return null
}
/**
- * Gets the PackageInfo for this package from the system. Can be expensive if called too
- * often.
+ * Gets the PackageInfo for this package from the system. Can be expensive if called too often.
*
* @param app The current application, which will be used to get the PackageInfo
- *
- * @return The PackageInfo corresponding to this package, with this UID, or null, if no
- * such package exists
+ * @return The PackageInfo corresponding to this package, with this UID, or null, if no such
+ * package exists
*/
fun toPackageInfo(app: Application): PackageInfo? {
try {
val userContext = Utils.getUserContext(app, UserHandle.getUserHandleForUid(uid))
- return userContext.packageManager.getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS)
- } catch (e: PackageManager.NameNotFoundException) {
- }
+ return userContext.packageManager.getPackageInfo(
+ packageName, PackageManager.GET_PERMISSIONS)
+ } catch (e: PackageManager.NameNotFoundException) {}
return null
}
+
+ /** Companion object for [LightPackageInfo]. */
+ companion object {
+ /** Creates a mapping of attribution tag to labels from the provided attributions. */
+ fun buildAttributionTagsToLabelsMap(attributions: Array<Attribution>?): Map<String, Int> {
+ val attributionTagToLabel = mutableMapOf<String, Int>()
+ attributions?.forEach { attributionTagToLabel[it.tag] = it.label }
+
+ return attributionTagToLabel.toMap()
+ }
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermission.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermission.kt
index cb3c6acd3..fd7d82dfc 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermission.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/LightPermission.kt
@@ -16,6 +16,7 @@
package com.android.permissioncontroller.permission.model.livedatatypes
+import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PermissionInfo
import com.android.permissioncontroller.permission.utils.PermissionMapping.isRuntimePlatformPermission
@@ -71,6 +72,20 @@ data class LightPermission(
val isOneTime = flags and PackageManager.FLAG_PERMISSION_ONE_TIME != 0
/** Whether this permission is an instant app permission */
val isInstantPerm = permInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_INSTANT != 0
+ /** Whether this permission is implicitly added to the package */
+ val isImplicit: Boolean by lazy {
+ var implicit = false
+ for ((permName, permFlags) in
+ pkgInfo.requestedPermissions.zip(pkgInfo.requestedPermissionsFlags)) {
+ if (permName == permInfo.name &&
+ (permFlags and PackageInfo.REQUESTED_PERMISSION_IMPLICIT) != 0
+ ) {
+ implicit = true
+ break
+ }
+ }
+ implicit
+ }
/** Whether this permission is a runtime only permission */
val isRuntimeOnly =
permInfo.protectionFlags and PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY != 0
@@ -124,4 +139,4 @@ data class LightPermission(
if (isAutoRevoked) append(", AutoRevoked")
if (isSelectedLocationAccuracy) append(", SelectedLocationAccuracy")
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/SafetyLabelInfo.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/SafetyLabelInfo.kt
new file mode 100644
index 000000000..2c26ad0d4
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/SafetyLabelInfo.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.model.livedatatypes
+
+import com.android.permission.safetylabel.SafetyLabel
+
+/**
+ * A wrapping class for [SafetyLabel] class that includes the install source package name
+ *
+ * @param safetyLabel The resulting [SafetyLabel], or null if none found
+ * @param installSourcePackageName The package name of the install source for the APK and safety
+ * label(usually the app store)
+ */
+class SafetyLabelInfo(val safetyLabel: SafetyLabel?, val installSourcePackageName: String?) {
+
+ companion object {
+ /** Default definition of unavailable or no safety label found */
+ val UNAVAILABLE = SafetyLabelInfo(null, null)
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt
new file mode 100644
index 000000000..25c888caa
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.model.livedatatypes.v31
+
+import android.app.AppOpsManager.AttributedHistoricalOps
+import android.app.AppOpsManager.AttributedOpEntry
+import android.app.AppOpsManager.HistoricalOp
+import android.app.AppOpsManager.HistoricalPackageOps
+import android.app.AppOpsManager.OPSTR_READ_WRITE_HEALTH_DATA
+import android.app.AppOpsManager.OP_FLAG_SELF
+import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED
+import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY
+import android.app.AppOpsManager.OpEventProxyInfo
+import android.app.AppOpsManager.opToPermission
+import android.healthconnect.HealthPermissions.HEALTH_PERMISSION_GROUP
+import android.os.UserHandle
+import com.android.permissioncontroller.permission.utils.PermissionMapping.getGroupOfPlatformPermission
+
+/**
+ * Light version of [HistoricalPackageOps] class, tracking the last permission access for system
+ * permission groups.
+ */
+data class LightHistoricalPackageOps(
+ /** Name of the package. */
+ val packageName: String,
+ /** [UserHandle] running the package. */
+ val userHandle: UserHandle,
+ /**
+ * Data about permission accesses, one [AppPermissionDiscreteAccesses] for each permission
+ * group.
+ */
+ // TODO(b/262042582): Consider removing this field and using attributed accesses aggregated over
+ // attribution tags instead.
+ val appPermissionDiscreteAccesses: List<AppPermissionDiscreteAccesses>,
+ /**
+ * Attributed data about permission accesses, one [AttributedAppPermissionDiscreteAccesses] for
+ * each permission group.
+ */
+ val attributedAppPermissionDiscreteAccesses: List<AttributedAppPermissionDiscreteAccesses>
+) {
+ constructor(
+ historicalPackageOps: HistoricalPackageOps,
+ userHandle: UserHandle,
+ opNames: Set<String>
+ ) : this(
+ historicalPackageOps.packageName,
+ userHandle,
+ historicalPackageOps.getAppPermissionDiscreteAccesses(userHandle, opNames),
+ historicalPackageOps.getAttributedAppPermissionDiscreteAccesses(userHandle, opNames),
+ )
+
+ /** Companion object for [LightHistoricalPackageOps]. */
+ companion object {
+ /** String to represent the absence of a permission group. */
+ private const val NO_PERM_GROUP = "no_perm_group"
+ private const val DISCRETE_ACCESS_OP_FLAGS =
+ OP_FLAG_SELF or OP_FLAG_TRUSTED_PROXIED or OP_FLAG_TRUSTED_PROXY
+
+ /**
+ * Creates a list of [AppPermissionDiscreteAccesses] for the provided package, user and ops.
+ */
+ private fun HistoricalPackageOps.getAppPermissionDiscreteAccesses(
+ userHandle: UserHandle,
+ opNames: Set<String>
+ ): List<AppPermissionDiscreteAccesses> {
+ val permissionsToOpNames = partitionOpsByPermission(opNames)
+ val appPermissionDiscreteAccesses = mutableListOf<AppPermissionDiscreteAccesses>()
+ for (permissionToOpNames in permissionsToOpNames.entries) {
+ this.getDiscreteAccesses(permissionToOpNames.value)?.let {
+ appPermissionDiscreteAccesses.add(
+ AppPermissionDiscreteAccesses(
+ AppPermissionId(packageName, userHandle, permissionToOpNames.key), it))
+ }
+ }
+
+ return appPermissionDiscreteAccesses
+ }
+
+ /**
+ * Creates a list of [AttributedAppPermissionDiscreteAccesses] for the provided package,
+ * user and ops.
+ */
+ private fun HistoricalPackageOps.getAttributedAppPermissionDiscreteAccesses(
+ userHandle: UserHandle,
+ opNames: Set<String>
+ ): List<AttributedAppPermissionDiscreteAccesses> {
+ val permissionsToOpNames = partitionOpsByPermission(opNames)
+ val attributedAppPermissionDiscreteAccesses =
+ mutableMapOf<AppPermissionId, MutableMap<String, List<DiscreteAccess>>>()
+
+ val attributedHistoricalOpsList = mutableListOf<AttributedHistoricalOps>()
+ for (i in 0 until attributedOpsCount) {
+ attributedHistoricalOpsList.add(getAttributedOpsAt(i))
+ }
+
+ for (permissionToOpNames in permissionsToOpNames.entries) {
+ attributedHistoricalOpsList.forEach { attributedHistoricalOps ->
+ if (attributedHistoricalOps.tag != null) {
+ attributedHistoricalOps
+ .getDiscreteAccesses(permissionToOpNames.value)
+ ?.let { discAccessData ->
+ val appPermissionId =
+ AppPermissionId(
+ packageName, userHandle, permissionToOpNames.key)
+ if (!attributedAppPermissionDiscreteAccesses.containsKey(
+ appPermissionId)) {
+ attributedAppPermissionDiscreteAccesses[appPermissionId] =
+ mutableMapOf()
+ }
+ attributedAppPermissionDiscreteAccesses[appPermissionId]?.put(
+ attributedHistoricalOps.tag!!, discAccessData)
+ }
+ }
+ }
+ }
+
+ return attributedAppPermissionDiscreteAccesses.map {
+ AttributedAppPermissionDiscreteAccesses(it.key, it.value)
+ }
+ }
+
+ /**
+ * Retrieves all discrete accesses for the provided op names, if any.
+ *
+ * Returns null if there are no accesses.
+ */
+ private fun HistoricalPackageOps.getDiscreteAccesses(
+ opNames: List<String>
+ ): List<DiscreteAccess>? {
+ if (opCount == 0) {
+ return null
+ }
+
+ val historicalOps = mutableListOf<HistoricalOp>()
+ for (opName in opNames) {
+ getOp(opName)?.let { historicalOps.add(it) }
+ }
+
+ val discreteAccessList = mutableListOf<DiscreteAccess>()
+ historicalOps.forEach {
+ for (i in 0 until it.discreteAccessCount) {
+ val opEntry: AttributedOpEntry = it.getDiscreteAccessAt(i)
+ discreteAccessList.add(
+ DiscreteAccess(
+ opEntry.getLastAccessTime(DISCRETE_ACCESS_OP_FLAGS),
+ opEntry.getLastDuration(DISCRETE_ACCESS_OP_FLAGS),
+ opEntry.getLastProxyInfo(DISCRETE_ACCESS_OP_FLAGS)))
+ }
+ }
+
+ if (discreteAccessList.isEmpty()) {
+ return null
+ }
+ return discreteAccessList.sortedWith(compareBy { -it.accessTimeMs })
+ }
+
+ /**
+ * Retrieves all discrete accesses for the provided op names, if any.
+ *
+ * Returns null if there are no accesses.
+ */
+ private fun AttributedHistoricalOps.getDiscreteAccesses(
+ opNames: List<String>
+ ): List<DiscreteAccess>? {
+ if (opCount == 0) {
+ return null
+ }
+
+ val historicalOps = mutableListOf<HistoricalOp>()
+ for (opName in opNames) {
+ getOp(opName)?.let { historicalOps.add(it) }
+ }
+
+ val discreteAccessList = mutableListOf<DiscreteAccess>()
+ historicalOps.forEach {
+ for (i in 0 until it.discreteAccessCount) {
+ val attributedOpEntry: AttributedOpEntry = it.getDiscreteAccessAt(i)
+ discreteAccessList.add(
+ DiscreteAccess(
+ attributedOpEntry.getLastAccessTime(DISCRETE_ACCESS_OP_FLAGS),
+ attributedOpEntry.getLastDuration(DISCRETE_ACCESS_OP_FLAGS),
+ attributedOpEntry.getLastProxyInfo(DISCRETE_ACCESS_OP_FLAGS)))
+ }
+ }
+
+ if (discreteAccessList.isEmpty()) {
+ return null
+ }
+ return discreteAccessList.sortedWith(compareBy { -it.accessTimeMs })
+ }
+
+ /** Returns the permission group for the permission that the provided op backs, if any. */
+ private fun getPermissionGroupForOp(opName: String): String {
+ // The OPSTR_READ_WRITE_HEALTH_DATA is a special case as unlike other ops, it does not
+ // map to a single permission. However it is safe to retrieve a permission group for it,
+ // as all permissions it maps to, map to the same permission group
+ // HEALTH_PERMISSION_GROUP.
+ if (opName == OPSTR_READ_WRITE_HEALTH_DATA) {
+ return HEALTH_PERMISSION_GROUP
+ }
+
+ return opToPermission(opName)?.let { getGroupOfPlatformPermission(it) } ?: NO_PERM_GROUP
+ }
+
+ private fun partitionOpsByPermission(ops: Set<String>): Map<String, List<String>> =
+ ops.groupBy { getPermissionGroupForOp(it) }.filter { it.key != NO_PERM_GROUP }
+ }
+
+ // TODO(b/257317733): Move out of this class so this class can be shared across both the main
+ // and the timeline permission usage UI.
+ /** Identifier for an app permission group combination. */
+ data class AppPermissionId(
+ val packageName: String,
+ val userHandle: UserHandle,
+ val permGroup: String,
+ )
+
+ /**
+ * Data class representing permissions accesses for a particular permission group by a
+ * particular package and user.
+ */
+ data class AppPermissionDiscreteAccesses(
+ val appPermissionId: AppPermissionId,
+ val discreteAccesses: List<DiscreteAccess>
+ )
+
+ /**
+ * Data class representing permissions accesses for a particular permission group by a
+ * particular package and user, partitioned by attribution tag.
+ */
+ data class AttributedAppPermissionDiscreteAccesses(
+ val appPermissionId: AppPermissionId,
+ val attributedDiscreteAccesses: Map<String, List<DiscreteAccess>>
+ )
+
+ /** Data class representing a discrete permission access. */
+ data class DiscreteAccess(
+ val accessTimeMs: Long,
+ val accessDurationMs: Long,
+ val proxy: OpEventProxyInfo?
+ )
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt
new file mode 100644
index 000000000..87426eb35
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.model.livedatatypes.v31
+
+import android.app.AppOpsManager.OPSTR_READ_WRITE_HEALTH_DATA
+import android.app.AppOpsManager.OP_FLAG_SELF
+import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED
+import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY
+import android.app.AppOpsManager.PackageOps
+import android.app.AppOpsManager.opToPermission
+import android.healthconnect.HealthPermissions.HEALTH_PERMISSION_GROUP
+import android.os.UserHandle
+import com.android.permissioncontroller.permission.utils.PermissionMapping.getGroupOfPlatformPermission
+
+/**
+ * Light version of [PackageOps] class, tracking the last permission access for system permission
+ * groups.
+ */
+data class LightPackageOps(
+ /** Name of the package. */
+ val packageName: String,
+ /** [UserHandle] running the package. */
+ val userHandle: UserHandle,
+ /**
+ * Mapping of permission group name to the last access time of any op backing a permission in
+ * the group.
+ */
+ val lastPermissionGroupAccessTimesMs: Map<String, Long>
+) {
+ constructor(
+ ops: Set<String>,
+ packageOps: PackageOps
+ ) : this(
+ packageOps.packageName,
+ UserHandle.getUserHandleForUid(packageOps.uid),
+ createLastPermissionGroupAccessTimesMap(ops, packageOps))
+
+ /** Companion object for [LightPackageOps]. */
+ companion object {
+ /** Flags to use for querying an op's last access time. */
+ private const val OPS_LAST_ACCESS_FLAGS =
+ OP_FLAG_SELF or OP_FLAG_TRUSTED_PROXIED or OP_FLAG_TRUSTED_PROXY
+
+ /** Creates a mapping from permission group to the last time it was accessed. */
+ private fun createLastPermissionGroupAccessTimesMap(
+ opNames: Set<String>,
+ packageOps: PackageOps
+ ): Map<String, Long> {
+ val lastAccessTimeMs = mutableMapOf<String, Long>()
+ // Add keys for all permissions groups covered by the provided ops, regardless of
+ // whether they have been observed recently.
+ for (permissionGroup in opNames.mapNotNull { getPermissionGroupForOp(it) }.toSet()) {
+ lastAccessTimeMs[permissionGroup] = -1
+ }
+
+ for (opEntry in packageOps.ops) {
+ val permissionGroupOfOp = getPermissionGroupForOp(opEntry.opStr) ?: continue
+ lastAccessTimeMs[permissionGroupOfOp] =
+ maxOf(
+ lastAccessTimeMs[permissionGroupOfOp] ?: -1,
+ opEntry.getLastAccessTime(OPS_LAST_ACCESS_FLAGS))
+ }
+
+ return lastAccessTimeMs
+ }
+
+ /** Returns the permission group for the permission that the provided op backs, if any. */
+ private fun getPermissionGroupForOp(opName: String): String? {
+ // The OPSTR_READ_WRITE_HEALTH_DATA is a special case as unlike other ops, it does not
+ // map to a single permission. However it is safe to retrieve a permission group for it,
+ // as all permissions it maps to, map to the same permission group
+ // HEALTH_PERMISSION_GROUP.
+ if (opName == OPSTR_READ_WRITE_HEALTH_DATA) {
+ return HEALTH_PERMISSION_GROUP
+ }
+
+ return opToPermission(opName)?.let { getGroupOfPlatformPermission(it) }
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/v31/AppPermissionUsage.java b/PermissionController/src/com/android/permissioncontroller/permission/model/v31/AppPermissionUsage.java
index 8622ef3ab..b7cddace2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/v31/AppPermissionUsage.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/v31/AppPermissionUsage.java
@@ -125,16 +125,6 @@ public final class AppPermissionUsage {
return lastAccessTime;
}
- public long getAccessCount() {
- long accessCount = 0;
- final int permissionCount = mGroupUsages.size();
- for (int i = 0; i < permissionCount; i++) {
- final GroupUsage permission = mGroupUsages.get(i);
- accessCount += permission.getAccessCount();
- }
- return accessCount;
- }
-
public @NonNull List<GroupUsage> getGroupUsages() {
return mGroupUsages;
}
@@ -163,62 +153,6 @@ public final class AppPermissionUsage {
return lastAccessAggregate((op) -> op.getLastAccessTime(PRIVACY_HUB_FLAGS));
}
- public long getLastAccessForegroundTime() {
- if (mLastUsage == null) {
- return 0;
- }
-
- return lastAccessAggregate((op) -> op.getLastAccessForegroundTime(PRIVACY_HUB_FLAGS));
- }
-
- public long getLastAccessBackgroundTime() {
- if (mLastUsage == null) {
- return 0;
- }
-
- return lastAccessAggregate((op) -> op.getLastAccessBackgroundTime(PRIVACY_HUB_FLAGS));
- }
-
- public long getForegroundAccessCount() {
- if (mHistoricalUsage == null) {
- return 0;
- }
-
- return extractAggregate((HistoricalOp op)
- -> op.getForegroundAccessCount(PRIVACY_HUB_FLAGS));
- }
-
- public long getBackgroundAccessCount() {
- if (mHistoricalUsage == null) {
- return 0;
- }
-
- return extractAggregate((HistoricalOp op)
- -> op.getBackgroundAccessCount(PRIVACY_HUB_FLAGS));
- }
-
- public long getAccessCount() {
- if (mHistoricalUsage == null) {
- return 0;
- }
-
- return extractAggregate((HistoricalOp op) ->
- op.getForegroundAccessCount(PRIVACY_HUB_FLAGS)
- + op.getBackgroundAccessCount(PRIVACY_HUB_FLAGS)
- );
- }
-
- /**
- * Get the last access duration.
- */
- public long getLastAccessDuration() {
- if (mLastUsage == null) {
- return 0;
- }
- return lastAccessAggregate(
- (op) -> op.getLastDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
- }
-
/**
* Get the access duration.
*/
@@ -508,11 +442,12 @@ public final class AppPermissionUsage {
}
AttributionLabelledGroupUsage build() {
+ ArrayList<String> attributionTagsList = new ArrayList<>();
+ attributionTagsList.addAll(mAttributionTags);
return new AttributionLabelledGroupUsage(mLabel,
mAppPermissionGroup,
- new ArrayList<String>() {{
- addAll(mAttributionTags);
- }}, mDiscreteAccessTime);
+ attributionTagsList,
+ mDiscreteAccessTime);
}
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/v31/PermissionUsages.java b/PermissionController/src/com/android/permissioncontroller/permission/model/v31/PermissionUsages.java
index a8044796a..6e37973ed 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/v31/PermissionUsages.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/v31/PermissionUsages.java
@@ -20,8 +20,10 @@ import static android.Manifest.permission.CAMERA;
import static android.Manifest.permission.RECORD_AUDIO;
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_CAMERA;
import static android.app.AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE;
+import static android.healthconnect.HealthPermissions.HEALTH_PERMISSION_GROUP;
import static com.android.permissioncontroller.Constants.OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+import static com.android.permissioncontroller.permission.utils.Utils.isHealthPermissionUiEnabled;
import android.app.AppOpsManager;
import android.app.AppOpsManager.HistoricalOps;
@@ -233,7 +235,9 @@ public final class PermissionUsages implements LoaderCallbacks<List<AppPermissio
for (int groupIdx = 0; groupIdx < groupCount; groupIdx++) {
final PermissionGroup group = groups.get(groupIdx);
// Filter out third party permissions
- if (!group.getDeclaringPackage().equals(Utils.OS_PKG)) {
+ if (!(group.getDeclaringPackage().equals(Utils.OS_PKG)
+ || (isHealthPermissionUiEnabled() && HEALTH_PERMISSION_GROUP.equals(
+ group.getName())))) {
continue;
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/v34/AppDataSharingUpdate.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/v34/AppDataSharingUpdate.kt
new file mode 100644
index 000000000..9288ccc4c
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/v34/AppDataSharingUpdate.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.model.v34
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import com.android.permissioncontroller.permission.model.v34.DataSharingUpdateType.ADDS_ADVERTISING_PURPOSE
+import com.android.permissioncontroller.permission.model.v34.DataSharingUpdateType.ADDS_SHARING_WITHOUT_ADVERTISING_PURPOSE
+import com.android.permissioncontroller.permission.model.v34.DataSharingUpdateType.ADDS_SHARING_WITH_ADVERTISING_PURPOSE
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.AppSafetyLabelDiff
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.SafetyLabel
+
+/**
+ * Class representing a significant update in an app's data sharing policy from its safety label.
+ *
+ * Note that safety labels are part of package information, and therefore the safety label
+ * information applies to apps for all users that have the app installed.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+data class AppDataSharingUpdate(
+ /** Package name for the app with the data sharing update. */
+ val packageName: String,
+ /** How data sharing for each category has changed. */
+ val categorySharingUpdates: Map<String, DataSharingUpdateType>
+) {
+
+ /** Companion object for [AppDataSharingUpdate]. */
+ companion object {
+ /**
+ * Builds and returns an [AppDataSharingUpdate] from an [AppSafetyLabelDiff], if the change
+ * in safety labels is significant, else returns null.
+ */
+ fun AppSafetyLabelDiff.buildUpdateIfSignificantChange(): AppDataSharingUpdate? {
+ // In Android U, only updates for the location data category will be displayed in
+ // the UI.
+ val updates = getUpdatesForCategories(listOf(LOCATION_CATEGORY))
+
+ return if (updates.isEmpty()) null
+ else AppDataSharingUpdate(safetyLabelBefore.appInfo.packageName, updates)
+ }
+
+ private fun AppSafetyLabelDiff.getUpdatesForCategories(
+ categories: List<String>
+ ): Map<String, DataSharingUpdateType> {
+ val categoryUpdateMap = mutableMapOf<String, DataSharingUpdateType>()
+
+ for (category in categories) {
+ var categoryUpdateType: DataSharingUpdateType? = null
+
+ val beforeSharesData = safetyLabelBefore.sharesData(category)
+ val beforeSharesDataForAds = safetyLabelBefore.sharesDataForAdsPurpose(category)
+ val afterSharesData = safetyLabelAfter.sharesData(category)
+ val afterSharesDataForAds = safetyLabelAfter.sharesDataForAdsPurpose(category)
+
+ categoryUpdateType =
+ when {
+ !beforeSharesData && afterSharesDataForAds ->
+ ADDS_SHARING_WITH_ADVERTISING_PURPOSE
+ !beforeSharesData && afterSharesData && !afterSharesDataForAds ->
+ ADDS_SHARING_WITHOUT_ADVERTISING_PURPOSE
+ beforeSharesData && !beforeSharesDataForAds && afterSharesDataForAds ->
+ ADDS_ADVERTISING_PURPOSE
+ else -> null
+ }
+
+ if (categoryUpdateType == null) {
+ continue
+ }
+
+ categoryUpdateMap[category] = categoryUpdateType
+ }
+
+ return categoryUpdateMap
+ }
+
+ // TODO(b/263153040): Use categories from safety label library.
+ const val LOCATION_CATEGORY = "location"
+
+ private fun SafetyLabel.sharesData(category: String) =
+ dataLabel.dataShared.containsKey(category)
+
+ private fun SafetyLabel.sharesDataForAdsPurpose(category: String) =
+ dataLabel.dataShared[category]?.containsAdvertisingPurpose ?: false
+ }
+}
+
+/**
+ * Different ways in which data sharing can be significantly updated for a particular data category.
+ */
+enum class DataSharingUpdateType {
+ ADDS_ADVERTISING_PURPOSE,
+ ADDS_SHARING_WITHOUT_ADVERTISING_PURPOSE,
+ ADDS_SHARING_WITH_ADVERTISING_PURPOSE
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java b/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java
index e0e124f54..6d58d96d2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/LocationAccessCheck.java
@@ -160,7 +160,7 @@ import java.util.stream.Collectors;
*/
public class LocationAccessCheck {
private static final String LOG_TAG = LocationAccessCheck.class.getSimpleName();
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final long DEFAULT_RENOTIFY_DURATION_MILLIS = DAYS.toMillis(90);
private static final String ISSUE_ID_PREFIX = "bg_location_";
private static final String ISSUE_TYPE_ID = "bg_location_privacy_issue";
@@ -410,6 +410,7 @@ public class LocationAccessCheck {
private void addLocationNotificationIfNeeded(@NonNull JobParameters params,
@NonNull LocationAccessCheckJobService service) {
if (!checkLocationAccessCheckEnabledAndUpdateEnabledTime()) {
+ Log.v(LOG_TAG, "LocationAccessCheck feature is not enabled.");
service.jobFinished(params, false);
return;
}
@@ -419,11 +420,13 @@ public class LocationAccessCheck {
if (currentTimeMillis() - mSharedPrefs.getLong(
KEY_LAST_LOCATION_ACCESS_NOTIFICATION_SHOWN, 0)
< getInBetweenNotificationsMillis()) {
+ Log.v(LOG_TAG, "location notification interval is not enough.");
service.jobFinished(params, false);
return;
}
if (getCurrentlyShownNotificationLocked() != null) {
+ Log.v(LOG_TAG, "already location notification exist.");
service.jobFinished(params, false);
return;
}
@@ -437,6 +440,7 @@ public class LocationAccessCheck {
} finally {
synchronized (sLock) {
service.mAddLocationNotificationIfNeededTask = null;
+ Log.v(LOG_TAG, "LocationAccessCheck privacy job marked complete.");
}
}
}
@@ -447,6 +451,10 @@ public class LocationAccessCheck {
synchronized (sLock) {
List<UserPackage> packages = getLocationUsersLocked(ops);
ArraySet<UserPackage> alreadyNotifiedPackages = loadAlreadyNotifiedPackagesLocked();
+ if (DEBUG) {
+ Log.v(LOG_TAG, "location packages: " + packages);
+ Log.v(LOG_TAG, "already notified packages: " + alreadyNotifiedPackages);
+ }
throwInterruptedExceptionIfTaskIsCanceled();
// Send these issues to safety center
if (isSafetyCenterBgLocationReminderEnabled()) {
@@ -462,6 +470,9 @@ public class LocationAccessCheck {
throwInterruptedExceptionIfTaskIsCanceled();
if (packages.isEmpty()) {
+ if (DEBUG) {
+ Log.v(LOG_TAG, "No package found to send a notification");
+ }
return;
}
@@ -1152,6 +1163,7 @@ public class LocationAccessCheck {
@Override
public void onCreate() {
+ Log.v(LOG_TAG, "LocationAccessCheck privacy job is created");
super.onCreate();
mLocationAccessCheck = new LocationAccessCheck(this, () -> {
synchronized (sLock) {
@@ -1170,8 +1182,10 @@ public class LocationAccessCheck {
*/
@Override
public boolean onStartJob(JobParameters params) {
+ Log.v(LOG_TAG, "LocationAccessCheck privacy job is started");
synchronized (LocationAccessCheck.sLock) {
if (mAddLocationNotificationIfNeededTask != null) {
+ Log.v(LOG_TAG, "LocationAccessCheck old job not completed yet.");
return false;
}
@@ -1192,6 +1206,7 @@ public class LocationAccessCheck {
*/
@Override
public boolean onStopJob(JobParameters params) {
+ Log.v(LOG_TAG, "LocationAccessCheck privacy source onStopJob called.");
AddLocationNotificationIfNeededTask task;
synchronized (sLock) {
if (mAddLocationNotificationIfNeededTask == null) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java b/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java
index 2a090f662..5e03e721c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java
@@ -64,8 +64,8 @@ import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.PermissionMapping;
import com.android.permissioncontroller.permission.utils.UserSensitiveFlagsUtils;
import com.android.permissioncontroller.permission.utils.Utils;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlSerializer;
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 af151320f..b4a0d73ea 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.permission.service.v33
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
+import android.graphics.drawable.Icon
import android.os.IBinder
import android.provider.DeviceConfig
import android.safetycenter.SafetyCenterManager
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt
new file mode 100644
index 000000000..738c152be
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/v34/SafetyLabelChangesJobService.kt
@@ -0,0 +1,211 @@
+/*
+ * 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.service.v34
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.job.JobInfo
+import android.app.job.JobParameters
+import android.app.job.JobScheduler
+import android.app.job.JobService
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_BOOT_COMPLETED
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.core.app.NotificationCompat
+import com.android.permissioncontroller.Constants.PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID
+import com.android.permissioncontroller.Constants.PERMISSION_REMINDER_CHANNEL_ID
+import com.android.permissioncontroller.Constants.SAFETY_LABEL_CHANGES_JOB_ID
+import com.android.permissioncontroller.Constants.SAFETY_LABEL_CHANGES_NOTIFICATION_ID
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.Utils.getSystemServiceSafe
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+
+/**
+ * Runs a monthly job that performs Safety Labels-related tasks, e.g., data policy changes
+ * notification, hygiene, etc.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class SafetyLabelChangesJobService : JobService() {
+ class Receiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (!KotlinUtils.isSafetyLabelChangeNotificationsEnabled()) {
+ return
+ }
+ if (intent.action != ACTION_BOOT_COMPLETED) {
+ return
+ }
+ Log.i(LOG_TAG, "Received broadcast")
+ schedulePeriodicJob(context)
+ }
+ }
+
+ /**
+ * Called twice each interval, first for the periodic job
+ * [PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID], then for the main job [SAFETY_LABEL_CHANGES_JOB_ID].
+ */
+ override fun onStartJob(params: JobParameters): Boolean {
+ if (!KotlinUtils.isSafetyLabelChangeNotificationsEnabled()) {
+ return false
+ }
+ when (params.jobId) {
+ PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID -> scheduleMainJobWithDelay()
+ SAFETY_LABEL_CHANGES_JOB_ID -> {
+ dispatchMainJobTask(params)
+ return true
+ }
+ else -> Log.w(LOG_TAG, "Unexpected job ID")
+ }
+ return false
+ }
+
+ private fun dispatchMainJobTask(params: JobParameters) {
+ GlobalScope.launch(Dispatchers.Default) {
+ try {
+ Log.i(LOG_TAG, "Job started")
+ runMainJob()
+ Log.i(LOG_TAG, "Job finished successfully")
+ } catch (e: Throwable) {
+ Log.e(LOG_TAG, "Job failed", e)
+ throw e
+ } finally {
+ jobFinished(params, false)
+ }
+ }
+ }
+
+ private fun runMainJob() {
+ postSafetyLabelChangedNotification()
+ }
+
+ private fun postSafetyLabelChangedNotification() {
+ if (hasDataSharingChanged()) {
+ Log.i(LOG_TAG, "Showing notification: data sharing has changed")
+ showNotification()
+ } else {
+ Log.i(LOG_TAG, "Not showing notification: data sharing has not changed")
+ }
+ }
+
+ override fun onStopJob(params: JobParameters?): Boolean = true
+
+ private fun hasDataSharingChanged(): Boolean {
+ // TODO(b/261663886): Check whether data sharing has changed
+ return true
+ }
+
+ private fun showNotification() {
+ val context = PermissionControllerApplication.get() as Context
+ val notificationManager = getSystemServiceSafe(context, NotificationManager::class.java)
+
+ createNotificationChannel(context, notificationManager)
+
+ val notification =
+ NotificationCompat.Builder(context, PERMISSION_REMINDER_CHANNEL_ID)
+ .setSmallIcon(R.drawable.ic_info)
+ .setContentTitle(
+ context.getString(R.string.safety_label_changes_notification_title))
+ .setContentText(context.getString(R.string.safety_label_changes_notification_desc))
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+ .setLocalOnly(true)
+ .setAutoCancel(true)
+ .setSilent(true)
+ .build()
+
+ notificationManager.notify(SAFETY_LABEL_CHANGES_NOTIFICATION_ID, notification)
+ }
+
+ private fun createNotificationChannel(
+ context: Context,
+ notificationManager: NotificationManager
+ ) {
+ val notificationChannel =
+ NotificationChannel(
+ PERMISSION_REMINDER_CHANNEL_ID,
+ context.getString(R.string.permission_reminders),
+ NotificationManager.IMPORTANCE_LOW)
+
+ notificationManager.createNotificationChannel(notificationChannel)
+ }
+
+ companion object {
+ private val LOG_TAG = SafetyLabelChangesJobService::class.java.simpleName
+
+ private fun schedulePeriodicJob(context: Context) {
+ try {
+ val jobScheduler = getSystemServiceSafe(context, JobScheduler::class.java)
+
+ if (jobScheduler.getPendingJob(PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID) != null) {
+ Log.i(LOG_TAG, "Not scheduling periodic job: already scheduled")
+ return
+ }
+
+ Log.i(LOG_TAG, "Scheduling periodic job")
+ val job =
+ JobInfo.Builder(
+ PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID,
+ ComponentName(context, SafetyLabelChangesJobService::class.java))
+ .setPersisted(true)
+ .setRequiresDeviceIdle(
+ KotlinUtils.runSafetyLabelChangesJobOnlyWhenDeviceIdle())
+ .setPeriodic(KotlinUtils.getSafetyLabelChangesJobIntervalMillis())
+ .build()
+ jobScheduler.schedule(job)
+ Log.i(LOG_TAG, "Periodic job scheduled successfully")
+ } catch (e: Throwable) {
+ Log.e(LOG_TAG, "Failed to schedule periodic job", e)
+ throw e
+ }
+ }
+
+ private fun scheduleMainJobWithDelay() {
+ try {
+ val context = PermissionControllerApplication.get() as Context
+ val jobScheduler = getSystemServiceSafe(context, JobScheduler::class.java)
+
+ if (jobScheduler.getPendingJob(SAFETY_LABEL_CHANGES_JOB_ID) != null) {
+ Log.i(LOG_TAG, "Not scheduling job: already scheduled")
+ return
+ }
+
+ Log.i(LOG_TAG, "Scheduling job")
+ val job =
+ JobInfo.Builder(
+ SAFETY_LABEL_CHANGES_JOB_ID,
+ ComponentName(context, SafetyLabelChangesJobService::class.java))
+ .setPersisted(true)
+ .setRequiresDeviceIdle(
+ KotlinUtils.runSafetyLabelChangesJobOnlyWhenDeviceIdle())
+ .setMinimumLatency(KotlinUtils.getSafetyLabelChangesJobDelayMillis())
+ .build()
+ jobScheduler.schedule(job)
+ Log.i(LOG_TAG, "Job scheduled successfully")
+ } catch (e: Throwable) {
+ Log.e(LOG_TAG, "Failed to schedule job", e)
+ throw e
+ }
+ }
+ }
+}
diff --git a/service/java/com/android/access/AccessCheckingService.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/v34/package-info.java
index e61a245fd..0625654c0 100644
--- a/service/java/com/android/access/AccessCheckingService.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/service/v34/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,11 +14,5 @@
* limitations under the License.
*/
-package com.android.access
-
-import androidx.annotation.Keep
-
-@Keep
-class AccessCheckingService {
- var list = mutableListOf<Any>()
-}
+@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+package com.android.permissioncontroller.permission.service.v34;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
index b9c4a07d9..393ed8194 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java
@@ -18,6 +18,8 @@ package com.android.permissioncontroller.permission.ui;
import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission_group.READ_MEDIA_VISUAL;
+import static android.healthconnect.HealthPermissions.HEALTH_PERMISSION_GROUP;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED;
@@ -26,7 +28,10 @@ import static com.android.permissioncontroller.permission.ui.GrantPermissionsVie
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME;
+import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_USER_SELECTED;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.LINKED_TO_SETTINGS;
+import static com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.APP_PERMISSION_REQUEST_CODE;
+import static com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.PHOTO_PICKER_REQUEST_CODE;
import static com.android.permissioncontroller.permission.utils.Utils.getRequestMessage;
import android.Manifest;
@@ -36,6 +41,7 @@ import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
+import android.icu.lang.UCharacter;
import android.os.Bundle;
import android.os.Process;
import android.text.Annotation;
@@ -59,9 +65,9 @@ import com.android.modules.utils.build.SdkLevel;
import com.android.permissioncontroller.DeviceUtils;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.ui.auto.GrantPermissionsAutoViewHandler;
-import com.android.permissioncontroller.permission.ui.model.v31.GrantPermissionsViewModel;
-import com.android.permissioncontroller.permission.ui.model.v31.GrantPermissionsViewModel.RequestInfo;
-import com.android.permissioncontroller.permission.ui.model.v31.GrantPermissionsViewModelFactory;
+import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel;
+import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModel.RequestInfo;
+import com.android.permissioncontroller.permission.ui.model.GrantPermissionsViewModelFactory;
import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.Utils;
@@ -71,6 +77,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Random;
/**
@@ -85,7 +92,7 @@ public class GrantPermissionsActivity extends SettingsActivity
+ "_REQUEST_ID";
public static final String ANNOTATION_ID = "link";
- public static final int NEXT_BUTTON = 11;
+ public static final int NEXT_BUTTON = 15;
public static final int ALLOW_BUTTON = 0;
public static final int ALLOW_ALWAYS_BUTTON = 1; // Used in auto
public static final int ALLOW_FOREGROUND_BUTTON = 2;
@@ -97,6 +104,10 @@ public class GrantPermissionsActivity extends SettingsActivity
public static final int NO_UPGRADE_OT_BUTTON = 8; // one-time
public static final int NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON = 9; // one-time
public static final int LINK_TO_SETTINGS = 10;
+ public static final int ALLOW_ALL_PHOTOS_BUTTON = 11;
+ public static final int ALLOW_SELECTED_PHOTOS_BUTTON = 12;
+ public static final int ALLOW_MORE_SELECTED_PHOTOS_BUTTON = 13;
+ public static final int DONT_ALLOW_MORE_SELECTED_PHOTOS_BUTTON = 14;
public static final int NEXT_LOCATION_DIALOG = 6;
public static final int LOCATION_ACCURACY_LAYOUT = 0;
@@ -106,13 +117,11 @@ public class GrantPermissionsActivity extends SettingsActivity
public static final int DIALOG_WITH_FINE_LOCATION_ONLY = 4;
public static final int DIALOG_WITH_COARSE_LOCATION_ONLY = 5;
- public static final Map<String, Integer> PERMISSION_TO_BIT_SHIFT =
- new HashMap<String, Integer>() {{
- put(ACCESS_COARSE_LOCATION, 0);
- put(ACCESS_FINE_LOCATION, 1);
- }};
+ public static final Map<String, Integer> PERMISSION_TO_BIT_SHIFT = Map.of(
+ ACCESS_COARSE_LOCATION, 0,
+ ACCESS_FINE_LOCATION, 1);
- private static final int APP_PERMISSION_REQUEST_CODE = 1;
+ public static final String INTENT_PHOTOS_SELECTED = "intent_extra_result";
/**
* A map of the currently shown GrantPermissionsActivity for this user, per package and task ID
@@ -147,8 +156,6 @@ public class GrantPermissionsActivity extends SettingsActivity
private List<GrantPermissionsActivity> mFollowerActivities = new ArrayList<>();
/** Whether this activity has asked another GrantPermissionsActivity to show on its behalf */
private boolean mDelegated;
- /** Whether this activity has been triggered by the system */
- private boolean mIsSystemTriggered = false;
/** The set result code, or MAX_VALUE if it hasn't been set yet */
private int mResultCode = Integer.MAX_VALUE;
/** Package that shall have permissions granted */
@@ -180,7 +187,6 @@ public class GrantPermissionsActivity extends SettingsActivity
.getStringArrayExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(getIntent().getAction())) {
- mIsSystemTriggered = true;
mTargetPackage = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
if (mTargetPackage == null) {
Log.e(LOG_TAG, "null EXTRA_PACKAGE_NAME. Must be set for "
@@ -200,7 +206,7 @@ public class GrantPermissionsActivity extends SettingsActivity
// If this app is below the android T targetSdk, filter out the POST_NOTIFICATIONS
// permission, if present
mRequestedPermissions = GrantPermissionsViewModel.Companion
- .filterNotificationPermissionIfNeededSync(
+ .filterPermissionsIfNeededSync(
mTargetPackage, mRequestedPermissions);
}
@@ -216,17 +222,16 @@ public class GrantPermissionsActivity extends SettingsActivity
if (!sCurrentGrantRequests.containsKey(mKey)) {
sCurrentGrantRequests.put(mKey, this);
finishSystemStartedDialogsOnOtherTasksLocked();
- } else if (mIsSystemTriggered) {
- // The system triggered dialog doesn't require results. Delegate, and finish.
+ } else if (getCallingPackage() == null) {
+ // The trampoline doesn't require results. Delegate, and finish.
sCurrentGrantRequests.get(mKey).onNewFollowerActivity(null,
mRequestedPermissions);
finishAfterTransition();
return;
- } else if (sCurrentGrantRequests.get(mKey).mIsSystemTriggered) {
- // Normal permission requests should only merge into the system triggered dialog,
- // which has task overlay set
+ } else {
mDelegated = true;
- sCurrentGrantRequests.get(mKey).onNewFollowerActivity(this, mRequestedPermissions);
+ sCurrentGrantRequests.get(mKey).onNewFollowerActivity(this,
+ mRequestedPermissions);
}
}
@@ -384,6 +389,15 @@ public class GrantPermissionsActivity extends SettingsActivity
if (info.getSendToSettingsImmediately()) {
mViewModel.sendDirectlyToSettings(this, info.getGroupName());
return;
+ } else if (info.getOpenPhotoPicker()) {
+ mViewModel.openPhotoPicker(this, GRANTED_USER_SELECTED);
+ return;
+ }
+
+ if (Utils.isHealthPermissionUiEnabled() && HEALTH_PERMISSION_GROUP.equals(
+ info.getGroupName())) {
+ mViewModel.handleHealthConnectPermissions(this);
+ return;
}
CharSequence appLabel = KotlinUtils.INSTANCE.getPackageLabel(getApplication(),
@@ -415,6 +429,9 @@ public class GrantPermissionsActivity extends SettingsActivity
icon = Icon.createWithResource(getPackageName(), mStoragePermGroupIcon);
messageId = R.string.permgrouprequest_storage_pre_q;
break;
+ case MORE_PHOTOS_MESSAGE:
+ messageId = R.string.permgrouprequest_more_photos;
+ break;
}
CharSequence message = getRequestMessage(appLabel, mTargetPackage,
@@ -471,6 +488,16 @@ public class GrantPermissionsActivity extends SettingsActivity
setTitle(message);
}
+ CharSequence permissionRationaleMessage = null;
+ if (info.getShowPermissionRationale()) {
+ String permissionGroupLabel =
+ KotlinUtils.INSTANCE.getPermGroupLabel(this, info.getGroupName())
+ .toString();
+
+ permissionRationaleMessage = getString(R.string.permission_rationale_message_template,
+ UCharacter.toLowerCase(permissionGroupLabel));
+ }
+
ArrayList<Integer> idxs = new ArrayList<>();
mButtonVisibilities = new boolean[info.getButtonVisibilities().size()];
for (int i = 0; i < info.getButtonVisibilities().size(); i++) {
@@ -486,7 +513,8 @@ public class GrantPermissionsActivity extends SettingsActivity
}
mViewHandler.updateUi(info.getGroupName(), mTotalRequests, mCurrentRequestIdx, icon,
- message, detailMessage, mButtonVisibilities, mLocationVisibilities);
+ message, detailMessage, permissionRationaleMessage, mButtonVisibilities,
+ mLocationVisibilities);
if (showingNewGroup) {
mCurrentRequestIdx++;
}
@@ -499,6 +527,7 @@ public class GrantPermissionsActivity extends SettingsActivity
}
}
+ // LINT.IfChange(dispatchTouchEvent)
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
View rootView = getWindow().getDecorView();
@@ -516,6 +545,7 @@ public class GrantPermissionsActivity extends SettingsActivity
}
return super.dispatchTouchEvent(ev);
}
+ // LINT.ThenChange(PermissionRationaleActivity.java:dispatchTouchEvent)
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
@@ -547,30 +577,21 @@ public class GrantPermissionsActivity extends SettingsActivity
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Consumer<Intent> callback = mViewModel.getActivityResultCallback();
-
- if (requestCode == APP_PERMISSION_REQUEST_CODE && callback != null) {
- callback.accept(data);
- mViewModel.setActivityResultCallback(null);
+ if (callback == null || (requestCode != APP_PERMISSION_REQUEST_CODE
+ && requestCode != PHOTO_PICKER_REQUEST_CODE)) {
+ return;
}
+ if (requestCode == PHOTO_PICKER_REQUEST_CODE) {
+ data = new Intent("").putExtra(INTENT_PHOTOS_SELECTED, resultCode == RESULT_OK);
+ }
+ callback.accept(data);
+ mViewModel.setActivityResultCallback(null);
}
@Override
public void onPermissionGrantResult(String name,
@GrantPermissionsViewHandler.Result int result) {
- if (checkKgm(name, null, result)) {
- return;
- }
-
- if (name == null || name.equals(mPreMergeShownGroupName)) {
- mPreMergeShownGroupName = null;
- }
-
- logGrantPermissionActivityButtons(name, null, result);
- mViewModel.onPermissionGrantResult(name, null, result);
- showNextRequest();
- if (result == CANCELED) {
- setResultAndFinish();
- }
+ onPermissionGrantResult(name, null, result);
}
@Override
@@ -580,10 +601,16 @@ public class GrantPermissionsActivity extends SettingsActivity
return;
}
- if (name != null && name.equals(mPreMergeShownGroupName)) {
+ if (name == null || name.equals(mPreMergeShownGroupName)) {
mPreMergeShownGroupName = null;
}
+ if (Objects.equals(READ_MEDIA_VISUAL, name)
+ && result == GrantPermissionsViewHandler.GRANTED_USER_SELECTED) {
+ mViewModel.openPhotoPicker(this, result);
+ return;
+ }
+
logGrantPermissionActivityButtons(name, affectedForegroundPermissions, result);
mViewModel.onPermissionGrantResult(name, affectedForegroundPermissions, result);
showNextRequest();
@@ -593,6 +620,11 @@ public class GrantPermissionsActivity extends SettingsActivity
}
@Override
+ public void onPermissionRationaleClicked(String groupName) {
+ mViewModel.showPermissionRationaleActivity(this, groupName);
+ }
+
+ @Override
public void onBackPressed() {
if (mViewHandler == null) {
return;
@@ -797,7 +829,9 @@ public class GrantPermissionsActivity extends SettingsActivity
for (Pair<String, Integer> key : sCurrentGrantRequests.keySet()) {
if (key.first.equals(mTargetPackage) && key.second != getTaskId()) {
GrantPermissionsActivity other = sCurrentGrantRequests.get(key);
- if (other.mIsSystemTriggered && other.mFollowerActivities.isEmpty()) {
+ if (other.getIntent().getAction()
+ .equals(PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER)
+ && other.mFollowerActivities.isEmpty()) {
other.finish();
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java
index d5dc22e46..6aaa836b7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsViewHandler.java
@@ -36,7 +36,7 @@ import java.util.List;
public interface GrantPermissionsViewHandler {
@Retention(SOURCE)
@IntDef({CANCELED, GRANTED_ALWAYS, GRANTED_FOREGROUND_ONLY, DENIED, DENIED_DO_NOT_ASK_AGAIN,
- GRANTED_ONE_TIME})
+ GRANTED_ONE_TIME, GRANTED_USER_SELECTED, DENIED_MORE_PHOTOS})
@interface Result {}
int LINKED_TO_SETTINGS = -2;
int CANCELED = -1;
@@ -45,6 +45,8 @@ public interface GrantPermissionsViewHandler {
int DENIED = 2;
int DENIED_DO_NOT_ASK_AGAIN = 3;
int GRANTED_ONE_TIME = 4;
+ int GRANTED_USER_SELECTED = 5;
+ int DENIED_MORE_PHOTOS = 6;
/**
* Listener interface for getting notified when the user responds to a
@@ -55,6 +57,8 @@ public interface GrantPermissionsViewHandler {
void onPermissionGrantResult(String groupName, List<String> affectedForegroundPermissions,
@Result int result);
+
+ void onPermissionRationaleClicked(String groupName);
}
/**
@@ -83,11 +87,15 @@ public interface GrantPermissionsViewHandler {
* @param message the message to display the user
* @param detailMessage another message to display to the user. This clarifies "message" in more
* detail
+ * @param permissionRationaleMessage another message to display to the user. This message lets
+ * users know developer stated data usages for the requested
+ * permission
* @param buttonVisibilities visibilities for each button
* @param locationVisibilities visibilities for location options
*/
void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
- CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities,
+ CharSequence message, CharSequence detailMessage,
+ CharSequence permissionRationaleMessage, boolean[] buttonVisibilities,
boolean[] locationVisibilities);
/**
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
index 91ca82338..65d08a217 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
@@ -16,6 +16,7 @@
package com.android.permissioncontroller.permission.ui;
+import static android.healthconnect.HealthPermissions.HEALTH_PERMISSION_GROUP;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static com.android.permissioncontroller.Constants.ACTION_MANAGE_AUTO_REVOKE;
@@ -71,6 +72,7 @@ import com.android.permissioncontroller.permission.ui.handheld.HandheldUnusedApp
import com.android.permissioncontroller.permission.ui.handheld.PermissionAppsFragment;
import com.android.permissioncontroller.permission.ui.handheld.v31.PermissionDetailsWrapperFragment;
import com.android.permissioncontroller.permission.ui.handheld.v31.PermissionUsageV2WrapperFragment;
+import com.android.permissioncontroller.permission.ui.handheld.v34.AppDataSharingUpdatesFragment;
import com.android.permissioncontroller.permission.ui.legacy.AppPermissionActivity;
import com.android.permissioncontroller.permission.ui.television.TvUnusedAppsFragment;
import com.android.permissioncontroller.permission.ui.wear.AppPermissionsFragmentWear;
@@ -364,6 +366,13 @@ public final class ManagePermissionsActivity extends SettingsActivity {
return;
}
+ if (Utils.isHealthPermissionUiEnabled() && permissionGroupName
+ .equals(HEALTH_PERMISSION_GROUP)) {
+ Utils.navigateToHealthConnectSettings(this);
+ finishAfterTransition();
+ return;
+ }
+
if (DeviceUtils.isAuto(this)) {
androidXFragment =
AutoPermissionAppsFragment.newInstance(permissionGroupName, sessionId);
@@ -434,6 +443,23 @@ public final class ManagePermissionsActivity extends SettingsActivity {
}
} break;
+ case Intent.ACTION_REVIEW_APP_DATA_SHARING_UPDATES: {
+ if (KotlinUtils.INSTANCE.isSafetyLabelChangeNotificationsEnabled()) {
+ if (DeviceUtils.isAuto(this) || DeviceUtils.isWear(this)
+ || DeviceUtils.isTelevision(this)) {
+ Log.e(LOG_TAG, "ACTION_REVIEW_APP_DATA_SHARING_UPDATES is not "
+ + "supported on this device type");
+ finishAfterTransition();
+ return;
+ }
+ setNavGraph(AppDataSharingUpdatesFragment.Companion.createArgs(sessionId),
+ R.id.app_data_sharing_updates);
+ } else {
+ finishAfterTransition();
+ return;
+ }
+ } break;
+
default: {
Log.w(LOG_TAG, "Unrecognized action " + action);
finishAfterTransition();
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ReviewOngoingUsageActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ReviewOngoingUsageActivity.java
index 6dfceb90d..05a75f594 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ReviewOngoingUsageActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ReviewOngoingUsageActivity.java
@@ -25,8 +25,8 @@ import android.view.MenuItem;
import androidx.annotation.NonNull;
import com.android.permissioncontroller.DeviceUtils;
-import com.android.permissioncontroller.permission.ui.handheld.v31.DashboardUtilsKt;
import com.android.permissioncontroller.permission.ui.handheld.v31.ReviewOngoingUsageWrapperFragment;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
/**
* A dialog listing the currently uses of camera, microphone, and location.
@@ -40,8 +40,8 @@ public final class ReviewOngoingUsageActivity extends SettingsActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (!DashboardUtilsKt.shouldShowCameraMicIndicators()
- && !DashboardUtilsKt.shouldShowLocationIndicators()) {
+ if (!KotlinUtils.INSTANCE.shouldShowCameraMicIndicators()
+ && !KotlinUtils.INSTANCE.shouldShowLocationIndicators()) {
finishAfterTransition();
return;
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/UnusedAppsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/UnusedAppsFragment.kt
index 639996d46..8b634fbcf 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/UnusedAppsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/UnusedAppsFragment.kt
@@ -21,6 +21,7 @@ import android.app.AlertDialog
import android.app.Application
import android.app.Dialog
import android.content.Intent
+import android.icu.text.MessageFormat
import android.os.Bundle
import android.os.Handler
import android.os.Looper
@@ -195,9 +196,9 @@ class UnusedAppsFragment<PF, UnusedAppPref> : Fragment()
for ((month, packages) in categorizedPackages) {
val category = preferenceScreen.findPreference<PreferenceCategory>(month.value)!!
category.title = if (month == Months.THREE) {
- getString(R.string.last_opened_category_title, "3")
+ MessageFormat.format(getString(R.string.last_opened_category_title), mapOf("count" to 3))
} else {
- getString(R.string.last_opened_category_title, "6")
+ MessageFormat.format(getString(R.string.last_opened_category_title), mapOf("count" to 6))
}
category.isVisible = packages.isNotEmpty()
if (packages.isNotEmpty()) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionsFragment.java
index 0973d1dfe..cf58f1e17 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionsFragment.java
@@ -23,7 +23,6 @@ import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_
import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__DENIED;
import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME;
-import static com.android.permissioncontroller.permission.ui.handheld.v31.DashboardUtilsKt.is7DayToggleEnabled;
import static java.util.concurrent.TimeUnit.DAYS;
@@ -146,7 +145,7 @@ public class AutoAppPermissionsFragment extends AutoSettingsFrameFragment implem
if (SdkLevel.isAtLeastS()) {
mPermissionUsages = new PermissionUsages(getContext());
- long aggregateDataFilterBeginDays = is7DayToggleEnabled()
+ long aggregateDataFilterBeginDays = KotlinUtils.INSTANCE.is7DayToggleEnabled()
? AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS_7 :
AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS_1;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java
index 194faff6f..6b09921cb 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/GrantPermissionsAutoViewHandler.java
@@ -91,8 +91,11 @@ public class GrantPermissionsAutoViewHandler implements GrantPermissionsViewHand
@Override
public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
- CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities,
+ CharSequence message, CharSequence detailMessage,
+ CharSequence permissionRationaleMessage, boolean[] buttonVisibilities,
boolean[] locationVisibilities) {
+ // permissionRationaleMessage ignored by auto
+
mGroupName = groupName;
mGroupCount = groupCount;
mGroupIndex = groupIndex;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt
index 0d2ee9895..ff7697cf9 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt
@@ -18,14 +18,13 @@ package com.android.permissioncontroller.permission.ui.auto.dashboard
import android.content.Context
import android.content.Intent
+import android.text.format.DateFormat
import androidx.preference.Preference.OnPreferenceClickListener
import com.android.car.ui.preference.CarUiPreference
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel
-/**
- * Preference that displays a permission usage for an app.
- */
+/** Preference that displays a permission usage for an app. */
class AutoPermissionHistoryPreference(
context: Context,
historyPreferenceData: PermissionUsageDetailsViewModel.HistoryPreferenceData
@@ -33,21 +32,25 @@ class AutoPermissionHistoryPreference(
init {
title = historyPreferenceData.preferenceTitle
- summary = if (historyPreferenceData.summaryText != null) {
- context.getString(R.string.auto_permission_usage_timeline_summary,
- historyPreferenceData.accessTime, historyPreferenceData.summaryText)
- } else {
- historyPreferenceData.accessTime
- }
+ summary =
+ if (historyPreferenceData.summaryText != null) {
+ context.getString(
+ R.string.auto_permission_usage_timeline_summary,
+ DateFormat.getTimeFormat(context).format(historyPreferenceData.accessEndTime),
+ historyPreferenceData.summaryText)
+ } else {
+ DateFormat.getTimeFormat(context).format(historyPreferenceData.accessEndTime)
+ }
if (historyPreferenceData.appIcon != null) {
icon = historyPreferenceData.appIcon
}
onPreferenceClickListener = OnPreferenceClickListener {
- val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS).apply {
- putExtra(Intent.EXTRA_USER, historyPreferenceData.userHandle)
- putExtra(Intent.EXTRA_PACKAGE_NAME, historyPreferenceData.pkgName)
- }
+ val intent =
+ Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS).apply {
+ putExtra(Intent.EXTRA_USER, historyPreferenceData.userHandle)
+ putExtra(Intent.EXTRA_PACKAGE_NAME, historyPreferenceData.pkgName)
+ }
context.startActivity(intent)
true
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt
index f13a3565d..1e336d905 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt
@@ -17,14 +17,15 @@
package com.android.permissioncontroller.permission.ui.auto.dashboard
import android.app.role.RoleManager
-import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
+import android.text.format.DateFormat
import androidx.annotation.RequiresApi
import androidx.lifecycle.ViewModelProvider
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
+import androidx.preference.PreferenceScreen
import com.android.car.ui.preference.CarUiPreference
import com.android.permissioncontroller.Constants
import com.android.permissioncontroller.DumpableLog
@@ -34,47 +35,56 @@ import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED
import com.android.permissioncontroller.R
import com.android.permissioncontroller.auto.AutoSettingsFrameFragment
+import com.android.permissioncontroller.permission.model.legacy.PermissionApps.AppDataLoader
import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
import com.android.permissioncontroller.permission.model.v31.PermissionUsages
import com.android.permissioncontroller.permission.model.v31.PermissionUsages.PermissionsUsagesChangeCallback
-import com.android.permissioncontroller.permission.model.legacy.PermissionApps.AppDataLoader
-import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
import com.android.permissioncontroller.permission.ui.auto.AutoDividerPreference
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.AppPermissionUsageEntry
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelFactory
import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
import com.android.permissioncontroller.permission.utils.Utils
-import java.util.concurrent.atomic.AtomicBoolean
+import java.time.Clock
+import java.time.Instant
+import java.time.ZoneId
+import java.time.ZonedDateTime
+import java.time.temporal.ChronoUnit
import java.util.concurrent.atomic.AtomicReference
@RequiresApi(Build.VERSION_CODES.S)
-class AutoPermissionUsageDetailsFragment : AutoSettingsFrameFragment(),
- PermissionsUsagesChangeCallback {
+class AutoPermissionUsageDetailsFragment :
+ AutoSettingsFrameFragment(), PermissionsUsagesChangeCallback {
companion object {
private const val LOG_TAG = "AutoPermissionUsageDetailsFragment"
private const val KEY_SESSION_ID = "_session_id"
private const val FILTER_24_HOURS = 2
+ private val MIDNIGHT_TODAY =
+ ZonedDateTime.now(ZoneId.systemDefault()).truncatedTo(ChronoUnit.DAYS).toEpochSecond() *
+ 1000L
+ private val MIDNIGHT_YESTERDAY =
+ ZonedDateTime.now(ZoneId.systemDefault())
+ .minusDays(1)
+ .truncatedTo(ChronoUnit.DAYS)
+ .toEpochSecond() * 1000L
// Only show the last 24 hours on Auto right now
private const val SHOW_7_DAYS = false
- /**
- * Creates a new instance of [AutoPermissionUsageDetailsFragment].
- */
+ /** Creates a new instance of [AutoPermissionUsageDetailsFragment]. */
fun newInstance(
groupName: String?,
showSystem: Boolean,
sessionId: Long
): AutoPermissionUsageDetailsFragment {
return AutoPermissionUsageDetailsFragment().apply {
- arguments = Bundle().apply {
- putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
- putLong(Constants.EXTRA_SESSION_ID, sessionId)
- putBoolean(ManagePermissionsActivity.EXTRA_SHOW_SYSTEM, showSystem)
- }
+ arguments =
+ Bundle().apply {
+ putString(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
+ putLong(Constants.EXTRA_SESSION_ID, sessionId)
+ putBoolean(ManagePermissionsActivity.EXTRA_SHOW_SYSTEM, showSystem)
+ }
}
}
}
@@ -91,7 +101,7 @@ class AutoPermissionUsageDetailsFragment : AutoSettingsFrameFragment(),
private var finishedInitialLoad = false
private var hasSystemApps = false
- /** Unique Id of a request */
+ /** Unique Id of a request */
private var sessionId: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
@@ -108,25 +118,26 @@ class AutoPermissionUsageDetailsFragment : AutoSettingsFrameFragment(),
return
}
filterGroup = requireArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME)!!
- showSystem = requireArguments().getBoolean(ManagePermissionsActivity.EXTRA_SHOW_SYSTEM,
- false)
- sessionId = savedInstanceState?.getLong(SESSION_ID_KEY)
- ?: (arguments?.getLong(Constants.EXTRA_SESSION_ID, Constants.INVALID_SESSION_ID)
- ?: Constants.INVALID_SESSION_ID)
- headerLabel = resources.getString(R.string.permission_group_usage_title,
- getPermGroupLabel(requireContext(), filterGroup))
+ showSystem =
+ requireArguments().getBoolean(ManagePermissionsActivity.EXTRA_SHOW_SYSTEM, false)
+ sessionId =
+ savedInstanceState?.getLong(SESSION_ID_KEY)
+ ?: (arguments?.getLong(Constants.EXTRA_SESSION_ID, Constants.INVALID_SESSION_ID)
+ ?: Constants.INVALID_SESSION_ID)
+ headerLabel =
+ resources.getString(
+ R.string.permission_group_usage_title,
+ getPermGroupLabel(requireContext(), filterGroup))
val context = preferenceManager.getContext()
- permissionUsages =
- PermissionUsages(
- context
- )
+ permissionUsages = PermissionUsages(context)
roleManager = Utils.getSystemServiceSafe(context, RoleManager::class.java)
- val usageViewModelFactory = PermissionUsageDetailsViewModelFactory(
- PermissionControllerApplication.get(), roleManager,
- filterGroup, sessionId)
- usageViewModel = ViewModelProvider(this,
- usageViewModelFactory)[PermissionUsageDetailsViewModel::class.java]
+ val usageViewModelFactory =
+ PermissionUsageDetailsViewModelFactory(
+ PermissionControllerApplication.get(), roleManager, filterGroup, sessionId)
+ usageViewModel =
+ ViewModelProvider(this, usageViewModelFactory)[
+ PermissionUsageDetailsViewModel::class.java]
reloadData()
}
@@ -142,9 +153,7 @@ class AutoPermissionUsageDetailsFragment : AutoSettingsFrameFragment(),
preferenceScreen.addPreference(AutoDividerPreference(context))
}
- /**
- * Reloads the data to show.
- */
+ /** Reloads the data to show. */
private fun reloadData() {
usageViewModel.loadPermissionUsages(
requireActivity().getLoaderManager(), permissionUsages, this, FILTER_24_HOURS)
@@ -164,7 +173,8 @@ class AutoPermissionUsageDetailsFragment : AutoSettingsFrameFragment(),
private fun updateSystemToggle() {
if (!showSystem) {
PermissionControllerStatsLog.write(
- PERMISSION_USAGE_FRAGMENT_INTERACTION, sessionId,
+ PERMISSION_USAGE_FRAGMENT_INTERACTION,
+ sessionId,
PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED)
}
showSystem = !showSystem
@@ -177,11 +187,12 @@ class AutoPermissionUsageDetailsFragment : AutoSettingsFrameFragment(),
setAction(null, null)
return
}
- val label = if (showSystem) {
- getString(R.string.menu_hide_system)
- } else {
- getString(R.string.menu_show_system)
- }
+ val label =
+ if (showSystem) {
+ getString(R.string.menu_hide_system)
+ } else {
+ getString(R.string.menu_show_system)
+ }
setAction(label) { updateSystemToggle() }
}
@@ -192,68 +203,102 @@ class AutoPermissionUsageDetailsFragment : AutoSettingsFrameFragment(),
preferenceScreen.removeAll()
setupHeaderPreferences()
- val permApps = arrayListOf<PermissionApp>()
val exemptedPackages = Utils.getExemptedPackages(roleManager)
- val seenSystemApp = AtomicBoolean(false)
- val usages: List<AppPermissionUsageEntry> = usageViewModel.parseUsages(
- appPermissionUsages, exemptedPackages, permApps, seenSystemApp, showSystem,
- SHOW_7_DAYS)
- if (hasSystemApps != seenSystemApp.get()) {
- hasSystemApps = seenSystemApp.get()
+ val uiData =
+ usageViewModel.buildPermissionUsageDetailsUiData(
+ appPermissionUsages, showSystem, SHOW_7_DAYS)
+
+ if (hasSystemApps != uiData.shouldDisplayShowSystemToggle) {
+ hasSystemApps = uiData.shouldDisplayShowSystemToggle
updateAction()
}
- val preferenceFactory = PreferenceFactory(requireActivity())
- val category = AtomicReference(preferenceFactory.createDayCategoryPreference())
+ val category = AtomicReference(PreferenceCategory(context))
preferenceScreen.addPreference(category.get())
AppDataLoader(context) {
- usageViewModel.renderTimelinePreferences(usages, category, preferenceScreen,
- preferenceFactory)
+ renderHistoryPreferences(
+ uiData.getHistoryPreferenceDataList(), category, preferenceScreen)
- setLoading(false)
- finishedInitialLoad = true
- permissionUsages.stopLoader(requireActivity().getLoaderManager())
- }.execute(*permApps.toTypedArray())
+ setLoading(false)
+ finishedInitialLoad = true
+ permissionUsages.stopLoader(requireActivity().getLoaderManager())
+ }
+ .execute(*uiData.permissionApps.toTypedArray())
}
- private class PreferenceFactory(val context: Context) :
- PermissionUsageDetailsViewModel.HistoryPreferenceFactory {
-
- override fun createDayCategoryPreference(): PreferenceCategory {
- return PreferenceCategory(context)
- }
-
- override fun createPermissionHistoryPreference(
- historyPreferenceData: PermissionUsageDetailsViewModel.HistoryPreferenceData
- ): Preference {
- return AutoPermissionHistoryPreference(context, historyPreferenceData)
- }
+ fun createPermissionHistoryPreference(
+ historyPreferenceData: PermissionUsageDetailsViewModel.HistoryPreferenceData
+ ): Preference {
+ return AutoPermissionHistoryPreference(requireContext(), historyPreferenceData)
}
private fun addTimelineDescriptionPreference() {
- val preference = CarUiPreference(context).apply {
- summary = getString(R.string.permission_group_usage_subtitle_24h,
- getPermGroupLabel(requireContext(), filterGroup))
- isSelectable = false
- }
+ val preference =
+ CarUiPreference(context).apply {
+ summary =
+ getString(
+ R.string.permission_group_usage_subtitle_24h,
+ getPermGroupLabel(requireContext(), filterGroup))
+ isSelectable = false
+ }
preferenceScreen.addPreference(preference)
}
private fun addManagePermissionPreference() {
- val preference = CarUiPreference(context).apply {
- title = getString(R.string.manage_permission)
- summary = getString(R.string.manage_permission_summary,
- getPermGroupLabel(requireContext(), filterGroup))
- onPreferenceClickListener = Preference.OnPreferenceClickListener {
- val intent = Intent(Intent.ACTION_MANAGE_PERMISSION_APPS).apply {
- putExtra(Intent.EXTRA_PERMISSION_NAME, filterGroup)
+ val preference =
+ CarUiPreference(context).apply {
+ title = getString(R.string.manage_permission)
+ summary =
+ getString(
+ R.string.manage_permission_summary,
+ getPermGroupLabel(requireContext(), filterGroup))
+ onPreferenceClickListener =
+ Preference.OnPreferenceClickListener {
+ val intent =
+ Intent(Intent.ACTION_MANAGE_PERMISSION_APPS).apply {
+ putExtra(Intent.EXTRA_PERMISSION_NAME, filterGroup)
+ }
+ startActivity(intent)
+ true
+ }
+ }
+ preferenceScreen.addPreference(preference)
+ }
+
+ /** Render the provided [historyPreferenceDataList] into the [preferenceScreen] UI. */
+ fun renderHistoryPreferences(
+ historyPreferenceDataList: List<PermissionUsageDetailsViewModel.HistoryPreferenceData>,
+ category: AtomicReference<PreferenceCategory>,
+ preferenceScreen: PreferenceScreen,
+ ) {
+ var previousDateMs = 0L
+ historyPreferenceDataList.forEach {
+ val usageTimestamp = it.accessEndTime
+ val currentDateMs =
+ ZonedDateTime.ofInstant(
+ Instant.ofEpochMilli(usageTimestamp),
+ Clock.system(ZoneId.systemDefault()).zone)
+ .truncatedTo(ChronoUnit.DAYS)
+ .toEpochSecond() * 1000L
+ if (currentDateMs != previousDateMs) {
+ if (previousDateMs != 0L) {
+ category.set(PreferenceCategory(context))
+ preferenceScreen.addPreference(category.get())
}
- startActivity(intent)
- true
+ if (usageTimestamp > MIDNIGHT_TODAY) {
+ category.get().setTitle(R.string.permission_history_category_today)
+ } else if (usageTimestamp > MIDNIGHT_YESTERDAY) {
+ category.get().setTitle(R.string.permission_history_category_yesterday)
+ } else {
+ category
+ .get()
+ .setTitle(DateFormat.getDateFormat(context).format(currentDateMs))
+ }
+ previousDateMs = currentDateMs
}
+ category.get().addPreference(createPermissionHistoryPreference(it))
}
- preferenceScreen.addPreference(preference)
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt
index 323a13706..a4d1398b0 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt
@@ -31,15 +31,16 @@ import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED
import com.android.permissioncontroller.R
import com.android.permissioncontroller.auto.AutoSettingsFrameFragment
-import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
-import com.android.permissioncontroller.permission.model.v31.PermissionUsages
-import com.android.permissioncontroller.permission.model.v31.PermissionUsages.PermissionsUsagesChangeCallback
import com.android.permissioncontroller.permission.model.legacy.PermissionApps.AppDataLoader
import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp
import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo
+import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
+import com.android.permissioncontroller.permission.model.v31.PermissionUsages
+import com.android.permissioncontroller.permission.model.v31.PermissionUsages.PermissionsUsagesChangeCallback
import com.android.permissioncontroller.permission.ui.model.ManagePermissionsViewModel
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageControlPreferenceUtils
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageViewModel
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageViewModel.PermissionGroupWithUsageCount
import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageViewModelFactory
import com.android.permissioncontroller.permission.utils.Utils
@@ -67,33 +68,32 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment(), PermissionsUsag
private var finishedInitialLoad = false
private var hasSystemApps = false
- /** Unique Id of a request */
+ /** Unique Id of a request */
private var sessionId: Long = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
headerLabel = getString(R.string.permission_usage_title)
- sessionId = savedInstanceState?.getLong(SESSION_ID_KEY)
- ?: (arguments?.getLong(Constants.EXTRA_SESSION_ID, Constants.INVALID_SESSION_ID)
- ?: Constants.INVALID_SESSION_ID)
+ sessionId =
+ savedInstanceState?.getLong(SESSION_ID_KEY)
+ ?: (arguments?.getLong(Constants.EXTRA_SESSION_ID, Constants.INVALID_SESSION_ID)
+ ?: Constants.INVALID_SESSION_ID)
val context: Context = preferenceManager.getContext()
- permissionUsages =
- PermissionUsages(
- context
- )
+ permissionUsages = PermissionUsages(context)
val roleManager = Utils.getSystemServiceSafe(context, RoleManager::class.java)
val application: Application = requireActivity().getApplication()
- val managePermissionsViewModelFactory = ViewModelProvider.AndroidViewModelFactory
- .getInstance(application)
- managePermissionsViewModel = ViewModelProvider(this,
- managePermissionsViewModelFactory)[ManagePermissionsViewModel::class.java]
+ val managePermissionsViewModelFactory =
+ ViewModelProvider.AndroidViewModelFactory.getInstance(application)
+ managePermissionsViewModel =
+ ViewModelProvider(this, managePermissionsViewModelFactory)[
+ ManagePermissionsViewModel::class.java]
val usageViewModelFactory = PermissionUsageViewModelFactory(roleManager)
- usageViewModel = ViewModelProvider(this,
- usageViewModelFactory)[PermissionUsageViewModel::class.java]
+ usageViewModel =
+ ViewModelProvider(this, usageViewModelFactory)[PermissionUsageViewModel::class.java]
- managePermissionsViewModel.standardPermGroupsLiveData.observe(this,
- this::onPermissionGroupsChanged)
+ managePermissionsViewModel.standardPermGroupsLiveData.observe(
+ this, this::onPermissionGroupsChanged)
setLoading(true)
reloadData()
}
@@ -114,7 +114,9 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment(), PermissionsUsag
private fun updateSystemToggle() {
if (!showSystem) {
- PermissionControllerStatsLog.write(PERMISSION_USAGE_FRAGMENT_INTERACTION, sessionId,
+ PermissionControllerStatsLog.write(
+ PERMISSION_USAGE_FRAGMENT_INTERACTION,
+ sessionId,
PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED)
}
showSystem = !showSystem
@@ -127,17 +129,16 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment(), PermissionsUsag
setAction(null, null)
return
}
- val label = if (showSystem) {
- getString(R.string.menu_hide_system)
- } else {
- getString(R.string.menu_show_system)
- }
+ val label =
+ if (showSystem) {
+ getString(R.string.menu_hide_system)
+ } else {
+ getString(R.string.menu_show_system)
+ }
setAction(label) { updateSystemToggle() }
}
- /**
- * Reloads the data to show.
- */
+ /** Reloads the data to show. */
private fun reloadData() {
usageViewModel.loadPermissionUsages(
requireActivity().getLoaderManager(), permissionUsages, this)
@@ -160,56 +161,67 @@ class AutoPermissionUsageFragment : AutoSettingsFrameFragment(), PermissionsUsag
}
getPreferenceScreen().removeAll()
- val (usages, permApps, seenSystemApps) = usageViewModel.extractUsages(appPermissionUsages,
- show7Days, showSystem)
+ val permissionUsagesUiData =
+ usageViewModel.buildPermissionUsagesUiData(
+ appPermissionUsages, show7Days, showSystem, requireContext())
+ val permissionApps = permissionUsagesUiData.permissionApps
+ val displayShowSystemToggle = permissionUsagesUiData.displayShowSystemToggle
- if (hasSystemApps != seenSystemApps) {
- hasSystemApps = seenSystemApps
+ if (hasSystemApps != displayShowSystemToggle) {
+ hasSystemApps = displayShowSystemToggle
updateAction()
}
- val groupUsagesList: List<Map.Entry<String, Int>> = usageViewModel
- .createGroupUsagesList(requireContext(), usages)
+ val permissionGroupWithUsageCounts: List<PermissionGroupWithUsageCount> =
+ permissionUsagesUiData.orderedPermissionGroupsWithUsageCount
- addUIContent(groupUsagesList, permApps)
+ addUIContent(permissionGroupWithUsageCounts, permissionApps)
}
- /**
- * Use the usages and permApps that are previously constructed to add UI content to the page
- */
+ /** Use the usages and permApps that are previously constructed to add UI content to the page */
private fun addUIContent(
- usages: List<Map.Entry<String, Int>>,
+ permissionGroupWithUsageCounts:
+ List<PermissionGroupWithUsageCount>,
permApps: java.util.ArrayList<PermissionApp>
) {
AppDataLoader(context) {
- // Show permission groups with permissions granted to an app, including groups
- // where the permission is only granted to a system app. This still excludes groups
- // that don't have grants from any apps. Showing the same groups regardless of
- // whether showSystem is selected avoids permission groups hiding and appearing,
- // which is a confusing user experience.
- val usedPermissionGroups = permissionGroups
- .filter {
- (it.nonSystemUserSetOrPreGranted > 0) or
- (it.systemUserSetOrPreGranted > 0)
+ // Show permission groups with permissions granted to an app, including groups
+ // where the permission is only granted to a system app. This still excludes groups
+ // that don't have grants from any apps. Showing the same groups regardless of
+ // whether showSystem is selected avoids permission groups hiding and appearing,
+ // which is a confusing user experience.
+ val usedPermissionGroups =
+ permissionGroups
+ .filter {
+ (it.nonSystemUserSetOrPreGranted > 0) or
+ (it.systemUserSetOrPreGranted > 0)
+ }
+ .filterNot { it.onlyShellPackageGranted }
+
+ for (i in permissionGroupWithUsageCounts.indices) {
+ val groupName = permissionGroupWithUsageCounts[i].permGroup
+ val count = permissionGroupWithUsageCounts[i].appCount
+ if ((usedPermissionGroups.filter { it.name == groupName }).isEmpty()) {
+ continue
+ }
+ val permissionUsagePreference = CarUiPreference(requireContext())
+ PermissionUsageControlPreferenceUtils.initPreference(
+ permissionUsagePreference,
+ requireContext(),
+ groupName,
+ count,
+ showSystem,
+ sessionId,
+ show7Days)
+ getPreferenceScreen().addPreference(permissionUsagePreference)
}
- .filterNot { it.onlyShellPackageGranted }
-
- for (i in usages.indices) {
- val (groupName, count) = usages[i]
- if ((usedPermissionGroups.filter { it.name == groupName }).isEmpty()) {
- continue
+ finishedInitialLoad = true
+ setLoading(false)
+ val activity: Activity? = activity
+ if (activity != null) {
+ permissionUsages.stopLoader(activity.loaderManager)
}
- val permissionUsagePreference = CarUiPreference(requireContext())
- PermissionUsageControlPreferenceUtils.initPreference(permissionUsagePreference,
- requireContext(), groupName, count, showSystem, sessionId, show7Days)
- getPreferenceScreen().addPreference(permissionUsagePreference)
- }
- finishedInitialLoad = true
- setLoading(false)
- val activity: Activity? = activity
- if (activity != null) {
- permissionUsages.stopLoader(activity.loaderManager)
}
- }.execute(*permApps.toTypedArray())
+ .execute(*permApps.toTypedArray())
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
index eaa45fc8c..320067959 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
@@ -17,6 +17,7 @@
package com.android.permissioncontroller.permission.ui.handheld;
import static android.Manifest.permission_group.STORAGE;
+import static android.app.Activity.RESULT_OK;
import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
@@ -27,6 +28,7 @@ import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_
import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY;
import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__DENY_FOREGROUND;
import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__GRANT_FINE_LOCATION;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__PHOTOS_SELECTED;
import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__REVOKE_FINE_LOCATION;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED;
import static com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN;
@@ -38,7 +40,6 @@ import static com.android.permissioncontroller.permission.ui.ManagePermissionsAc
import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack;
import android.app.ActionBar;
-import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.role.RoleManager;
@@ -72,6 +73,7 @@ import androidx.lifecycle.ViewModelProvider;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState;
+import com.android.permissioncontroller.permission.model.livedatatypes.SafetyLabelInfo;
import com.android.permissioncontroller.permission.ui.AdvancedConfirmDialogArgs;
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler;
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel;
@@ -104,11 +106,14 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
static final String GRANT_CATEGORY = "grant_category";
private @NonNull AppPermissionViewModel mViewModel;
+ private @NonNull ViewGroup mAppPermissionRationaleContainer;
+ private @NonNull ViewGroup mAppPermissionRationaleContent;
private @NonNull RadioButton mAllowButton;
private @NonNull RadioButton mAllowAlwaysButton;
private @NonNull RadioButton mAllowForegroundButton;
private @NonNull RadioButton mAskOneTimeButton;
private @NonNull RadioButton mAskButton;
+ private @NonNull RadioButton mSelectButton;
private @NonNull RadioButton mDenyButton;
private @NonNull RadioButton mDenyForegroundButton;
private @NonNull View mLocationAccuracy;
@@ -190,6 +195,10 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
getActivity().getApplication(), mPackageName, mPermGroupName, mUser, mSessionId);
mViewModel = new ViewModelProvider(this, factory).get(AppPermissionViewModel.class);
Handler delayHandler = new Handler(Looper.getMainLooper());
+ if (KotlinUtils.INSTANCE.isPermissionRationaleEnabled()) {
+ mViewModel.getSafetyLabelInfoLiveData().observe(this,
+ this::showPermissionRationaleDialog);
+ }
mViewModel.getButtonStateLiveData().observe(this, buttonState -> {
if (mIsInitialLoad) {
setRadioButtonsState(buttonState);
@@ -203,6 +212,7 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
if (mIsStorageGroup) {
mViewModel.getFullStorageStateLiveData().observe(this, this::setSpecialStorageState);
}
+ mViewModel.registerPhotoPickerResultIfNeeded(this);
mRoleManager = Utils.getSystemServiceSafe(getContext(), RoleManager.class);
}
@@ -251,6 +261,7 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
mAllowForegroundButton = root.requireViewById(R.id.allow_foreground_only_radio_button);
mAskOneTimeButton = root.requireViewById(R.id.ask_one_time_radio_button);
mAskButton = root.requireViewById(R.id.ask_radio_button);
+ mSelectButton = root.requireViewById(R.id.select_photos_radio_button);
mDenyButton = root.requireViewById(R.id.deny_radio_button);
mDenyForegroundButton = root.requireViewById(R.id.deny_foreground_radio_button);
mDivider = root.requireViewById(R.id.two_target_divider);
@@ -280,14 +291,47 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
TextView storageFooter = root.requireViewById(R.id.footer_storage_special_app_access);
storageFooter.setVisibility(View.GONE);
}
+ mAppPermissionRationaleContainer =
+ root.requireViewById(R.id.app_permission_rationale_container);
+ mAppPermissionRationaleContent =
+ root.requireViewById(R.id.app_permission_rationale_content);
+ if (!KotlinUtils.INSTANCE.isPermissionRationaleEnabled()) {
+ hidePermissionRationaleContainer();
+ } else {
+ setPermissionRationaleContainer(root, context);
+ }
getActivity().setTitle(
getPreferenceManager().getContext().getString(R.string.app_permission_title,
mPermGroupLabel));
-
return root;
}
+ private void setPermissionRationaleContainer(View root, Context context) {
+ ((TextView) root.requireViewById(R.id.app_permission_rationale_message)).setText(
+ context.getString(R.string.app_permission_rationale_message));
+ ((TextView) root.requireViewById(R.id.app_permission_rationale_title)).setText(
+ context.getString(R.string.app_location_permission_rationale_title));
+ ((TextView) root.requireViewById(R.id.app_permission_rationale_subtitle)).setText(
+ context.getString(R.string.app_location_permission_rationale_subtitle));
+ }
+
+ private void showPermissionRationaleDialog(@Nullable SafetyLabelInfo safetyLabelInfo) {
+ if (safetyLabelInfo == null
+ || !mViewModel.shouldShowPermissionRationale(safetyLabelInfo, mPermGroupName)) {
+ hidePermissionRationaleContainer();
+ } else {
+ mAppPermissionRationaleContainer.setVisibility(View.VISIBLE);
+ mAppPermissionRationaleContent.setOnClickListener((v) -> {
+ mViewModel.showPermissionRationaleActivity(getActivity(), mPermGroupName);
+ });
+ }
+ }
+
+ private void hidePermissionRationaleContainer() {
+ mAppPermissionRationaleContainer.setVisibility(View.GONE);
+ }
+
private void setBottomLinkState(TextView view, String caller, String action) {
if ((Objects.equals(caller, AppPermissionGroupsFragment.class.getName())
&& action.equals(Intent.ACTION_MANAGE_APP_PERMISSIONS))
@@ -378,8 +422,20 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__ASK_EVERY_TIME);
setResult(DENIED);
});
+ mSelectButton.setOnClickListener((v) -> {
+ int buttonPressed =
+ APP_PERMISSION_FRAGMENT_ACTION_REPORTED__BUTTON_PRESSED__PHOTOS_SELECTED;
+ mViewModel.openPhotoPicker(result -> {
+ if (result == RESULT_OK) {
+ mViewModel.requestChange(false, this, this, ChangeRequest.PHOTOS_SELECTED,
+ buttonPressed);
+ } else {
+ // Reset the button state to what is was previously
+ setRadioButtonsState(states);
+ }
+ });
+ });
mDenyButton.setOnClickListener((v) -> {
-
if (mViewModel.getFullStorageStateLiveData().getValue() != null
&& !mViewModel.getFullStorageStateLiveData().getValue().isLegacy()) {
mViewModel.setAllFilesAccess(false);
@@ -416,6 +472,12 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
setButtonState(mAskButton, states.get(ButtonType.ASK));
setButtonState(mDenyButton, states.get(ButtonType.DENY));
setButtonState(mDenyForegroundButton, states.get(ButtonType.DENY_FOREGROUND));
+ setButtonState(mSelectButton, states.get(ButtonType.SELECT_PHOTOS));
+ if (mSelectButton.getVisibility() == View.VISIBLE) {
+ mAllowButton.setText(R.string.app_permission_button_allow_all_photos);
+ } else {
+ mAllowButton.setText(R.string.app_permission_button_allow);
+ }
ButtonState locationAccuracyState = states.get(ButtonType.LOCATION_ACCURACY);
if (!locationAccuracyState.isShown()) {
@@ -485,7 +547,7 @@ public class AppPermissionFragment extends SettingsWithLargeHeader
Intent intent = new Intent()
.putExtra(EXTRA_RESULT_PERMISSION_INTERACTED, mPermGroupName)
.putExtra(EXTRA_RESULT_PERMISSION_RESULT, result);
- getActivity().setResult(Activity.RESULT_OK, intent);
+ getActivity().setResult(RESULT_OK, intent);
}
private void setDetail(Pair<Integer, Integer> detailResIds) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
index 9bb49bed6..568d7f225 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
@@ -24,7 +24,6 @@ import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_
import static com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__DENIED;
import static com.android.permissioncontroller.hibernation.HibernationPolicyKt.isHibernationEnabled;
import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack;
-import static com.android.permissioncontroller.permission.ui.handheld.v31.DashboardUtilsKt.is7DayToggleEnabled;
import static java.util.concurrent.TimeUnit.DAYS;
@@ -183,7 +182,7 @@ public final class AppPermissionGroupsFragment extends SettingsWithLargeHeader i
Context context = getPreferenceManager().getContext();
mPermissionUsages = new PermissionUsages(context);
- long aggregateDataFilterBeginDays = is7DayToggleEnabled()
+ long aggregateDataFilterBeginDays = KotlinUtils.INSTANCE.is7DayToggleEnabled()
? AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS_7 :
AppPermissionGroupsViewModel.AGGREGATE_DATA_FILTER_BEGIN_DAYS_1;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt
index 5d1d29df5..883ff86d8 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/GrantPermissionsViewHandlerImpl.kt
@@ -19,17 +19,7 @@ package com.android.permissioncontroller.permission.ui.handheld
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.app.Activity
-import android.content.res.Configuration
-import android.graphics.Color
-import android.graphics.ImageDecoder
-import android.graphics.Paint
-import android.graphics.Path
-import android.graphics.PixelFormat
-import android.graphics.PorterDuff
-import android.graphics.PorterDuffXfermode
import android.graphics.Typeface
-import android.graphics.drawable.AnimatedImageDrawable
-import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.os.Bundle
import android.text.method.LinkMovementMethod
@@ -50,18 +40,25 @@ import android.widget.LinearLayout
import android.widget.RadioButton
import android.widget.RadioGroup
import android.widget.TextView
+import androidx.annotation.RawRes
+import com.airbnb.lottie.LottieCompositionFactory
+import com.airbnb.lottie.LottieDrawable
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALL_PHOTOS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_MORE_SELECTED_PHOTOS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_SELECTED_PHOTOS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.COARSE_RADIO_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_BOTH_LOCATIONS
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_COARSE_LOCATION_ONLY
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_FINE_LOCATION_ONLY
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DONT_ALLOW_MORE_SELECTED_PHOTOS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.FINE_RADIO_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LOCATION_ACCURACY_LAYOUT
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_BUTTON
@@ -74,9 +71,11 @@ import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandle
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_MORE_PHOTOS
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_USER_SELECTED
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.ResultListener
class GrantPermissionsViewHandlerImpl(
@@ -87,7 +86,7 @@ class GrantPermissionsViewHandlerImpl(
private val LOCATION_ACCURACY_DIALOGS = listOf(DIALOG_WITH_BOTH_LOCATIONS,
DIALOG_WITH_FINE_LOCATION_ONLY, DIALOG_WITH_COARSE_LOCATION_ONLY)
private val LOCATION_ACCURACY_IMAGE_DIAMETER = mActivity.resources.getDimension(
- R.dimen.location_accuracy_image_size).toInt()
+ R.dimen.location_accuracy_image_size)
// Configuration of the current dialog
private var groupName: String? = null
@@ -96,21 +95,24 @@ class GrantPermissionsViewHandlerImpl(
private var groupIcon: Icon? = null
private var groupMessage: CharSequence? = null
private var detailMessage: CharSequence? = null
+ private var permissionRationaleMessage: CharSequence? = null
private val buttonVisibilities = BooleanArray(NEXT_BUTTON) { false }
private val locationVisibilities = BooleanArray(NEXT_LOCATION_DIALOG) { false }
private var selectedPrecision: Int = 0
private var isLocationPermissionDialogActionClicked: Boolean = false
private var coarseRadioButton: RadioButton? = null
private var fineRadioButton: RadioButton? = null
- private var coarseOffDrawable: AnimatedImageDrawable? = null
- private var coarseOnDrawable: AnimatedImageDrawable? = null
- private var fineOffDrawable: AnimatedImageDrawable? = null
- private var fineOnDrawable: AnimatedImageDrawable? = null
+ private var coarseOffDrawable: LottieDrawable? = null
+ private var coarseOnDrawable: LottieDrawable? = null
+ private var fineOffDrawable: LottieDrawable? = null
+ private var fineOnDrawable: LottieDrawable? = null
// Views
private var iconView: ImageView? = null
private var messageView: TextView? = null
private var detailMessageView: TextView? = null
+ private var permissionRationaleView: View? = null
+ private var permissionRationaleMessageView: TextView? = null
private var buttons: Array<Button?> = arrayOfNulls(NEXT_BUTTON)
private var locationViews: Array<View?> = arrayOfNulls(NEXT_LOCATION_DIALOG)
private var rootView: ViewGroup? = null
@@ -122,6 +124,8 @@ class GrantPermissionsViewHandlerImpl(
arguments.putParcelable(ARG_GROUP_ICON, groupIcon)
arguments.putCharSequence(ARG_GROUP_MESSAGE, groupMessage)
arguments.putCharSequence(ARG_GROUP_DETAIL_MESSAGE, detailMessage)
+ arguments.putCharSequence(ARG_GROUP_PERMISSION_RATIONALE_MESSAGE,
+ permissionRationaleMessage)
arguments.putBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES, buttonVisibilities)
arguments.putBooleanArray(ARG_DIALOG_LOCATION_VISIBILITIES, locationVisibilities)
arguments.putInt(ARG_DIALOG_SELECTED_PRECISION, selectedPrecision)
@@ -134,6 +138,8 @@ class GrantPermissionsViewHandlerImpl(
groupCount = savedInstanceState.getInt(ARG_GROUP_COUNT)
groupIndex = savedInstanceState.getInt(ARG_GROUP_INDEX)
detailMessage = savedInstanceState.getCharSequence(ARG_GROUP_DETAIL_MESSAGE)
+ permissionRationaleMessage =
+ savedInstanceState.getCharSequence(ARG_GROUP_PERMISSION_RATIONALE_MESSAGE)
setButtonVisibilities(savedInstanceState.getBooleanArray(ARG_DIALOG_BUTTON_VISIBILITIES))
setLocationVisibilities(savedInstanceState.getBooleanArray(
ARG_DIALOG_LOCATION_VISIBILITIES))
@@ -143,14 +149,15 @@ class GrantPermissionsViewHandlerImpl(
}
override fun updateUi(
- groupName: String,
+ groupName: String?,
groupCount: Int,
groupIndex: Int,
icon: Icon?,
message: CharSequence?,
detailMessage: CharSequence?,
- buttonVisibilities: BooleanArray,
- locationVisibilities: BooleanArray
+ permissionRationaleMessage: CharSequence?,
+ buttonVisibilities: BooleanArray?,
+ locationVisibilities: BooleanArray?
) {
this.groupName = groupName
@@ -159,6 +166,7 @@ class GrantPermissionsViewHandlerImpl(
groupIcon = icon
groupMessage = message
this.detailMessage = detailMessage
+ this.permissionRationaleMessage = permissionRationaleMessage
setButtonVisibilities(buttonVisibilities)
setLocationVisibilities(locationVisibilities)
@@ -171,11 +179,12 @@ class GrantPermissionsViewHandlerImpl(
private fun updateAll() {
updateDescription()
updateDetailDescription()
+ updatePermissionRationale()
updateButtons()
updateLocationVisibilities()
- // Animate change in size
- // Grow or shrink the content container to size of new content
+ // Animate change in size
+ // Grow or shrink the content container to size of new content
val growShrinkToNewContentSize = ChangeBounds()
growShrinkToNewContentSize.duration = ANIMATION_DURATION_MILLIS
growShrinkToNewContentSize.interpolator = AnimationUtils.loadInterpolator(mActivity,
@@ -213,6 +222,10 @@ class GrantPermissionsViewHandlerImpl(
detailMessageView!!.movementMethod = LinkMovementMethod.getInstance()
iconView = rootView.findViewById(R.id.permission_icon)
+ permissionRationaleView = rootView.findViewById(R.id.permission_rationale_container)
+ permissionRationaleMessageView = rootView.findViewById(R.id.permission_rationale_message)
+ permissionRationaleView!!.setOnClickListener(this)
+
val buttons = arrayOfNulls<Button>(NEXT_BUTTON)
val numButtons = BUTTON_RES_ID_TO_NUM.size()
for (i in 0 until numButtons) {
@@ -227,6 +240,7 @@ class GrantPermissionsViewHandlerImpl(
val locationView = rootView.findViewById<View>(LOCATION_RES_ID_TO_NUM.keyAt(i))
locationViews[LOCATION_RES_ID_TO_NUM.valueAt(i)] = locationView
}
+
initializeAnimatedImages()
// Set location accuracy radio buttons' click listeners
@@ -243,44 +257,26 @@ class GrantPermissionsViewHandlerImpl(
return rootView
}
- private fun initializeAnimatedImages() {
- val isDarkMode = (mActivity.resources.configuration.uiMode and
- Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
- val coarseOffDrawableId = if (isDarkMode) R.drawable.coarse_off_dark
- else R.drawable.coarse_off_light
- val coarseOnDrawableId = if (isDarkMode) R.drawable.coarse_on_dark
- else R.drawable.coarse_on_light
- val fineOffDrawableId = if (isDarkMode) R.drawable.fine_off_dark
- else R.drawable.fine_off_light
- val fineOnDrawableId = if (isDarkMode) R.drawable.fine_on_dark else R.drawable.fine_on_light
-
- coarseOffDrawable = getDrawableFromId(coarseOffDrawableId) as AnimatedImageDrawable
- coarseOnDrawable = getDrawableFromId(coarseOnDrawableId) as AnimatedImageDrawable
- fineOffDrawable = getDrawableFromId(fineOffDrawableId) as AnimatedImageDrawable
- fineOnDrawable = getDrawableFromId(fineOnDrawableId) as AnimatedImageDrawable
- }
-
- private fun getDrawableFromId(drawableId: Int): Drawable {
- val source = ImageDecoder.createSource(mActivity.resources, drawableId)
- return ImageDecoder.decodeDrawable(source) { decoder, _, _ ->
- decoder.setTargetSize(LOCATION_ACCURACY_IMAGE_DIAMETER,
- LOCATION_ACCURACY_IMAGE_DIAMETER)
- decoder.setPostProcessor { canvas ->
- // This will crop the image to circle image.
- val path = Path()
- path.fillType = Path.FillType.INVERSE_EVEN_ODD
- val width: Int = canvas.width
- val height: Int = canvas.height
- path.addRoundRect(0f, 0f, width.toFloat(), height.toFloat(),
- width.toFloat() / 2, height.toFloat() / 2, Path.Direction.CW)
- val paint = Paint()
- paint.isAntiAlias = true
- paint.color = Color.TRANSPARENT
- paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC)
- canvas.drawPath(path, paint)
- PixelFormat.TRANSLUCENT
+ private fun getLottieDrawable(@RawRes rawResId: Int): LottieDrawable {
+ val composition = LottieCompositionFactory.fromRawResSync(mActivity, rawResId).value!!
+ val scale = LOCATION_ACCURACY_IMAGE_DIAMETER / composition.bounds.width()
+ val drawable = object : LottieDrawable() {
+ override fun getIntrinsicHeight(): Int {
+ return (super.getIntrinsicHeight() * scale).toInt()
+ }
+ override fun getIntrinsicWidth(): Int {
+ return (super.getIntrinsicWidth() * scale).toInt()
}
}
+ drawable.composition = composition
+ return drawable
+ }
+
+ private fun initializeAnimatedImages() {
+ coarseOffDrawable = getLottieDrawable(R.raw.coarse_loc_off)
+ coarseOnDrawable = getLottieDrawable(R.raw.coarse_loc_on)
+ fineOffDrawable = getLottieDrawable(R.raw.fine_loc_off)
+ fineOnDrawable = getLottieDrawable(R.raw.fine_loc_on)
}
override fun updateWindowAttributes(outLayoutParams: LayoutParams) {
@@ -323,6 +319,16 @@ class GrantPermissionsViewHandlerImpl(
}
}
+ private fun updatePermissionRationale() {
+ val message = permissionRationaleMessage
+ if (message == null || message.isEmpty()) {
+ permissionRationaleView!!.visibility = View.GONE
+ } else {
+ permissionRationaleMessageView!!.text = message
+ permissionRationaleView!!.visibility = View.VISIBLE
+ }
+ }
+
private fun updateButtons() {
for (i in 0 until BUTTON_RES_ID_TO_NUM.size()) {
val pos = BUTTON_RES_ID_TO_NUM.valueAt(i)
@@ -420,8 +426,8 @@ class GrantPermissionsViewHandlerImpl(
null, null)
fineRadioButton?.setCompoundDrawablesWithIntrinsicBounds(null, fineOffDrawable,
null, null)
- coarseOnDrawable?.start()
fineOffDrawable?.start()
+ coarseOnDrawable?.start()
coarseRadioButton?.setTypeface(null, Typeface.BOLD)
fineRadioButton?.setTypeface(null, Typeface.NORMAL)
}
@@ -430,6 +436,11 @@ class GrantPermissionsViewHandlerImpl(
override fun onClick(view: View) {
val id = view.id
+ if (id == R.id.permission_rationale_container) {
+ resultListener.onPermissionRationaleClicked(groupName)
+ return
+ }
+
if (id == R.id.permission_location_accuracy_radio_fine) {
(locationViews[FINE_RADIO_BUTTON] as RadioButton).isChecked = true
selectedPrecision = FINE_RADIO_BUTTON
@@ -469,6 +480,7 @@ class GrantPermissionsViewHandlerImpl(
}
when (BUTTON_RES_ID_TO_NUM.get(id, -1)) {
+ ALLOW_ALL_PHOTOS_BUTTON,
ALLOW_BUTTON -> {
view.performAccessibilityAction(
AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
@@ -493,6 +505,16 @@ class GrantPermissionsViewHandlerImpl(
resultListener.onPermissionGrantResult(groupName, affectedForegroundPermissions,
GRANTED_ONE_TIME)
}
+ ALLOW_SELECTED_PHOTOS_BUTTON, ALLOW_MORE_SELECTED_PHOTOS_BUTTON -> {
+ view.performAccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
+ resultListener.onPermissionGrantResult(groupName, affectedForegroundPermissions,
+ GRANTED_USER_SELECTED)
+ }
+ DONT_ALLOW_MORE_SELECTED_PHOTOS_BUTTON -> {
+ resultListener.onPermissionGrantResult(groupName, affectedForegroundPermissions,
+ DENIED_MORE_PHOTOS)
+ }
DENY_BUTTON, NO_UPGRADE_BUTTON, NO_UPGRADE_OT_BUTTON -> {
view.performAccessibilityAction(
AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null)
@@ -522,6 +544,7 @@ class GrantPermissionsViewHandlerImpl(
}
companion object {
+ private val TAG = GrantPermissionsViewHandlerImpl::class.java.simpleName
const val ARG_GROUP_NAME = "ARG_GROUP_NAME"
const val ARG_GROUP_COUNT = "ARG_GROUP_COUNT"
@@ -529,6 +552,8 @@ class GrantPermissionsViewHandlerImpl(
const val ARG_GROUP_ICON = "ARG_GROUP_ICON"
const val ARG_GROUP_MESSAGE = "ARG_GROUP_MESSAGE"
private const val ARG_GROUP_DETAIL_MESSAGE = "ARG_GROUP_DETAIL_MESSAGE"
+ private const val ARG_GROUP_PERMISSION_RATIONALE_MESSAGE =
+ "ARG_GROUP_PERMISSION_RATIONALE_MESSAGE"
private const val ARG_DIALOG_BUTTON_VISIBILITIES = "ARG_DIALOG_BUTTON_VISIBILITIES"
private const val ARG_DIALOG_LOCATION_VISIBILITIES = "ARG_DIALOG_LOCATION_VISIBILITIES"
private const val ARG_DIALOG_SELECTED_PRECISION = "ARG_DIALOG_SELECTED_PRECISION"
@@ -557,6 +582,14 @@ class GrantPermissionsViewHandlerImpl(
NO_UPGRADE_OT_BUTTON)
BUTTON_RES_ID_TO_NUM.put(R.id.permission_no_upgrade_one_time_and_dont_ask_again_button,
NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_all_photos_button,
+ ALLOW_ALL_PHOTOS_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_selected_photos_button,
+ ALLOW_SELECTED_PHOTOS_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_allow_more_selected_photos_button,
+ ALLOW_MORE_SELECTED_PHOTOS_BUTTON)
+ BUTTON_RES_ID_TO_NUM.put(R.id.permission_dont_allow_more_selected_photos_button,
+ DONT_ALLOW_MORE_SELECTED_PHOTOS_BUTTON)
LOCATION_RES_ID_TO_NUM.put(R.id.permission_location_accuracy, LOCATION_ACCURACY_LAYOUT)
LOCATION_RES_ID_TO_NUM.put(R.id.permission_location_accuracy_radio_fine,
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java
index 879e6082a..8e3192eee 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ManageStandardPermissionsFragment.java
@@ -20,7 +20,6 @@ import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory;
import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack;
-import static com.android.permissioncontroller.permission.ui.handheld.v31.DashboardUtilsKt.shouldShowPermissionsDashboard;
import android.app.Application;
import android.content.Intent;
@@ -39,6 +38,7 @@ import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity;
import com.android.permissioncontroller.permission.ui.UnusedAppsFragment;
import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModel;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.StringUtils;
import com.android.permissioncontroller.permission.utils.Utils;
import com.android.settingslib.widget.FooterPreference;
@@ -77,6 +77,9 @@ public final class ManageStandardPermissionsFragment extends ManagePermissionsFr
mPermissionGroups = mViewModel.getUiDataLiveData().getValue();
mViewModel.getUiDataLiveData().observe(this, permissionGroups -> {
+ // Once we have loaded data for the first time, further loads should be staggered,
+ // for performance reasons.
+ mViewModel.getUiDataLiveData().setLoadStaggered(true);
if (permissionGroups != null) {
mPermissionGroups = permissionGroups;
updatePermissionsUi();
@@ -84,6 +87,11 @@ public final class ManageStandardPermissionsFragment extends ManagePermissionsFr
Log.e(LOG_TAG, "ViewModel returned null data, exiting");
getActivity().finishAfterTransition();
}
+
+ // If we've loaded all LiveDatas, no need to prioritize loading any particular one
+ if (!mViewModel.getUiDataLiveData().isStale()) {
+ mViewModel.getUiDataLiveData().setFirstLoadGroup(null);
+ }
});
mViewModel.getNumCustomPermGroups().observe(this, permNames -> updatePermissionsUi());
@@ -115,7 +123,7 @@ public final class ManageStandardPermissionsFragment extends ManagePermissionsFr
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
- if (shouldShowPermissionsDashboard()) {
+ if (KotlinUtils.INSTANCE.shouldShowPermissionsDashboard()) {
menu.add(Menu.NONE, MENU_PERMISSION_USAGE, Menu.NONE, R.string.permission_usage_title);
}
}
@@ -209,6 +217,9 @@ public final class ManageStandardPermissionsFragment extends ManagePermissionsFr
@Override
public void showPermissionApps(String permissionGroupName) {
+ // If we return to this page within a reasonable time, prioritize loading data from the
+ // permission group whose page we are going to, as that is group most likely to have changed
+ mViewModel.getUiDataLiveData().setFirstLoadGroup(permissionGroupName);
mViewModel.showPermissionApps(this, PermissionAppsFragment.createArgs(
permissionGroupName, getArguments().getLong(EXTRA_SESSION_ID)));
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
index 1d92a6e74..90d7204cf 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java
@@ -22,7 +22,6 @@ import static com.android.permissioncontroller.permission.ui.Category.ALLOWED_FO
import static com.android.permissioncontroller.permission.ui.Category.ASK;
import static com.android.permissioncontroller.permission.ui.Category.DENIED;
import static com.android.permissioncontroller.permission.ui.handheld.UtilsKt.pressBack;
-import static com.android.permissioncontroller.permission.ui.handheld.v31.DashboardUtilsKt.shouldShowPermissionsDashboard;
import android.Manifest;
import android.app.ActionBar;
@@ -197,7 +196,7 @@ public final class PermissionAppsFragment extends SettingsWithLargeHeader implem
updateMenu(mViewModel.getShouldShowSystemLiveData().getValue());
}
- if (shouldShowPermissionsDashboard()) {
+ if (KotlinUtils.INSTANCE.shouldShowPermissionsDashboard()) {
menu.add(Menu.NONE, MENU_PERMISSION_USAGE, Menu.NONE, R.string.permission_usage_title);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionControlPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionControlPreference.java
index 31ef791b6..814adf6ee 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionControlPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionControlPreference.java
@@ -16,6 +16,8 @@
package com.android.permissioncontroller.permission.ui.handheld;
+import static android.healthconnect.HealthPermissions.HEALTH_PERMISSION_GROUP;
+
import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
import static com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_CALLER_NAME;
import static com.android.permissioncontroller.permission.ui.handheld.AppPermissionFragment.GRANT_CATEGORY;
@@ -221,6 +223,11 @@ public class PermissionControlPreference extends Preference {
Utils.navigateToAppNotificationSettings(mContext, mPackageName, mUser);
return true;
}
+ if (Utils.isHealthPermissionUiEnabled()
+ && mPermGroupName.equals(HEALTH_PERMISSION_GROUP)) {
+ Utils.navigateToAppHealthConnectSettings(mContext, mPackageName, mUser);
+ return true;
+ }
Bundle args = new Bundle();
args.putString(Intent.EXTRA_PACKAGE_NAME, mPackageName);
args.putString(Intent.EXTRA_PERMISSION_GROUP_NAME, mPermGroupName);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.java
index 9569baeeb..658a82af5 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionPreference.java
@@ -36,10 +36,10 @@ import androidx.preference.PreferenceFragmentCompat;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup;
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel;
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.PermissionSummary;
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.PermissionTarget;
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.SummaryMessage;
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel;
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel.PermissionSummary;
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel.PermissionTarget;
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel.SummaryMessage;
import com.android.permissioncontroller.permission.utils.LocationUtils;
import com.android.permissioncontroller.permission.utils.Utils;
import com.android.settingslib.RestrictedLockUtils;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewPermissionsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewPermissionsFragment.java
index a6f74c822..5e5c221ae 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewPermissionsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/ReviewPermissionsFragment.java
@@ -57,9 +57,9 @@ import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup;
import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission;
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity;
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionViewModelFactory;
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel;
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.PermissionTarget;
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionViewModelFactory;
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel;
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel.PermissionTarget;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.Utils;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/DashboardUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/DashboardUtils.kt
index 19b19f620..5b92dd36d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/DashboardUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/DashboardUtils.kt
@@ -19,7 +19,6 @@ package com.android.permissioncontroller.permission.ui.handheld.v31
import android.content.Context
import android.icu.util.Calendar
import android.os.Build
-import android.provider.DeviceConfig
import android.text.format.DateFormat.getMediumDateFormat
import android.text.format.DateFormat.getTimeFormat
import android.util.Pair
@@ -30,58 +29,12 @@ import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage.
import com.android.permissioncontroller.permission.utils.StringUtils
import java.util.Locale
-/** Whether to show the Permissions Hub. */
-private const val PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled"
-
-/** Whether to show the mic and camera icons. */
-const val PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled"
-
-/** Whether to show the location indicators. */
-const val PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled"
-
-/* Whether location accuracy feature is enabled */
-const val PROPERTY_LOCATION_ACCURACY_ENABLED = "location_accuracy_enabled"
-
-/** Whether to show 7-day toggle in privacy hub. */
-private const val PRIVACY_DASHBOARD_7_DAY_TOGGLE = "privacy_dashboard_7_day_toggle"
-
-/* Default location precision */
-const val PROPERTY_LOCATION_PRECISION = "location_precision"
-
const val SECONDS = 1
const val MINUTES = 2
const val HOURS = 3
const val DAYS = 4
/**
- * Whether the Permissions Hub 2 flag is enabled
- *
- * @return whether the flag is enabled
- */
-fun isPermissionsHub2FlagEnabled(): Boolean {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_PERMISSIONS_HUB_2_ENABLED, false)
-}
-/**
- * Whether to show the Permissions Dashboard
- *
- * @return whether to show the Permissions Dashboard.
- */
-fun shouldShowPermissionsDashboard(): Boolean {
- return isPermissionsHub2FlagEnabled()
-}
-
-/**
- * Whether we should enable the 7-day toggle in privacy dashboard
- *
- * @return whether the flag is enabled
- */
-fun is7DayToggleEnabled(): Boolean {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PRIVACY_DASHBOARD_7_DAY_TOGGLE, false)
-}
-
-/**
* Whether to show the subattribution in the Permissions Dashboard
*
* @return whether to show subattribution in the Permissions Dashboard.
@@ -91,62 +44,6 @@ fun shouldShowSubattributionInPermissionsDashboard(): Boolean {
}
/**
- * Whether the Camera and Mic Icons are enabled by flag.
- *
- * @return whether the Camera and Mic Icons are enabled.
- */
-fun isCameraMicIconsFlagEnabled(): Boolean {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_CAMERA_MIC_ICONS_ENABLED, true)
-}
-
-/**
- * Whether to show Camera and Mic Icons. They should be shown if the permission hub, or the icons
- * specifically, are enabled.
- *
- * @return whether to show the icons.
- */
-fun shouldShowCameraMicIndicators(): Boolean {
- return isCameraMicIconsFlagEnabled() || isPermissionsHub2FlagEnabled()
-}
-
-/**
- * Whether the location indicators are enabled by flag.
- *
- * @return whether the location indicators are enabled by flag.
- */
-fun isLocationIndicatorsFlagEnabled(): Boolean {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_LOCATION_INDICATORS_ENABLED, false)
-}
-
-/**
- * Whether to show the location indicators. The location indicators are enable if the
- * permission hub, or location indicator specifically are enabled.
- */
-fun shouldShowLocationIndicators(): Boolean {
- return isLocationIndicatorsFlagEnabled() || isPermissionsHub2FlagEnabled()
-}
-
-/**
- * Whether the location accuracy feature is enabled
- */
-fun isLocationAccuracyEnabled(): Boolean {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_LOCATION_ACCURACY_ENABLED, true)
-}
-
-/**
- * Default state of location precision
- * true: default is FINE.
- * false: default is COARSE.
- */
-fun getDefaultPrecision(): Boolean {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
- PROPERTY_LOCATION_PRECISION, true)
-}
-
-/**
* Build a string representing the given time if it happened on the current day and the date
* otherwise.
*
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionDetailsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionDetailsFragment.java
index 9556d1590..15ccd8d64 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionDetailsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionDetailsFragment.java
@@ -18,11 +18,9 @@ package com.android.permissioncontroller.permission.ui.handheld.v31;
import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
import static com.android.permissioncontroller.Constants.INVALID_SESSION_ID;
-import static com.android.permissioncontroller.permission.ui.handheld.v31.DashboardUtilsKt.is7DayToggleEnabled;
import android.app.ActionBar;
import android.app.Activity;
-import android.app.role.RoleManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
@@ -30,6 +28,8 @@ import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
+import android.text.format.DateFormat;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -37,7 +37,6 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
@@ -49,84 +48,86 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.permissioncontroller.PermissionControllerApplication;
import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.permission.model.legacy.PermissionApps;
-import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage;
-import com.android.permissioncontroller.permission.model.v31.PermissionUsages;
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity;
import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader;
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel;
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelFactory;
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelNew;
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelNew.AppPermissionAccessUiInfo;
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelNew.PermissionUsageDetailsUiInfo;
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelNew.PermissionUsageDetailsViewModelNewFactory;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
-import com.android.permissioncontroller.permission.utils.Utils;
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
-import java.util.ArrayList;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
-/**
- * The permission details page showing the history/timeline of a permission
- */
+/** The permission details page showing the history/timeline of a permission */
@RequiresApi(Build.VERSION_CODES.S)
-public class PermissionDetailsFragment extends SettingsWithLargeHeader implements
- PermissionUsages.PermissionsUsagesChangeCallback {
-
- public static final int FILTER_7_DAYS = 1;
- private static final String KEY_SHOW_SYSTEM_PREFS = "_show_system";
- private static final String SHOW_SYSTEM_KEY = PermissionDetailsFragment.class.getName()
- + KEY_SHOW_SYSTEM_PREFS;
-
+public class PermissionDetailsFragment extends SettingsWithLargeHeader {
private static final String KEY_SESSION_ID = "_session_id";
- private static final String SESSION_ID_KEY = PermissionDetailsFragment.class.getName()
- + KEY_SESSION_ID;
+ private static final String SESSION_ID_KEY =
+ PermissionDetailsFragment.class.getName() + KEY_SESSION_ID;
+ private static final String TAG = PermissionDetailsFragment.class.getName();
private static final int MENU_SHOW_7_DAYS_DATA = Menu.FIRST + 4;
private static final int MENU_SHOW_24_HOURS_DATA = Menu.FIRST + 5;
-
- private @Nullable String mFilterGroup;
- private int mFilterTimeIndex;
- private @Nullable List<AppPermissionUsage> mAppPermissionUsages = new ArrayList<>();
- private @NonNull PermissionUsages mPermissionUsages;
- private boolean mFinishedInitialLoad;
-
- private boolean mShowSystem;
+ private static final long MIDNIGHT_TODAY =
+ ZonedDateTime.now(ZoneId.systemDefault()).truncatedTo(ChronoUnit.DAYS).toEpochSecond()
+ * 1000L;
+ private static final long MIDNIGHT_YESTERDAY =
+ ZonedDateTime.now(ZoneId.systemDefault())
+ .minusDays(1)
+ .truncatedTo(ChronoUnit.DAYS)
+ .toEpochSecond()
+ * 1000L;
+ private @Nullable String mPermissionGroup;
+ private int mUsageSubtitle;
private boolean mHasSystemApps;
- private boolean mShow7Days;
private MenuItem mShowSystemMenu;
private MenuItem mHideSystemMenu;
private MenuItem mShow7DaysDataMenu;
private MenuItem mShow24HoursDataMenu;
- private @NonNull RoleManager mRoleManager;
- private PermissionUsageDetailsViewModel mViewModel;
+ private PermissionUsageDetailsViewModelNew mViewModel;
private long mSessionId;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mPermissionGroup = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
+
+ if (mPermissionGroup == null) {
+ Log.e(TAG, "No permission group was provided for PermissionDetailsFragment");
+ return;
+ }
- mFinishedInitialLoad = false;
- mFilterTimeIndex = FILTER_7_DAYS;
+ PermissionUsageDetailsViewModelNewFactory factory =
+ new PermissionUsageDetailsViewModelNewFactory(
+ PermissionControllerApplication.get(),
+ this,
+ mPermissionGroup);
+ mViewModel =
+ new ViewModelProvider(this, factory).get(PermissionUsageDetailsViewModelNew.class);
if (savedInstanceState != null) {
- mShowSystem = savedInstanceState.getBoolean(SHOW_SYSTEM_KEY);
mSessionId = savedInstanceState.getLong(SESSION_ID_KEY);
} else {
- mShowSystem = getArguments().getBoolean(
- ManagePermissionsActivity.EXTRA_SHOW_SYSTEM, false);
- mShow7Days = is7DayToggleEnabled() && getArguments().getBoolean(
- ManagePermissionsActivity.EXTRA_SHOW_7_DAYS, false);
mSessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
}
- if (mFilterGroup == null) {
- mFilterGroup = getArguments().getString(Intent.EXTRA_PERMISSION_GROUP_NAME);
- }
+ mViewModel.updateShowSystem(
+ getArguments().getBoolean(ManagePermissionsActivity.EXTRA_SHOW_SYSTEM, false));
+ mViewModel.updateShow7Days(
+ KotlinUtils.INSTANCE.is7DayToggleEnabled()
+ && getArguments()
+ .getBoolean(ManagePermissionsActivity.EXTRA_SHOW_7_DAYS, false));
setHasOptionsMenu(true);
ActionBar ab = getActivity().getActionBar();
@@ -134,56 +135,56 @@ public class PermissionDetailsFragment extends SettingsWithLargeHeader implement
ab.setDisplayHomeAsUpEnabled(true);
}
- Context context = getPreferenceManager().getContext();
-
- mPermissionUsages = new PermissionUsages(context);
- mRoleManager = Utils.getSystemServiceSafe(context, RoleManager.class);
-
- PermissionUsageDetailsViewModelFactory factory = new PermissionUsageDetailsViewModelFactory(
- PermissionControllerApplication.get(), mRoleManager, mFilterGroup, mSessionId);
- mViewModel = new ViewModelProvider(this, factory).get(
- PermissionUsageDetailsViewModel.class);
-
- reloadData();
+ mViewModel.getPermissionUsagesDetailsInfoUiLiveData().observe(this, this::updateUI);
+ mViewModel.getShowSystemLiveData().observe(this, this::updateShowSystem);
+ mViewModel.getShow7DaysLiveData().observe(this, this::updateShow7Days);
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- ViewGroup rootView = (ViewGroup) super.onCreateView(inflater, container,
- savedInstanceState);
+ public View onCreateView(
+ LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ ViewGroup rootView =
+ (ViewGroup) super.onCreateView(inflater, container, savedInstanceState);
- PermissionDetailsWrapperFragment parentFragment = (PermissionDetailsWrapperFragment)
- requireParentFragment();
+ PermissionDetailsWrapperFragment parentFragment =
+ (PermissionDetailsWrapperFragment) requireParentFragment();
CoordinatorLayout coordinatorLayout = parentFragment.getCoordinatorLayout();
inflater.inflate(R.layout.permission_details_extended_fab, coordinatorLayout);
- ExtendedFloatingActionButton extendedFab = coordinatorLayout.requireViewById(
- R.id.extended_fab);
+ ExtendedFloatingActionButton extendedFab =
+ coordinatorLayout.requireViewById(R.id.extended_fab);
// Load the background tint color from the application theme
// rather than the Material Design theme
Activity activity = getActivity();
- ColorStateList backgroundColor = activity.getColorStateList(
- android.R.color.system_accent3_100);
+ ColorStateList backgroundColor =
+ activity.getColorStateList(android.R.color.system_accent3_100);
extendedFab.setBackgroundTintList(backgroundColor);
extendedFab.setText(R.string.manage_permission);
- boolean isUiModeNight = (activity.getResources().getConfiguration().uiMode
- & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
- int textColorAttr = isUiModeNight ? android.R.attr.textColorPrimaryInverse
- : android.R.attr.textColorPrimary;
- TypedArray typedArray = activity.obtainStyledAttributes(new int[] { textColorAttr });
+ boolean isUiModeNight =
+ (activity.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+ int textColorAttr =
+ isUiModeNight
+ ? android.R.attr.textColorPrimaryInverse
+ : android.R.attr.textColorPrimary;
+ TypedArray typedArray = activity.obtainStyledAttributes(new int[] {textColorAttr});
ColorStateList textColor = typedArray.getColorStateList(0);
typedArray.recycle();
extendedFab.setTextColor(textColor);
extendedFab.setIcon(activity.getDrawable(R.drawable.ic_settings_outline));
extendedFab.setVisibility(View.VISIBLE);
- extendedFab.setOnClickListener(view -> {
- Intent intent = new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS)
- .putExtra(Intent.EXTRA_PERMISSION_NAME, mFilterGroup);
- startActivity(intent);
- });
+ extendedFab.setOnClickListener(
+ view -> {
+ Intent intent =
+ new Intent(Intent.ACTION_MANAGE_PERMISSION_APPS)
+ .putExtra(Intent.EXTRA_PERMISSION_NAME, mPermissionGroup);
+ startActivity(intent);
+ });
RecyclerView recyclerView = getListView();
- int bottomPadding = getResources()
- .getDimensionPixelSize(R.dimen.privhub_details_recycler_view_bottom_padding);
+ int bottomPadding =
+ getResources()
+ .getDimensionPixelSize(
+ R.dimen.privhub_details_recycler_view_bottom_padding);
recyclerView.setPadding(0, 0, 0, bottomPadding);
recyclerView.setClipToPadding(false);
recyclerView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
@@ -195,72 +196,43 @@ public class PermissionDetailsFragment extends SettingsWithLargeHeader implement
public void onStart() {
super.onStart();
CharSequence title = getString(R.string.permission_history_title);
- if (mFilterGroup != null) {
- title = getResources().getString(R.string.permission_group_usage_title,
- KotlinUtils.INSTANCE.getPermGroupLabel(getActivity(), mFilterGroup));
+ if (mPermissionGroup != null) {
+ title =
+ getResources()
+ .getString(
+ R.string.permission_group_usage_title,
+ KotlinUtils.INSTANCE.getPermGroupLabel(
+ getActivity(), mPermissionGroup));
}
getActivity().setTitle(title);
}
@Override
- public void onPermissionUsagesChanged() {
- if (mPermissionUsages.getUsages().isEmpty()) {
- return;
- }
- mAppPermissionUsages = new ArrayList<>(mPermissionUsages.getUsages());
-
- // Ensure the group name is valid.
- if (mViewModel.getGroup(mFilterGroup, mAppPermissionUsages) == null) {
- mFilterGroup = null;
- }
-
- updateUI();
- }
-
- @Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putBoolean(SHOW_SYSTEM_KEY, mShowSystem);
outState.putLong(SESSION_ID_KEY, mSessionId);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
- R.string.menu_show_system);
- mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
- R.string.menu_hide_system);
- if (is7DayToggleEnabled()) {
- mShow7DaysDataMenu = menu.add(Menu.NONE, MENU_SHOW_7_DAYS_DATA, Menu.NONE,
- R.string.menu_show_7_days_data);
- mShow24HoursDataMenu = menu.add(Menu.NONE, MENU_SHOW_24_HOURS_DATA, Menu.NONE,
- R.string.menu_show_24_hours_data);
- }
-
- updateMenu();
- }
-
- private void updateMenu() {
- if (mHasSystemApps) {
- mShowSystemMenu.setVisible(!mShowSystem);
- mShowSystemMenu.setEnabled(true);
-
- mHideSystemMenu.setVisible(mShowSystem);
- mHideSystemMenu.setEnabled(true);
- } else {
- mShowSystemMenu.setVisible(true);
- mShowSystemMenu.setEnabled(false);
-
- mHideSystemMenu.setVisible(false);
- mHideSystemMenu.setEnabled(false);
- }
-
- if (mShow7DaysDataMenu != null) {
- mShow7DaysDataMenu.setVisible(!mShow7Days);
- }
-
- if (mShow24HoursDataMenu != null) {
- mShow24HoursDataMenu.setVisible(mShow7Days);
+ mShowSystemMenu =
+ menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE, R.string.menu_show_system);
+ mHideSystemMenu =
+ menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE, R.string.menu_hide_system);
+
+ if (KotlinUtils.INSTANCE.is7DayToggleEnabled()) {
+ mShow7DaysDataMenu =
+ menu.add(
+ Menu.NONE,
+ MENU_SHOW_7_DAYS_DATA,
+ Menu.NONE,
+ R.string.menu_show_7_days_data);
+ mShow24HoursDataMenu =
+ menu.add(
+ Menu.NONE,
+ MENU_SHOW_24_HOURS_DATA,
+ Menu.NONE,
+ R.string.menu_show_24_hours_data);
}
}
@@ -272,25 +244,24 @@ public class PermissionDetailsFragment extends SettingsWithLargeHeader implement
getActivity().finishAfterTransition();
return true;
case MENU_SHOW_SYSTEM:
+ mViewModel.updateShowSystem(true);
+ break;
case MENU_HIDE_SYSTEM:
- mShowSystem = itemId == MENU_SHOW_SYSTEM;
- // We already loaded all data, so don't reload
- updateUI();
- updateMenu();
+ mViewModel.updateShowSystem(false);
break;
case MENU_SHOW_7_DAYS_DATA:
+ mViewModel.updateShow7Days(KotlinUtils.INSTANCE.is7DayToggleEnabled());
+ break;
case MENU_SHOW_24_HOURS_DATA:
- mShow7Days = is7DayToggleEnabled() && itemId == MENU_SHOW_7_DAYS_DATA;
- updateUI();
- updateMenu();
+ mViewModel.updateShow7Days(false);
break;
}
return super.onOptionsItemSelected(item);
}
- private void updateUI() {
- if (mAppPermissionUsages.isEmpty() || getActivity() == null) {
+ private void updateUI(PermissionUsageDetailsUiInfo uiData) {
+ if (getActivity() == null) {
return;
}
Context context = getActivity();
@@ -301,96 +272,143 @@ public class PermissionDetailsFragment extends SettingsWithLargeHeader implement
}
screen.removeAll();
- Set<String> exemptedPackages = Utils.getExemptedPackages(mRoleManager);
-
Preference subtitlePreference = new Preference(context);
+ mUsageSubtitle =
+ uiData.getShow7Days()
+ ? R.string.permission_group_usage_subtitle_7d
+ : R.string.permission_group_usage_subtitle_24h;
- int usageSubtitle = mShow7Days
- ? R.string.permission_group_usage_subtitle_7d
- : R.string.permission_group_usage_subtitle_24h;
subtitlePreference.setSummary(
- getResources().getString(usageSubtitle,
- KotlinUtils.INSTANCE.getPermGroupLabel(getActivity(), mFilterGroup)));
+ getResources()
+ .getString(
+ mUsageSubtitle,
+ KotlinUtils.INSTANCE.getPermGroupLabel(
+ getActivity(), mPermissionGroup)));
subtitlePreference.setSelectable(false);
screen.addPreference(subtitlePreference);
- AtomicBoolean seenSystemApp = new AtomicBoolean(false);
+ boolean seenSystemApp = uiData.getShouldDisplayShowSystemToggle();
- ArrayList<PermissionApps.PermissionApp> permApps = new ArrayList<>();
- List<PermissionUsageDetailsViewModel.AppPermissionUsageEntry> usages =
- mViewModel.parseUsages(mAppPermissionUsages, exemptedPackages, permApps,
- seenSystemApp, mShowSystem, mShow7Days);
-
- if (mHasSystemApps != seenSystemApp.get()) {
- mHasSystemApps = seenSystemApp.get();
+ if (mHasSystemApps != seenSystemApp) {
+ mHasSystemApps = seenSystemApp;
getActivity().invalidateOptionsMenu();
}
// Make these variables effectively final so that
// we can use these captured variables in the below lambda expression
- PreferenceFactory preferenceFactory = new PreferenceFactory(requireActivity());
- AtomicReference<PreferenceCategory> category = new AtomicReference<>(
- preferenceFactory.createDayCategoryPreference());
+ AtomicReference<PreferenceCategory> category =
+ new AtomicReference<>(createDayCategoryPreference());
screen.addPreference(category.get());
PreferenceScreen finalScreen = screen;
- new PermissionApps.AppDataLoader(context, () -> {
- if (getActivity() == null) {
- // Fragment has no Activity, return.
- return;
- }
- mViewModel.renderTimelinePreferences(usages, category, finalScreen, preferenceFactory);
+ if (getActivity() == null) {
+ // Fragment has no Activity, return.
+ return;
+ }
+ renderHistoryPreferences(uiData.getAppPermissionAccessUiInfoList(), category, finalScreen);
- setLoading(false, true);
- mFinishedInitialLoad = true;
- setProgressBarVisible(false);
- mPermissionUsages.stopLoader(getActivity().getLoaderManager());
+ setLoading(false, true);
+ setProgressBarVisible(false);
+ }
- }).execute(permApps.toArray(new PermissionApps.PermissionApp[permApps.size()]));
+ /** Render the provided appPermissionAccessUiInfoList into the [preferenceScreen] UI. */
+ private void renderHistoryPreferences(
+ List<AppPermissionAccessUiInfo> appPermissionAccessUiInfoList,
+ AtomicReference<PreferenceCategory> category,
+ PreferenceScreen preferenceScreen) {
+ Context context = getContext();
+ long previousDateMs = 0L;
+ for (int i = 0; i < appPermissionAccessUiInfoList.size(); i++) {
+ AppPermissionAccessUiInfo appPermissionAccessUiInfo =
+ appPermissionAccessUiInfoList.get(i);
+ long accessEndTime = appPermissionAccessUiInfo.getAccessEndTime();
+ long currentDateMs =
+ ZonedDateTime.ofInstant(
+ Instant.ofEpochMilli(accessEndTime),
+ Clock.system(ZoneId.systemDefault()).getZone())
+ .truncatedTo(ChronoUnit.DAYS)
+ .toEpochSecond()
+ * 1000L;
+ if (currentDateMs != previousDateMs) {
+ if (previousDateMs != 0L) {
+ category.set(createDayCategoryPreference());
+ preferenceScreen.addPreference(category.get());
+ }
+ if (accessEndTime > MIDNIGHT_TODAY) {
+ category.get().setTitle(R.string.permission_history_category_today);
+ } else if (accessEndTime > MIDNIGHT_YESTERDAY) {
+ category.get().setTitle(R.string.permission_history_category_yesterday);
+ } else {
+ category.get()
+ .setTitle(DateFormat.getDateFormat(context).format(currentDateMs));
+ }
+ previousDateMs = currentDateMs;
+ }
+
+ Preference permissionUsagePreference =
+ new PermissionHistoryPreference(
+ getContext(),
+ appPermissionAccessUiInfo.getUserHandle(),
+ appPermissionAccessUiInfo.getPkgName(),
+ KotlinUtils.INSTANCE.getBadgedPackageIcon(
+ mViewModel.getApplication(),
+ appPermissionAccessUiInfo.getPkgName(),
+ appPermissionAccessUiInfo.getUserHandle()),
+ KotlinUtils.INSTANCE.getPackageLabel(
+ mViewModel.getApplication(),
+ appPermissionAccessUiInfo.getPkgName(),
+ appPermissionAccessUiInfo.getUserHandle()),
+ appPermissionAccessUiInfo.getPermissionGroup(),
+ appPermissionAccessUiInfo.getAccessStartTime(),
+ appPermissionAccessUiInfo.getAccessEndTime(),
+ appPermissionAccessUiInfo.getSummaryText(),
+ appPermissionAccessUiInfo.getShowingAttribution(),
+ appPermissionAccessUiInfo.getAttributionTags(),
+ i == appPermissionAccessUiInfoList.size() - 1,
+ mSessionId);
+
+ category.get().addPreference(permissionUsagePreference);
+ }
}
- private static class PreferenceFactory implements
- PermissionUsageDetailsViewModel.HistoryPreferenceFactory {
+ private void updateShowSystem(boolean showSystem) {
+ if (mHasSystemApps) {
+ if (mShowSystemMenu != null) {
+ mShowSystemMenu.setVisible(!showSystem);
+ mShowSystemMenu.setEnabled(true);
+ }
- private Context mContext;
+ if (mHideSystemMenu != null) {
+ mHideSystemMenu.setVisible(showSystem);
+ mHideSystemMenu.setEnabled(true);
+ }
+ } else {
+ if (mShowSystemMenu != null) {
+ mShowSystemMenu.setVisible(true);
+ mShowSystemMenu.setEnabled(false);
+ }
- PreferenceFactory(Context context) {
- mContext = context;
+ if (mHideSystemMenu != null) {
+ mHideSystemMenu.setVisible(false);
+ mHideSystemMenu.setEnabled(false);
+ }
}
+ }
- @Override
- public PreferenceCategory createDayCategoryPreference() {
- PreferenceCategory category = new PreferenceCategory(mContext);
- // Do not reserve icon space, so that the text moves all the way left.
- category.setIconSpaceReserved(false);
- return category;
+ private void updateShow7Days(boolean show7Days) {
+ if (mShow7DaysDataMenu != null) {
+ mShow7DaysDataMenu.setVisible(!show7Days);
}
- @Override
- public Preference createPermissionHistoryPreference(
- PermissionUsageDetailsViewModel.HistoryPreferenceData historyPreferenceData) {
- return new PermissionHistoryPreference(mContext,
- historyPreferenceData.getUserHandle(),
- historyPreferenceData.getPkgName(),
- historyPreferenceData.getAppIcon(),
- historyPreferenceData.getPreferenceTitle(),
- historyPreferenceData.getPermissionGroup(),
- historyPreferenceData.getAccessTime(),
- historyPreferenceData.getSummaryText(),
- historyPreferenceData.getShowingAttribution(),
- historyPreferenceData.getAccessTimeList(),
- historyPreferenceData.getAttributionTags(),
- historyPreferenceData.isLastUsage(),
- historyPreferenceData.getSessionId()
- );
+ if (mShow24HoursDataMenu != null) {
+ mShow24HoursDataMenu.setVisible(show7Days);
}
}
- private void reloadData() {
- mViewModel.loadPermissionUsages(getActivity().getLoaderManager(),
- mPermissionUsages, this, mFilterTimeIndex);
- if (mFinishedInitialLoad) {
- setProgressBarVisible(true);
- }
+ private PreferenceCategory createDayCategoryPreference() {
+ PreferenceCategory category = new PreferenceCategory(getContext());
+ // Do not reserve icon space, so that the text moves all the way left.
+ category.setIconSpaceReserved(false);
+ return category;
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionHistoryPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionHistoryPreference.java
index 7cbb8c573..8a723be42 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionHistoryPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionHistoryPreference.java
@@ -28,6 +28,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
+import android.text.format.DateFormat;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@@ -48,7 +49,6 @@ import com.android.permissioncontroller.permission.compat.IntentCompat;
import com.android.permissioncontroller.permission.utils.Utils;
import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
/**
@@ -62,10 +62,10 @@ public class PermissionHistoryPreference extends Preference {
private final UserHandle mUserHandle;
private final String mPackageName;
private final String mPermissionGroup;
- private final String mAccessTime;
+ private final long mAccessStartTime;
+ private final long mAccessEndTime;
private final Drawable mAppIcon;
private final String mTitle;
- private final List<Long> mAccessTimeList;
private final ArrayList<String> mAttributionTags;
private final boolean mIsLastUsage;
private final Intent mIntent;
@@ -80,9 +80,10 @@ public class PermissionHistoryPreference extends Preference {
@NonNull UserHandle userHandle, @NonNull String pkgName,
@NonNull Drawable appIcon,
@NonNull String preferenceTitle,
- @NonNull String permissionGroup, @NonNull String accessTime,
+ @NonNull String permissionGroup,
+ @NonNull long accessStartTime,
+ @NonNull long accessEndTime,
@Nullable CharSequence summaryText, boolean showingAttribution,
- @NonNull List<Long> accessTimeList,
@NonNull ArrayList<String> attributionTags, boolean isLastUsage, long sessionId) {
super(context);
mContext = context;
@@ -91,11 +92,11 @@ public class PermissionHistoryPreference extends Preference {
mUserHandle = userHandle;
mPackageName = pkgName;
mPermissionGroup = permissionGroup;
- mAccessTime = accessTime;
+ mAccessStartTime = accessStartTime;
+ mAccessEndTime = accessEndTime;
mAppIcon = appIcon;
mTitle = preferenceTitle;
mWidgetIcon = null;
- mAccessTimeList = accessTimeList;
mAttributionTags = attributionTags;
mIsLastUsage = isLastUsage;
mSessionId = sessionId;
@@ -135,7 +136,7 @@ public class PermissionHistoryPreference extends Preference {
widgetFrameParent.setGravity(Gravity.TOP);
TextView permissionHistoryTime = widget.findViewById(R.id.permission_history_time);
- permissionHistoryTime.setText(mAccessTime);
+ permissionHistoryTime.setText(DateFormat.getTimeFormat(mContext).format(mAccessEndTime));
ImageView permissionIcon = widget.findViewById(R.id.permission_history_icon);
permissionIcon.setImageDrawable(mAppIcon);
@@ -204,8 +205,8 @@ public class PermissionHistoryPreference extends Preference {
intent.setPackage(mPackageName);
intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, mPermissionGroup);
intent.putExtra(Intent.EXTRA_ATTRIBUTION_TAGS, mAttributionTags.toArray(new String[0]));
- intent.putExtra(Intent.EXTRA_START_TIME, mAccessTimeList.get(mAccessTimeList.size() - 1));
- intent.putExtra(Intent.EXTRA_END_TIME, mAccessTimeList.get(0));
+ intent.putExtra(Intent.EXTRA_START_TIME, mAccessStartTime);
+ intent.putExtra(Intent.EXTRA_END_TIME, mAccessEndTime);
intent.putExtra(IntentCompat.EXTRA_SHOWING_ATTRIBUTION, mShowingAttribution);
ResolveInfo resolveInfo = mUserPackageManager.resolveActivity(intent,
@@ -233,8 +234,8 @@ public class PermissionHistoryPreference extends Preference {
viewUsageIntent.putExtra(Intent.EXTRA_ATTRIBUTION_TAGS,
mAttributionTags.toArray(new String[0]));
viewUsageIntent.putExtra(Intent.EXTRA_START_TIME,
- mAccessTimeList.get(mAccessTimeList.size() - 1));
- viewUsageIntent.putExtra(Intent.EXTRA_END_TIME, mAccessTimeList.get(0));
+ mAccessStartTime);
+ viewUsageIntent.putExtra(Intent.EXTRA_END_TIME, mAccessEndTime);
viewUsageIntent.putExtra(IntentCompat.EXTRA_SHOWING_ATTRIBUTION, showingAttribution);
viewUsageIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionUsageV2Fragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionUsageV2Fragment.java
index 46f016455..39cf050ac 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionUsageV2Fragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/PermissionUsageV2Fragment.java
@@ -22,11 +22,9 @@ import static com.android.permissioncontroller.PermissionControllerStatsLog.PERM
import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SEE_OTHER_PERMISSIONS_CLICKED;
import static com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED;
import static com.android.permissioncontroller.PermissionControllerStatsLog.write;
-import static com.android.permissioncontroller.permission.ui.handheld.v31.DashboardUtilsKt.is7DayToggleEnabled;
+import android.Manifest;
import android.app.ActionBar;
-import android.app.Activity;
-import android.app.role.RoleManager;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
@@ -34,8 +32,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.Preference;
@@ -45,61 +41,52 @@ import androidx.preference.PreferenceScreen;
import androidx.recyclerview.widget.RecyclerView;
import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage;
-import com.android.permissioncontroller.permission.model.v31.PermissionUsages;
-import com.android.permissioncontroller.permission.model.legacy.PermissionApps;
import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader;
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageViewModel;
-import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageViewModelFactory;
+import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageViewModelNew;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
-import com.android.permissioncontroller.permission.utils.Utils;
import com.android.settingslib.HelpUtils;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Map;
-import kotlin.Triple;
-
-/**
- * The main page for the privacy dashboard.
- */
+/** The main page for the privacy dashboard. */
+// TODO(b/257317510): Remove "V2" suffix.
@RequiresApi(Build.VERSION_CODES.S)
-public class PermissionUsageV2Fragment extends SettingsWithLargeHeader implements
- PermissionUsages.PermissionsUsagesChangeCallback {
+public class PermissionUsageV2Fragment extends SettingsWithLargeHeader {
+
+ private static final Map<String, Integer> PERMISSION_GROUP_ORDER =
+ Map.of(
+ Manifest.permission_group.LOCATION, 0,
+ Manifest.permission_group.CAMERA, 1,
+ Manifest.permission_group.MICROPHONE, 2);
+ private static final int DEFAULT_ORDER = 3;
// Pie chart in this screen will be the first child.
// Hence we use PERMISSION_GROUP_ORDER + 1 here.
private static final int PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT =
- PermissionUsageViewModel.Companion.getPERMISSION_GROUP_ORDER().size() + 1;
+ PERMISSION_GROUP_ORDER.size() + 1;
private static final int EXPAND_BUTTON_ORDER = 999;
-
+ /** Map to represent ordering for permission groups in the permissions usage UI. */
private static final String KEY_SESSION_ID = "_session_id";
- private static final String SESSION_ID_KEY = PermissionUsageV2Fragment.class.getName()
- + KEY_SESSION_ID;
+
+ private static final String SESSION_ID_KEY =
+ PermissionUsageV2Fragment.class.getName() + KEY_SESSION_ID;
private static final int MENU_SHOW_7_DAYS_DATA = Menu.FIRST + 4;
private static final int MENU_SHOW_24_HOURS_DATA = Menu.FIRST + 5;
private static final int MENU_REFRESH = Menu.FIRST + 6;
- private @NonNull PermissionUsages mPermissionUsages;
- private @Nullable List<AppPermissionUsage> mAppPermissionUsages = new ArrayList<>();
-
- private PermissionUsageViewModel mViewModel;
+ private PermissionUsageViewModelNew mViewModel;
- private boolean mShowSystem;
private boolean mHasSystemApps;
private MenuItem mShowSystemMenu;
private MenuItem mHideSystemMenu;
- private boolean mShow7Days;
private MenuItem mShow7DaysDataMenu;
private MenuItem mShow24HoursDataMenu;
private boolean mOtherExpanded;
- private boolean mFinishedInitialLoad;
-
- private @NonNull RoleManager mRoleManager;
-
private PermissionUsageGraphicPreference mGraphic;
/** Unique Id of a request */
@@ -115,13 +102,16 @@ public class PermissionUsageV2Fragment extends SettingsWithLargeHeader implement
mSessionId = getArguments().getLong(EXTRA_SESSION_ID, INVALID_SESSION_ID);
}
- mFinishedInitialLoad = false;
+ PermissionUsageViewModelNew.PermissionUsageViewModelFactory factory =
+ new PermissionUsageViewModelNew.PermissionUsageViewModelFactory(
+ getActivity().getApplication(), this, new Bundle());
+ mViewModel = new ViewModelProvider(this, factory).get(PermissionUsageViewModelNew.class);
// By default, do not show system app usages.
- mShowSystem = false;
+ mViewModel.updateShowSystem(false);
// By default, show permission usages for the past 24 hours.
- mShow7Days = false;
+ mViewModel.updateShow7Days(false);
// Start out with 'other' permissions not expanded.
mOtherExpanded = false;
@@ -133,14 +123,9 @@ public class PermissionUsageV2Fragment extends SettingsWithLargeHeader implement
ab.setDisplayHomeAsUpEnabled(true);
}
- Context context = getPreferenceManager().getContext();
- mPermissionUsages = new PermissionUsages(context);
- mRoleManager = Utils.getSystemServiceSafe(context, RoleManager.class);
-
- PermissionUsageViewModelFactory factory = new PermissionUsageViewModelFactory(mRoleManager);
- mViewModel = new ViewModelProvider(this, factory).get(PermissionUsageViewModel.class);
-
- reloadData();
+ mViewModel.getPermissionUsagesUiLiveData().observe(this, this::updateUI);
+ mViewModel.getShowSystemLiveData().observe(this, this::updateShowSystem);
+ mViewModel.getShow7DaysLiveData().observe(this, this::updateShow7Days);
}
@Override
@@ -148,39 +133,40 @@ public class PermissionUsageV2Fragment extends SettingsWithLargeHeader implement
PreferenceGroupAdapter adapter =
(PreferenceGroupAdapter) super.onCreateAdapter(preferenceScreen);
- adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
- @Override
- public void onChanged() {
- updatePreferenceScreenAdvancedTitleAndSummary(preferenceScreen, adapter);
- }
-
- @Override
- public void onItemRangeInserted(int positionStart, int itemCount) {
- onChanged();
- }
-
- @Override
- public void onItemRangeRemoved(int positionStart, int itemCount) {
- onChanged();
- }
-
- @Override
- public void onItemRangeChanged(int positionStart, int itemCount) {
- onChanged();
- }
-
- @Override
- public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
- onChanged();
- }
- });
+ adapter.registerAdapterDataObserver(
+ new RecyclerView.AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ updatePreferenceScreenAdvancedTitleAndSummary(preferenceScreen, adapter);
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ onChanged();
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ onChanged();
+ }
+ });
updatePreferenceScreenAdvancedTitleAndSummary(preferenceScreen, adapter);
return adapter;
}
- private void updatePreferenceScreenAdvancedTitleAndSummary(PreferenceScreen preferenceScreen,
- PreferenceGroupAdapter adapter) {
+ private void updatePreferenceScreenAdvancedTitleAndSummary(
+ PreferenceScreen preferenceScreen, PreferenceGroupAdapter adapter) {
int count = adapter.getItemCount();
if (count == 0) {
return;
@@ -215,26 +201,33 @@ public class PermissionUsageV2Fragment extends SettingsWithLargeHeader implement
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
if (mHasSystemApps) {
- mShowSystemMenu = menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE,
- R.string.menu_show_system);
- mHideSystemMenu = menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE,
- R.string.menu_hide_system);
+ mShowSystemMenu =
+ menu.add(Menu.NONE, MENU_SHOW_SYSTEM, Menu.NONE, R.string.menu_show_system);
+ mHideSystemMenu =
+ menu.add(Menu.NONE, MENU_HIDE_SYSTEM, Menu.NONE, R.string.menu_hide_system);
}
- if (is7DayToggleEnabled()) {
- mShow7DaysDataMenu = menu.add(Menu.NONE, MENU_SHOW_7_DAYS_DATA, Menu.NONE,
- R.string.menu_show_7_days_data);
- mShow24HoursDataMenu = menu.add(Menu.NONE, MENU_SHOW_24_HOURS_DATA, Menu.NONE,
- R.string.menu_show_24_hours_data);
+ if (KotlinUtils.INSTANCE.is7DayToggleEnabled()) {
+ mShow7DaysDataMenu =
+ menu.add(
+ Menu.NONE,
+ MENU_SHOW_7_DAYS_DATA,
+ Menu.NONE,
+ R.string.menu_show_7_days_data);
+ mShow24HoursDataMenu =
+ menu.add(
+ Menu.NONE,
+ MENU_SHOW_24_HOURS_DATA,
+ Menu.NONE,
+ R.string.menu_show_24_hours_data);
}
- HelpUtils.prepareHelpMenuItem(getActivity(), menu, R.string.help_permission_usage,
- getClass().getName());
- MenuItem refresh = menu.add(Menu.NONE, MENU_REFRESH, Menu.NONE,
- R.string.permission_usage_refresh);
+ HelpUtils.prepareHelpMenuItem(
+ getActivity(), menu, R.string.help_permission_usage, getClass().getName());
+ MenuItem refresh =
+ menu.add(Menu.NONE, MENU_REFRESH, Menu.NONE, R.string.permission_usage_refresh);
refresh.setIcon(R.drawable.ic_refresh);
refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
- updateMenu();
}
@Override
@@ -245,52 +238,28 @@ public class PermissionUsageV2Fragment extends SettingsWithLargeHeader implement
getActivity().finishAfterTransition();
return true;
case MENU_SHOW_SYSTEM:
- write(PERMISSION_USAGE_FRAGMENT_INTERACTION, mSessionId,
+ write(
+ PERMISSION_USAGE_FRAGMENT_INTERACTION,
+ mSessionId,
PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SHOW_SYSTEM_CLICKED);
- // fall through
+ mViewModel.updateShowSystem(true);
+ break;
case MENU_HIDE_SYSTEM:
- mShowSystem = itemId == MENU_SHOW_SYSTEM;
- // We already loaded all data, so don't reload
- updateUI();
- updateMenu();
+ mViewModel.updateShowSystem(false);
break;
case MENU_SHOW_7_DAYS_DATA:
+ mViewModel.updateShow7Days(KotlinUtils.INSTANCE.is7DayToggleEnabled());
+ break;
case MENU_SHOW_24_HOURS_DATA:
- mShow7Days = is7DayToggleEnabled() && itemId == MENU_SHOW_7_DAYS_DATA;
- updateUI();
- updateMenu();
+ mViewModel.updateShow7Days(false);
break;
case MENU_REFRESH:
- reloadData();
+ // TODO(b/257314894): What should happen on refresh?
break;
}
return super.onOptionsItemSelected(item);
}
- private void updateMenu() {
- if (mHasSystemApps) {
- mShowSystemMenu.setVisible(!mShowSystem);
- mHideSystemMenu.setVisible(mShowSystem);
- }
-
- if (mShow7DaysDataMenu != null) {
- mShow7DaysDataMenu.setVisible(!mShow7Days);
- }
-
- if (mShow24HoursDataMenu != null) {
- mShow24HoursDataMenu.setVisible(mShow7Days);
- }
- }
-
- @Override
- public void onPermissionUsagesChanged() {
- if (mPermissionUsages.getUsages().isEmpty()) {
- return;
- }
- mAppPermissionUsages = new ArrayList<>(mPermissionUsages.getUsages());
- updateUI();
- }
-
@Override
public int getEmptyViewString() {
return R.string.no_permission_usages;
@@ -304,8 +273,26 @@ public class PermissionUsageV2Fragment extends SettingsWithLargeHeader implement
}
}
- private void updateUI() {
- if (mAppPermissionUsages.isEmpty() || getActivity() == null) {
+ private void updateShowSystem(boolean showSystem) {
+ if (mHasSystemApps) {
+ mShowSystemMenu.setVisible(!showSystem);
+ mHideSystemMenu.setVisible(showSystem);
+ }
+ }
+
+ private void updateShow7Days(boolean show7Days) {
+ if (mShow7DaysDataMenu != null) {
+ mShow7DaysDataMenu.setVisible(!show7Days);
+ }
+
+ if (mShow24HoursDataMenu != null) {
+ mShow24HoursDataMenu.setVisible(show7Days);
+ }
+ }
+
+ private void updateUI(
+ PermissionUsageViewModelNew.PermissionUsagesUiData permissionUsagesUiData) {
+ if (getActivity() == null) {
return;
}
Context context = getActivity();
@@ -324,106 +311,127 @@ public class PermissionUsageV2Fragment extends SettingsWithLargeHeader implement
PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT);
}
screen.setOnExpandButtonClickListener(() -> {
- write(PERMISSION_USAGE_FRAGMENT_INTERACTION, mSessionId,
+ write(
+ PERMISSION_USAGE_FRAGMENT_INTERACTION,
+ mSessionId,
PERMISSION_USAGE_FRAGMENT_INTERACTION__ACTION__SEE_OTHER_PERMISSIONS_CLICKED);
});
-
- Triple<Map<String, Integer>, ArrayList<PermissionApps.PermissionApp>, Boolean>
- triple = mViewModel.extractUsages(mAppPermissionUsages, mShow7Days, mShowSystem);
- Map<String, Integer> usages = triple.getFirst();
- ArrayList<PermissionApps.PermissionApp> permApps = triple.getSecond();
- boolean seenSystemApp = triple.getThird();
-
- if (mHasSystemApps != seenSystemApp) {
- mHasSystemApps = seenSystemApp;
+ boolean displayShowSystemToggle = permissionUsagesUiData.getDisplayShowSystemToggle();
+ Map<String, Integer> permissionGroupWithUsageCounts =
+ permissionUsagesUiData.getPermissionGroupsWithUsageCount();
+ List<Map.Entry<String, Integer>> permissionGroupWithUsageCountsEntries =
+ new ArrayList(permissionGroupWithUsageCounts.entrySet());
+
+ permissionGroupWithUsageCountsEntries.sort(Comparator.comparing(
+ (Map.Entry<String, Integer> permissionGroupWithUsageCount) ->
+ PERMISSION_GROUP_ORDER.getOrDefault(
+ permissionGroupWithUsageCount.getKey(),
+ DEFAULT_ORDER))
+ .thenComparing(
+ (Map.Entry<String, Integer> permissionGroupWithUsageCount) ->
+ KotlinUtils.INSTANCE
+ .getPermGroupLabel(
+ context,
+ permissionGroupWithUsageCount
+ .getKey())
+ .toString()));
+
+ if (mHasSystemApps != displayShowSystemToggle) {
+ mHasSystemApps = displayShowSystemToggle;
getActivity().invalidateOptionsMenu();
}
- mGraphic = new PermissionUsageGraphicPreference(context, mShow7Days);
+ mGraphic =
+ new PermissionUsageGraphicPreference(
+ context, permissionUsagesUiData.getShow7DaysUsage());
screen.addPreference(mGraphic);
- mGraphic.setUsages(usages);
+
+ mGraphic.setUsages(permissionGroupWithUsageCounts);
// Add the preference header.
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
- List<Map.Entry<String, Integer>> groupUsagesList = mViewModel.createGroupUsagesList(
- getContext(), usages);
-
- CharSequence advancedInfoSummary = getAdvancedInfoSummaryString(context, groupUsagesList);
+ CharSequence advancedInfoSummary =
+ getAdvancedInfoSummaryString(context, permissionGroupWithUsageCountsEntries);
screen.setSummary(advancedInfoSummary);
- addUIContent(context, groupUsagesList, permApps, category);
+ addUIContent(
+ context,
+ permissionGroupWithUsageCountsEntries,
+ category,
+ permissionUsagesUiData.getShowSystemAppPermissions(),
+ permissionUsagesUiData.getShow7DaysUsage());
}
- private CharSequence getAdvancedInfoSummaryString(Context context,
- List<Map.Entry<String, Integer>> groupUsagesList) {
- int size = groupUsagesList.size();
+ private CharSequence getAdvancedInfoSummaryString(
+ Context context, List<Map.Entry<String, Integer>> permissionGroupWithUsageCounts) {
+ int size = permissionGroupWithUsageCounts.size();
if (size <= PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT - 1) {
return "";
}
// case for 1 extra item in the advanced info
if (size == PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT) {
- String permGroupName = groupUsagesList
- .get(PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT - 1).getKey();
+ String permGroupName =
+ permissionGroupWithUsageCounts
+ .get(PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT - 1)
+ .getKey();
return KotlinUtils.INSTANCE.getPermGroupLabel(context, permGroupName);
}
- String permGroupName1 = groupUsagesList
- .get(PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT - 1).getKey();
- String permGroupName2 = groupUsagesList
- .get(PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT).getKey();
- CharSequence permGroupLabel1 = KotlinUtils
- .INSTANCE.getPermGroupLabel(context, permGroupName1);
- CharSequence permGroupLabel2 = KotlinUtils
- .INSTANCE.getPermGroupLabel(context, permGroupName2);
+ String permGroupName1 =
+ permissionGroupWithUsageCounts
+ .get(PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT - 1)
+ .getKey();
+ String permGroupName2 =
+ permissionGroupWithUsageCounts
+ .get(PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT)
+ .getKey();
+ CharSequence permGroupLabel1 =
+ KotlinUtils.INSTANCE.getPermGroupLabel(context, permGroupName1);
+ CharSequence permGroupLabel2 =
+ KotlinUtils.INSTANCE.getPermGroupLabel(context, permGroupName2);
// case for 2 extra items in the advanced info
if (size == PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT + 1) {
- return context.getResources().getString(R.string.perm_usage_adv_info_summary_2_items,
- permGroupLabel1, permGroupLabel2);
+ return context.getResources()
+ .getString(
+ R.string.perm_usage_adv_info_summary_2_items,
+ permGroupLabel1,
+ permGroupLabel2);
}
// case for 3 or more extra items in the advanced info
int numExtraItems = size - PERMISSION_USAGE_INITIAL_EXPANDED_CHILDREN_COUNT - 1;
- return context.getResources().getString(R.string.perm_usage_adv_info_summary_more_items,
- permGroupLabel1, permGroupLabel2, numExtraItems);
+ return context.getResources()
+ .getString(
+ R.string.perm_usage_adv_info_summary_more_items,
+ permGroupLabel1,
+ permGroupLabel2,
+ numExtraItems);
}
- /**
- * Use the usages and permApps that are previously constructed to add UI content to the page
- */
- private void addUIContent(Context context,
- List<Map.Entry<String, Integer>> usages,
- ArrayList<PermissionApps.PermissionApp> permApps,
- PreferenceCategory category) {
- new PermissionApps.AppDataLoader(context, () -> {
- for (int i = 0; i < usages.size(); i++) {
- Map.Entry<String, Integer> currentEntry = usages.get(i);
- PermissionUsageV2ControlPreference permissionUsagePreference =
- new PermissionUsageV2ControlPreference(context, currentEntry.getKey(),
- currentEntry.getValue(), mShowSystem, mSessionId, mShow7Days);
- category.addPreference(permissionUsagePreference);
- }
-
- setLoading(false, true);
- mFinishedInitialLoad = true;
- setProgressBarVisible(false);
-
- Activity activity = getActivity();
- if (activity != null) {
- mPermissionUsages.stopLoader(activity.getLoaderManager());
- }
- }).execute(permApps.toArray(new PermissionApps.PermissionApp[0]));
- }
-
- /**
- * Reloads the data to show.
- */
- private void reloadData() {
- mViewModel.loadPermissionUsages(getActivity().getLoaderManager(), mPermissionUsages, this);
- if (mFinishedInitialLoad) {
- setProgressBarVisible(true);
+ /** Use the usages and permApps that are previously constructed to add UI content to the page */
+ private void addUIContent(
+ Context context,
+ List<Map.Entry<String, Integer>> permissionGroupWithUsageCounts,
+ PreferenceCategory category,
+ boolean showSystem,
+ boolean show7Days) {
+ for (int i = 0; i < permissionGroupWithUsageCounts.size(); i++) {
+ Map.Entry<String, Integer> permissionGroupWithUsageCount =
+ permissionGroupWithUsageCounts.get(i);
+ PermissionUsageV2ControlPreference permissionUsagePreference =
+ new PermissionUsageV2ControlPreference(
+ context,
+ permissionGroupWithUsageCount.getKey(),
+ permissionGroupWithUsageCount.getValue(),
+ showSystem,
+ mSessionId,
+ show7Days);
+ category.addPreference(permissionUsagePreference);
}
+
+ setLoading(false, true);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatePreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatePreference.kt
new file mode 100644
index 000000000..a5a2829a9
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatePreference.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.handheld.v34
+
+import android.app.Application
+import android.content.Context
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.os.UserHandle
+import android.view.View
+import android.widget.ImageButton
+import androidx.annotation.RequiresApi
+import androidx.preference.PreferenceViewHolder
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.handheld.SmartIconLoadPackagePermissionPreference
+
+/**
+ * A preference with package label, summary, app icon and settings gear icon, to represent updates
+ * in data sharing for one app.
+ */
+@RequiresApi(UPSIDE_DOWN_CAKE)
+class AppDataSharingUpdatePreference(
+ app: Application,
+ packageName: String,
+ user: UserHandle,
+ context: Context
+) : SmartIconLoadPackagePermissionPreference(app, packageName, user, context) {
+
+ init {
+ widgetLayoutResource = R.xml.settings_button_with_divider_preference_widget
+ }
+
+ /** [View.OnClickListener] for the settings gear icon. */
+ var settingsGearClick: View.OnClickListener? = null
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ val settingsGearButton = holder.findViewById(R.id.settings_button) as ImageButton
+ settingsGearButton.setOnClickListener(settingsGearClick)
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFragment.kt
new file mode 100644
index 000000000..79c7ab983
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFragment.kt
@@ -0,0 +1,184 @@
+package com.android.permissioncontroller.permission.ui.handheld.v34
+
+import android.os.Build
+import android.os.Bundle
+import android.os.UserHandle
+import android.view.View
+import androidx.annotation.RequiresApi
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.model.v34.DataSharingUpdateType
+import com.android.permissioncontroller.permission.ui.handheld.PermissionsFrameFragment
+import com.android.permissioncontroller.permission.ui.model.v34.AppDataSharingUpdatesViewModel
+import com.android.permissioncontroller.permission.ui.model.v34.AppDataSharingUpdatesViewModel.AppLocationDataSharingUpdateUiInfo
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.StringUtils
+import java.lang.IllegalArgumentException
+
+/** Fragment to display data sharing updates for installed apps. */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class AppDataSharingUpdatesFragment : PermissionsFrameFragment() {
+ lateinit var viewModel: AppDataSharingUpdatesViewModel
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ requireActivity().title = getString(R.string.data_sharing_updates_title)
+
+ val ab = activity?.actionBar
+ ab?.setDisplayHomeAsUpEnabled(true)
+
+ viewModel = AppDataSharingUpdatesViewModel(requireActivity().application)
+
+ if (preferenceScreen == null) {
+ addPreferencesFromResource(R.xml.app_data_sharing_updates)
+ showNoUpdatesPresentUi()
+ }
+
+ viewModel.appLocationDataSharingUpdateUiInfoLiveData.observe(this, this::updatePreferences)
+ }
+
+ private fun updatePreferences(updateUiInfos: List<AppLocationDataSharingUpdateUiInfo>) {
+ if (updateUiInfos.isNotEmpty()) {
+ showUpdatesPresentUi()
+ } else {
+ showNoUpdatesPresentUi()
+ }
+
+ val preferenceKeysToShow =
+ updateUiInfos.map {
+ createUpdatePreferenceKey(it.packageName, it.userHandle, it.dataSharingUpdateType)
+ }
+
+ val updatesCategory =
+ preferenceScreen.findPreference<PreferenceCategory>(
+ LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID)
+ ?: return
+ for (i in 0 until (updatesCategory.preferenceCount)) {
+ // Remove preferences that no longer need to be shown.
+ if (!preferenceKeysToShow.contains(updatesCategory.getPreference(i).key)) {
+ updatesCategory.removePreference(updatesCategory.getPreference(i))
+ }
+ }
+
+ updateUiInfos.forEach { updateUiInfo ->
+ val key =
+ createUpdatePreferenceKey(
+ updateUiInfo.packageName,
+ updateUiInfo.userHandle,
+ updateUiInfo.dataSharingUpdateType)
+ if (updatesCategory.findPreference<AppDataSharingUpdatePreference>(key) != null) {
+ // If a preference is already shown, don't recreate it.
+ return@forEach
+ }
+ val appDataSharingUpdatePreference =
+ AppDataSharingUpdatePreference(
+ requireActivity().application,
+ updateUiInfo.packageName,
+ updateUiInfo.userHandle,
+ requireActivity().applicationContext)
+ appDataSharingUpdatePreference.apply {
+ this.key = key
+ title =
+ KotlinUtils.getPackageLabel(
+ requireActivity().application,
+ updateUiInfo.packageName,
+ updateUiInfo.userHandle)
+ summary = getSummaryForLocationUpdateType(updateUiInfo.dataSharingUpdateType)
+ settingsGearClick =
+ View.OnClickListener { _ ->
+ viewModel.startAppPermissionsPage(
+ requireActivity(), updateUiInfo.packageName, updateUiInfo.userHandle)
+ }
+ updatesCategory.addPreference(this)
+ }
+ }
+ }
+
+ private fun getSummaryForLocationUpdateType(type: DataSharingUpdateType): String {
+ return when (type) {
+ DataSharingUpdateType.ADDS_ADVERTISING_PURPOSE ->
+ getString(R.string.shares_location_for_advertising)
+ DataSharingUpdateType.ADDS_SHARING_WITHOUT_ADVERTISING_PURPOSE ->
+ getString(R.string.shares_location_with_third_parties)
+ DataSharingUpdateType.ADDS_SHARING_WITH_ADVERTISING_PURPOSE ->
+ getString(R.string.shares_location_with_third_parties_for_advertising)
+ else -> throw IllegalArgumentException("Invalid DataSharingUpdateType: $type")
+ }
+ }
+
+ private fun showUpdatesPresentUi() {
+ if (preferenceScreen == null) {
+ return
+ }
+ val subtitlePreference =
+ preferenceScreen?.findPreference<Preference>(SUBTITLE_PREFERENCE_ID)
+ val footerPreference =
+ preferenceScreen?.findPreference<FooterWithLinkPreference>(FOOTER_PREFERENCE_ID)
+ val dataSharingUpdatesCategory =
+ preferenceScreen?.findPreference<PreferenceCategory>(
+ LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID)
+ subtitlePreference?.let {
+ it.summary = getString(R.string.data_sharing_updates_subtitle)
+ it.isVisible = true
+ }
+ dataSharingUpdatesCategory?.let {
+ it.title =
+ StringUtils.getIcuPluralsString(requireContext(), R.string.updated_in_last_days, 30)
+ it.isVisible = true
+ }
+ footerPreference?.let {
+ it.title =
+ StringUtils.getIcuPluralsString(requireContext(), R.string.updated_in_last_days, 30)
+ it.footerMessage = getString(R.string.data_sharing_updates_footer_message)
+ it.footerLink = getString(R.string.learn_more_about_data_sharing)
+ it.onFooterLinkClick =
+ View.OnClickListener { viewModel.openSafetyLabelsHelpCenterPage(requireActivity()) }
+ it.isVisible = true
+ }
+ }
+
+ private fun showNoUpdatesPresentUi() {
+ if (preferenceScreen == null) {
+ return
+ }
+ val subtitlePreference =
+ preferenceScreen?.findPreference<Preference>(SUBTITLE_PREFERENCE_ID)
+ val footerPreference =
+ preferenceScreen?.findPreference<FooterWithLinkPreference>(FOOTER_PREFERENCE_ID)
+ val dataSharingUpdatesCategory =
+ preferenceScreen?.findPreference<PreferenceCategory>(
+ LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID)
+ subtitlePreference?.let {
+ it.summary = getString(R.string.no_recent_updates)
+ it.isVisible = true
+ }
+ dataSharingUpdatesCategory?.isVisible = false
+ footerPreference?.isVisible = false
+ }
+
+ /** Creates an identifier for a preference. */
+ private fun createUpdatePreferenceKey(
+ packageName: String,
+ user: UserHandle,
+ update: DataSharingUpdateType
+ ): String {
+ return "$packageName:${user.identifier}:${update.name}"
+ }
+
+ /** Companion object for [AppDataSharingUpdatesFragment]. */
+ companion object {
+ /**
+ * Creates a [Bundle] with the arguments needed by this fragment.
+ *
+ * @param sessionId the current session ID
+ * @return a [Bundle] with all of the required args
+ */
+ fun createArgs(sessionId: Long) = Bundle().apply { putLong(EXTRA_SESSION_ID, sessionId) }
+
+ private const val SUBTITLE_PREFERENCE_ID = "subtitle"
+ private const val FOOTER_PREFERENCE_ID = "info_footer"
+ private const val LAST_PERIOD_UPDATES_PREFERENCE_CATEGORY_ID = "last_period_updates"
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesWrapperFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesWrapperFragment.kt
new file mode 100644
index 000000000..ad092cd9d
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesWrapperFragment.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.handheld.v34
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.preference.PreferenceFragmentCompat
+import com.android.permissioncontroller.permission.ui.handheld.PermissionsCollapsingToolbarBaseFragment
+
+/** Wrapper class over [AppDataSharingUpdatesFragment]. */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class AppDataSharingUpdatesWrapperFragment : PermissionsCollapsingToolbarBaseFragment() {
+ override fun createPreferenceFragment(): PreferenceFragmentCompat {
+ return AppDataSharingUpdatesFragment()
+ }
+
+ companion object {
+ /** Returns a new instance of [AppDataSharingUpdatesWrapperFragment] with arguments. */
+ fun newInstance(sessionId: Long): AppDataSharingUpdatesWrapperFragment {
+ val instance = AppDataSharingUpdatesWrapperFragment()
+ instance.arguments = AppDataSharingUpdatesFragment.createArgs(sessionId)
+ return instance
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/FooterWithLinkPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/FooterWithLinkPreference.kt
new file mode 100644
index 000000000..27c468000
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/FooterWithLinkPreference.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.handheld.v34
+
+import android.content.Context
+import android.text.SpannableString
+import android.text.method.LinkMovementMethod
+import android.text.style.ClickableSpan
+import android.util.AttributeSet
+import android.view.View
+import android.widget.TextView
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.permissioncontroller.R
+
+/** A preference for a footer with an icon and a link. */
+class FooterWithLinkPreference : Preference {
+ constructor(c: Context) : super(c)
+ constructor(c: Context, a: AttributeSet) : super(c, a)
+ constructor(c: Context, a: AttributeSet, attr: Int) : super(c, a, attr)
+ constructor(c: Context, a: AttributeSet, attr: Int, res: Int) : super(c, a, attr, res)
+
+ private var footerMessageView: TextView? = null
+ private var footerLinkView: TextView? = null
+
+ init {
+ layoutResource = R.layout.footer_with_link_preference
+ }
+
+ /** Message for the footer. */
+ var footerMessage: CharSequence = ""
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+
+ /** Clickable link for the footer. */
+ var footerLink: CharSequence = ""
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+
+ /** [View.OnClickListener] for the footer link. */
+ var onFooterLinkClick: View.OnClickListener? = null
+ set(value) {
+ field = value
+ notifyChanged()
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ footerMessageView = holder.findViewById(R.id.footer_message) as TextView?
+ footerMessageView?.text = footerMessage
+
+ footerLinkView = holder.findViewById(R.id.footer_link) as TextView?
+ val footerLinkText = SpannableString(footerLink)
+ footerLinkText.setSpan(
+ object : ClickableSpan() {
+ override fun onClick(v: View) {
+ onFooterLinkClick?.onClick(v)
+ }
+ },
+ 0,
+ footerLink.length,
+ 0)
+ footerLinkView?.let {
+ it.text = footerLinkText
+ it.movementMethod = LinkMovementMethod.getInstance()
+ }
+ super.onBindViewHolder(holder)
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt
new file mode 100644
index 000000000..10cf03793
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.handheld.v34
+
+import android.app.Activity
+import android.os.Build
+import android.os.Bundle
+import android.text.method.LinkMovementMethod
+import android.transition.ChangeBounds
+import android.transition.TransitionManager
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.View.OnClickListener
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.view.animation.AnimationUtils
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.annotation.RequiresApi
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler
+import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler.Result.Companion.CANCELLED
+
+/**
+ * Handheld implementation of [PermissionRationaleViewHandler]. Used for managing the presentation
+ * and user interaction of the "permission rationale" user interface.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class PermissionRationaleViewHandlerImpl(
+ private val mActivity: Activity,
+ private val resultListener: PermissionRationaleViewHandler.ResultListener
+) : PermissionRationaleViewHandler, OnClickListener {
+
+ private var groupName: String? = null
+ private var purposeMessage: CharSequence? = null
+ private var settingsMessage: CharSequence? = null
+ private var learnMoreMessage: CharSequence? = null
+
+ private var rootView: ViewGroup? = null
+ private var purposeMessageView: TextView? = null
+ private var settingsMessageView: TextView? = null
+ private var learnMoreMessageView: TextView? = null
+ private var backButton: Button? = null
+
+ override fun saveInstanceState(outState: Bundle) {
+ outState.putString(ARG_GROUP_NAME, groupName)
+ outState.putCharSequence(ARG_PURPOSE_MESSAGE, purposeMessage)
+ outState.putCharSequence(ARG_SETTINGS_MESSAGE, settingsMessage)
+ outState.putCharSequence(ARG_LEARN_MORE_MESSAGE, learnMoreMessage)
+ }
+
+ override fun loadInstanceState(savedInstanceState: Bundle) {
+ groupName = savedInstanceState.getString(ARG_GROUP_NAME)
+ purposeMessage = savedInstanceState.getCharSequence(ARG_PURPOSE_MESSAGE)
+ settingsMessage = savedInstanceState.getCharSequence(ARG_SETTINGS_MESSAGE)
+ learnMoreMessage = savedInstanceState.getCharSequence(ARG_LEARN_MORE_MESSAGE)
+ }
+
+ override fun updateUi(
+ groupName: String,
+ purposeMessage: CharSequence,
+ settingsMessage: CharSequence,
+ learnMoreMessage: CharSequence
+ ) {
+ this.groupName = groupName
+ this.purposeMessage = purposeMessage
+ this.settingsMessage = settingsMessage
+ this.learnMoreMessage = learnMoreMessage
+
+ // If view already created, update all children
+ if (rootView != null) {
+ updateAll()
+ }
+ }
+
+ private fun updateAll() {
+ updatePurposeMessage()
+ updateSettingsMessage()
+ updateLearnMoreMessage()
+
+ // Animate change in size
+ // Grow or shrink the content container to size of new content
+ val growShrinkToNewContentSize = ChangeBounds()
+ growShrinkToNewContentSize.duration = ANIMATION_DURATION_MILLIS
+ growShrinkToNewContentSize.interpolator = AnimationUtils.loadInterpolator(mActivity,
+ android.R.interpolator.fast_out_slow_in)
+ TransitionManager.beginDelayedTransition(rootView, growShrinkToNewContentSize)
+ }
+
+ override fun createView(): View {
+ // Make this activity be Non-IME target to prevent hiding keyboard flicker when it show up.
+ mActivity.window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
+
+ val rootView = LayoutInflater.from(mActivity)
+ .inflate(R.layout.permission_rationale, null) as ViewGroup
+
+ // Uses the vertical gravity of the PermissionGrantSingleton style to position the window
+ val gravity = rootView.requireViewById<LinearLayout>(R.id.grant_singleton).gravity
+ val verticalGravity = Gravity.VERTICAL_GRAVITY_MASK and gravity
+ mActivity.window.setGravity(Gravity.CENTER_HORIZONTAL or verticalGravity)
+
+ // Cancel dialog
+ rootView.findViewById<View>(R.id.grant_singleton)!!.setOnClickListener(this)
+ // Swallow click event
+ rootView.findViewById<View>(R.id.grant_dialog)!!.setOnClickListener(this)
+
+ purposeMessageView = rootView.findViewById(R.id.purpose_message)
+ purposeMessageView!!.movementMethod = LinkMovementMethod.getInstance()
+
+ settingsMessageView = rootView.findViewById(R.id.settings_message)
+ settingsMessageView!!.movementMethod = LinkMovementMethod.getInstance()
+
+ learnMoreMessageView = rootView.findViewById(R.id.learn_more_message)
+ learnMoreMessageView!!.movementMethod = LinkMovementMethod.getInstance()
+
+ backButton = rootView.findViewById<Button>(R.id.back_button)
+ backButton!!.setOnClickListener(this)
+
+ this.rootView = rootView
+
+ // If ui model present, update all children
+ if (groupName != null) {
+ updateAll()
+ }
+
+ return rootView
+ }
+
+ override fun onClick(view: View) {
+ val id = view.id
+
+ if (id == R.id.grant_singleton) {
+ onCancelled()
+ return
+ }
+
+ if (id == R.id.back_button) {
+ onCancelled()
+ }
+ }
+
+ override fun onBackPressed() {
+ onCancelled()
+ }
+
+ override fun onCancelled() {
+ resultListener.onPermissionRationaleResult(groupName, CANCELLED)
+ }
+
+ private fun updatePurposeMessage() {
+ if (purposeMessage == null) {
+ purposeMessageView!!.visibility = View.GONE
+ } else {
+ purposeMessageView!!.text = purposeMessage
+ purposeMessageView!!.visibility = View.VISIBLE
+ }
+ }
+
+ private fun updateSettingsMessage() {
+ if (settingsMessage == null) {
+ settingsMessageView!!.visibility = View.GONE
+ } else {
+ settingsMessageView!!.text = settingsMessage
+ settingsMessageView!!.visibility = View.VISIBLE
+ }
+ }
+
+ private fun updateLearnMoreMessage() {
+ if (learnMoreMessage == null) {
+ learnMoreMessageView!!.visibility = View.GONE
+ } else {
+ learnMoreMessageView!!.text = learnMoreMessage
+ learnMoreMessageView!!.visibility = View.VISIBLE
+ }
+ }
+
+ companion object {
+ private val TAG = PermissionRationaleViewHandlerImpl::class.java.simpleName
+
+ const val ARG_GROUP_NAME = "ARG_GROUP_NAME"
+ const val ARG_PURPOSE_MESSAGE = "ARG_PURPOSE_MESSAGE"
+ const val ARG_SETTINGS_MESSAGE = "ARG_SETTINGS_MESSAGE"
+ const val ARG_LEARN_MORE_MESSAGE = "ARG_LEARN_MORE_MESSAGE"
+
+ // Animation parameters.
+ private const val ANIMATION_DURATION_MILLIS: Long = 200
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/package-info.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/package-info.java
new file mode 100644
index 000000000..48eb8661a
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+package com.android.permissioncontroller.permission.ui.handheld.v34;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
index bb060e831..5e3cd8684 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
@@ -38,7 +38,6 @@ import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISS
import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_GROUPS_FRAGMENT_AUTO_REVOKE_ACTION__ACTION__SWITCH_ENABLED
import com.android.permissioncontroller.R
import com.android.permissioncontroller.hibernation.isHibernationEnabled
-import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData
import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
import com.android.permissioncontroller.permission.data.HibernationSettingStateLiveData
@@ -47,19 +46,20 @@ import com.android.permissioncontroller.permission.data.PackagePermissionsLiveDa
import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
import com.android.permissioncontroller.permission.data.get
-import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState
+import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
import com.android.permissioncontroller.permission.ui.Category
-import com.android.permissioncontroller.permission.ui.handheld.v31.is7DayToggleEnabled
import com.android.permissioncontroller.permission.utils.IPC
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.permission.utils.Utils.AppPermsLastAccessType
import com.android.permissioncontroller.permission.utils.navigateSafe
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
import java.time.Instant
import java.util.concurrent.TimeUnit
import kotlin.math.max
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
/**
* ViewModel for the AppPermissionGroupsFragment. Has a liveData with the UI information for all
@@ -281,7 +281,7 @@ class AppPermissionGroupsViewModel(
return
}
- val aggregateDataFilterBeginDays = if (is7DayToggleEnabled())
+ val aggregateDataFilterBeginDays = if (KotlinUtils.is7DayToggleEnabled())
AGGREGATE_DATA_FILTER_BEGIN_DAYS_7 else AGGREGATE_DATA_FILTER_BEGIN_DAYS_1
accessTime.clear()
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
index 98bb5d80c..40ef3d701 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
@@ -19,45 +19,54 @@ package com.android.permissioncontroller.permission.ui.model
import android.Manifest
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
-import android.Manifest.permission_group.LOCATION
+import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
+import android.Manifest.permission_group.READ_MEDIA_VISUAL
import android.annotation.SuppressLint
+import android.app.Activity
import android.app.AppOpsManager
import android.app.AppOpsManager.MODE_ALLOWED
import android.app.AppOpsManager.MODE_ERRORED
import android.app.AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE
import android.app.Application
+import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.UserHandle
+import android.provider.MediaStore
import android.util.Log
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContract
import androidx.annotation.ChecksSdkIntAtLeast
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
+import androidx.core.util.Consumer
import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.android.modules.utils.build.SdkLevel
+import com.android.permission.safetylabel.DataCategory
+import com.android.permission.safetylabel.DataType
+import com.android.permission.safetylabel.DataTypeConstants
+import com.android.permissioncontroller.Constants
import com.android.permissioncontroller.PermissionControllerStatsLog
import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_ACTION_REPORTED
import com.android.permissioncontroller.PermissionControllerStatsLog.APP_PERMISSION_FRAGMENT_VIEWED
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState
import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
+import com.android.permissioncontroller.permission.data.SafetyLabelInfoLiveData
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
import com.android.permissioncontroller.permission.data.get
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission
+import com.android.permissioncontroller.permission.model.livedatatypes.SafetyLabelInfo
import com.android.permissioncontroller.permission.service.PermissionChangeStorageImpl
import com.android.permissioncontroller.permission.service.v33.PermissionDecisionStorageImpl
import com.android.permissioncontroller.permission.ui.AdvancedConfirmDialogArgs
-
-import com.android.permissioncontroller.permission.ui.handheld.v31.getDefaultPrecision
-import com.android.permissioncontroller.permission.ui.handheld.v31.isLocationAccuracyEnabled
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW_ALWAYS
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND
@@ -66,8 +75,15 @@ import com.android.permissioncontroller.permission.ui.model.AppPermissionViewMod
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.DENY
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.DENY_FOREGROUND
import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.LOCATION_ACCURACY
+import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.SELECT_PHOTOS
+import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity
import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getDefaultPrecision
+import com.android.permissioncontroller.permission.utils.KotlinUtils.isLocationAccuracyEnabled
+import com.android.permissioncontroller.permission.utils.KotlinUtils.isPermissionRationaleEnabled
import com.android.permissioncontroller.permission.utils.LocationUtils
+import com.android.permissioncontroller.permission.utils.PermissionMapping
+import com.android.permissioncontroller.permission.utils.SafetyLabelPermissionMapping
import com.android.permissioncontroller.permission.utils.SafetyNetLogger
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.permission.utils.navigateSafe
@@ -96,10 +112,12 @@ class AppPermissionViewModel(
companion object {
private val LOG_TAG = AppPermissionViewModel::class.java.simpleName
-
private const val DEVICE_PROFILE_ROLE_PREFIX = "android.app.role"
+ const val PHOTO_PICKER_REQUEST_CODE = 1
}
+ val safetyLabelInfoLiveData = SafetyLabelInfoLiveData[packageName, user]
+
interface ConfirmDialogShowingFragment {
fun showConfirmDialog(
changeRequest: ChangeRequest,
@@ -112,21 +130,22 @@ class AppPermissionViewModel(
}
enum class ChangeRequest(val value: Int) {
- GRANT_FOREGROUND(1),
- REVOKE_FOREGROUND(2),
- GRANT_BACKGROUND(4),
- REVOKE_BACKGROUND(8),
+ GRANT_FOREGROUND(1 shl 0),
+ REVOKE_FOREGROUND(1 shl 1),
+ GRANT_BACKGROUND(1 shl 2),
+ REVOKE_BACKGROUND(1 shl 3),
GRANT_BOTH(GRANT_FOREGROUND.value or GRANT_BACKGROUND.value),
REVOKE_BOTH(REVOKE_FOREGROUND.value or REVOKE_BACKGROUND.value),
GRANT_FOREGROUND_ONLY(GRANT_FOREGROUND.value or REVOKE_BACKGROUND.value),
- GRANT_All_FILE_ACCESS(16),
- GRANT_FINE_LOCATION(32),
- REVOKE_FINE_LOCATION(64),
- GRANT_STORAGE_SUPERGROUP(128),
- REVOKE_STORAGE_SUPERGROUP(256),
+ GRANT_All_FILE_ACCESS(1 shl 4),
+ GRANT_FINE_LOCATION(1 shl 5),
+ REVOKE_FINE_LOCATION(1 shl 6),
+ GRANT_STORAGE_SUPERGROUP(1 shl 7),
+ REVOKE_STORAGE_SUPERGROUP(1 shl 8),
GRANT_STORAGE_SUPERGROUP_CONFIRMED(
GRANT_STORAGE_SUPERGROUP.value or GRANT_FOREGROUND.value),
- REVOKE_STORAGE_SUPERGROUP_CONFIRMED(REVOKE_STORAGE_SUPERGROUP.value or REVOKE_BOTH.value);
+ REVOKE_STORAGE_SUPERGROUP_CONFIRMED(REVOKE_STORAGE_SUPERGROUP.value or REVOKE_BOTH.value),
+ PHOTOS_SELECTED( 1 shl 9);
infix fun andValue(other: ChangeRequest): Int {
return value and other.value
@@ -141,13 +160,16 @@ class AppPermissionViewModel(
ASK(4),
DENY(5),
DENY_FOREGROUND(6),
- LOCATION_ACCURACY(7);
+ LOCATION_ACCURACY(7),
+ SELECT_PHOTOS( 8);
}
private val isStorageAndLessThanT =
permGroupName == Manifest.permission_group.STORAGE && !SdkLevel.isAtLeastT()
private var hasConfirmedRevoke = false
private var lightAppPermGroup: LightAppPermGroup? = null
+ private var photoPickerLauncher: ActivityResultLauncher<Unit>? = null
+ private var photoPickerResultConsumer: Consumer<Int>? = null
private val mediaStorageSupergroupPermGroups = mutableMapOf<String, LightAppPermGroup>()
@@ -200,8 +222,8 @@ class AppPermissionViewModel(
/**
* A livedata which computes the state of the radio buttons
*/
- val buttonStateLiveData = object
- : SmartUpdateMediatorLiveData<@JvmSuppressWildcards Map<ButtonType, ButtonState>>() {
+ val buttonStateLiveData = object :
+ SmartUpdateMediatorLiveData<@JvmSuppressWildcards Map<ButtonType, ButtonState>>() {
private val appPermGroupLiveData = LightAppPermGroupLiveData[packageName, permGroupName,
user]
@@ -275,6 +297,7 @@ class AppPermissionViewModel(
val askState = ButtonState()
val deniedState = ButtonState()
val deniedForegroundState = ButtonState()
+ val selectState = ButtonState()
askOneTimeState.isShown = group.foreground.isGranted && group.isOneTime
askState.isShown = PermissionMapping.supportsOneTimeGrant(permGroupName) &&
@@ -315,6 +338,19 @@ class AppPermissionViewModel(
val detailId = getIndividualPermissionDetailResId(group)
detailResIdLiveData.value = detailId.first to detailId.second
}
+ } else if (KotlinUtils.isPhotoPickerPromptEnabled() &&
+ group.permGroupName == READ_MEDIA_VISUAL &&
+ group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU) {
+ // Allow / Select Photos / Deny case
+ allowedState.isShown = true
+ deniedState.isShown = true
+ selectState.isShown = true
+
+ deniedState.isChecked = !group.isGranted
+ val onlyUserSelectedGranted = group.isGranted && group.permissions.values.all {
+ it.name == READ_MEDIA_VISUAL_USER_SELECTED || !it.isGrantedIncludingAppOp }
+ selectState.isChecked = onlyUserSelectedGranted
+ allowedState.isChecked = group.isGranted && !onlyUserSelectedGranted
} else {
// Allow / Deny case
allowedState.isShown = true
@@ -376,9 +412,8 @@ class AppPermissionViewModel(
}
if (shouldShowLocationAccuracy == null) {
- shouldShowLocationAccuracy = group.permGroupName == LOCATION &&
- group.permissions.containsKey(ACCESS_FINE_LOCATION) &&
- isLocationAccuracyEnabled()
+ shouldShowLocationAccuracy = isLocationAccuracyEnabled() &&
+ group.permissions.containsKey(ACCESS_FINE_LOCATION)
}
val locationAccuracyState = ButtonState(isFineLocationChecked(group),
true, false, null)
@@ -393,10 +428,36 @@ class AppPermissionViewModel(
ALLOW to allowedState, ALLOW_ALWAYS to allowedAlwaysState,
ALLOW_FOREGROUND to allowedForegroundState, ASK_ONCE to askOneTimeState,
ASK to askState, DENY to deniedState, DENY_FOREGROUND to deniedForegroundState,
- LOCATION_ACCURACY to locationAccuracyState)
+ LOCATION_ACCURACY to locationAccuracyState, SELECT_PHOTOS to selectState)
}
}
+ fun registerPhotoPickerResultIfNeeded(fragment: Fragment) {
+ if (permGroupName != READ_MEDIA_VISUAL) {
+ return
+ }
+ photoPickerLauncher = fragment.registerForActivityResult(
+ object : ActivityResultContract<Unit, Int>() {
+ override fun parseResult(resultCode: Int, intent: Intent?): Int {
+ return resultCode
+ }
+
+ override fun createIntent(context: Context, input: Unit): Intent {
+ return Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)
+ .putExtra(Intent.EXTRA_UID, lightAppPermGroup?.packageInfo?.uid)
+ .setType(KotlinUtils.getMimeTypeForPermissions(
+ lightAppPermGroup?.foregroundPermNames ?: emptyList()))
+ }
+ }) { result ->
+ photoPickerResultConsumer?.accept(result)
+ }
+ }
+
+ fun openPhotoPicker(onResult: Consumer<Int>) {
+ photoPickerResultConsumer = onResult
+ photoPickerLauncher?.launch(Unit)
+ }
+
private fun isFineLocationChecked(group: LightAppPermGroup): Boolean {
if (shouldShowLocationAccuracy == true) {
val coarseLocation = group.permissions[ACCESS_COARSE_LOCATION]!!
@@ -495,6 +556,60 @@ class AppPermissionViewModel(
return true
}
+ // TODO(b/264309196): extract should show permission rationale logic as a util method.
+ fun shouldShowPermissionRationale(
+ safetyLabelInfo: SafetyLabelInfo,
+ groupName: String
+ ): Boolean {
+ val safetyLabel = safetyLabelInfo.safetyLabel
+ if (safetyLabel == null) {
+ return false
+ }
+ if (safetyLabel.dataLabel.dataShared.isEmpty()) {
+ return false
+ }
+ val categoriesForPermission: List<String> =
+ SafetyLabelPermissionMapping.getCategoriesForPermissionGroup(groupName)
+ categoriesForPermission.forEach categoryLoop@{ category ->
+ val dataCategory: DataCategory? = safetyLabel.dataLabel.dataShared[category]
+ if (dataCategory == null) {
+ // Continue to next
+ return@categoryLoop
+ }
+ val typesForCategory = DataTypeConstants.getValidDataTypesForCategory(category)
+ typesForCategory.forEach typeLoop@{ type ->
+ val dataType: DataType? = dataCategory.dataTypes[type]
+ if (dataType == null) {
+ // Continue to next
+ return@typeLoop
+ }
+ if (dataType.purposeSet.isNotEmpty()) {
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ /**
+ * Shows the Permission Rationale Dialog. For use with U+ only, otherwise no-op.
+ *
+ * @param activity The current activity
+ * @param groupName The name of the permission group whose fragment should be opened
+ */
+ fun showPermissionRationaleActivity(activity: Activity, groupName: String) {
+ if (!isPermissionRationaleEnabled()) {
+ return
+ }
+
+ val intent = Intent(activity, PermissionRationaleActivity::class.java).apply {
+ putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
+ putExtra(Constants.EXTRA_SESSION_ID, sessionId)
+ }
+ activity.startActivity(intent)
+ }
+
/**
* Navigate to either the App Permission Groups screen, or the Permission Apps Screen.
* @param fragment The current fragment
@@ -551,7 +666,8 @@ class AppPermissionViewModel(
if (changeRequest == ChangeRequest.GRANT_FINE_LOCATION) {
if (!group.isOneTime) {
- KotlinUtils.grantForegroundRuntimePermissions(app, group)
+ val newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, group)
+ logPermissionChanges(group, newGroup, buttonClicked)
}
KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, group, true)
return
@@ -559,13 +675,25 @@ class AppPermissionViewModel(
if (changeRequest == ChangeRequest.REVOKE_FINE_LOCATION) {
if (!group.isOneTime) {
- KotlinUtils.revokeForegroundRuntimePermissions(app, group,
+ val newGroup = KotlinUtils.revokeForegroundRuntimePermissions(app, group,
filterPermissions = listOf(ACCESS_FINE_LOCATION))
+ logPermissionChanges(group, newGroup, buttonClicked)
}
KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, group, false)
return
}
+ if (changeRequest == ChangeRequest.PHOTOS_SELECTED) {
+ val nonSelectedPerms = group.permissions.keys.filter {
+ it != READ_MEDIA_VISUAL_USER_SELECTED }
+ var newGroup = KotlinUtils.revokeForegroundRuntimePermissions(app, group,
+ filterPermissions = nonSelectedPerms)
+ newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, newGroup,
+ filterPermissions = listOf(READ_MEDIA_VISUAL_USER_SELECTED))
+ logPermissionChanges(group, newGroup, buttonClicked)
+ return
+ }
+
val shouldGrantForeground = changeRequest andValue ChangeRequest.GRANT_FOREGROUND != 0
val shouldGrantBackground = changeRequest andValue ChangeRequest.GRANT_BACKGROUND != 0
val shouldRevokeForeground = changeRequest andValue ChangeRequest.REVOKE_FOREGROUND != 0
@@ -667,11 +795,12 @@ class AppPermissionViewModel(
}
if (shouldGrantForeground) {
- if (shouldShowLocationAccuracy == true && !isFineLocationChecked(newGroup)) {
- newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, newGroup,
- filterPermissions = listOf(ACCESS_COARSE_LOCATION))
+ newGroup = if (shouldShowLocationAccuracy == true &&
+ !isFineLocationChecked(newGroup)) {
+ KotlinUtils.grantForegroundRuntimePermissions(app, newGroup,
+ filterPermissions = listOf(ACCESS_COARSE_LOCATION))
} else {
- newGroup = KotlinUtils.grantForegroundRuntimePermissions(app, newGroup)
+ KotlinUtils.grantForegroundRuntimePermissions(app, newGroup)
}
if (!wasForegroundGranted) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
index 5763d92a7..65d50fe63 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt
@@ -14,13 +14,15 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.model.v31
+package com.android.permissioncontroller.permission.ui.model
import android.Manifest
import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.Manifest.permission.POST_NOTIFICATIONS
+import android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
import android.Manifest.permission_group.LOCATION
+import android.Manifest.permission_group.READ_MEDIA_VISUAL
import android.annotation.SuppressLint
import android.app.Activity
import android.app.Application
@@ -30,15 +32,24 @@ import android.content.pm.PackageManager
import android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED
import android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED
import android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET
+import android.healthconnect.HealthConnectManager.ACTION_REQUEST_HEALTH_PERMISSIONS
+import android.healthconnect.HealthConnectManager.isHealthPermission
+import android.healthconnect.HealthPermissions.HEALTH_PERMISSION_GROUP
import android.os.Build
import android.os.Bundle
import android.os.Process
import android.permission.PermissionManager
+import android.provider.MediaStore
import android.util.Log
+import androidx.annotation.ChecksSdkIntAtLeast
import androidx.core.util.Consumer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.modules.utils.build.SdkLevel
+import com.android.permission.safetylabel.DataCategory
+import com.android.permission.safetylabel.DataType
+import com.android.permission.safetylabel.DataTypeConstants
+import com.android.permission.safetylabel.SafetyLabel
import com.android.permissioncontroller.Constants
import com.android.permissioncontroller.DeviceUtils
import com.android.permissioncontroller.PermissionControllerApplication
@@ -50,6 +61,7 @@ import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_POLICY_FIXED
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_RESTRICTED_PERMISSION
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__PHOTOS_SELECTED
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_IN_SETTINGS
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
@@ -59,10 +71,10 @@ import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED
import com.android.permissioncontroller.auto.DrivingDecisionReminderService
-import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
+import com.android.permissioncontroller.permission.data.SafetyLabelInfoLiveData
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
import com.android.permissioncontroller.permission.data.get
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
@@ -72,16 +84,21 @@ import com.android.permissioncontroller.permission.service.PermissionChangeStora
import com.android.permissioncontroller.permission.service.v33.PermissionDecisionStorageImpl
import com.android.permissioncontroller.permission.ui.AutoGrantPermissionsNotifier
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALL_PHOTOS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_FOREGROUND_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_MORE_SELECTED_PHOTOS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ONE_TIME_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_SELECTED_PHOTOS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.COARSE_RADIO_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_AND_DONT_ASK_AGAIN_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DENY_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_BOTH_LOCATIONS
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_COARSE_LOCATION_ONLY
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DIALOG_WITH_FINE_LOCATION_ONLY
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.DONT_ALLOW_MORE_SELECTED_PHOTOS_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.FINE_RADIO_BUTTON
+import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.INTENT_PHOTOS_SELECTED
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LINK_TO_SETTINGS
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.LOCATION_ACCURACY_LAYOUT
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NEXT_BUTTON
@@ -90,18 +107,27 @@ import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.N
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON
import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON
-import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.CANCELED
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_DO_NOT_ASK_AGAIN
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.DENIED_MORE_PHOTOS
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ALWAYS
import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_FOREGROUND_ONLY
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_ONE_TIME
+import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler.GRANTED_USER_SELECTED
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED
import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT
-import com.android.permissioncontroller.permission.ui.handheld.v31.getDefaultPrecision
-import com.android.permissioncontroller.permission.ui.handheld.v31.isLocationAccuracyEnabled
+import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity
import com.android.permissioncontroller.permission.utils.AdminRestrictedPermissionsUtils
import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getDefaultPrecision
+import com.android.permissioncontroller.permission.utils.KotlinUtils.grantBackgroundRuntimePermissions
+import com.android.permissioncontroller.permission.utils.KotlinUtils.grantForegroundRuntimePermissions
+import com.android.permissioncontroller.permission.utils.KotlinUtils.isLocationAccuracyEnabled
+import com.android.permissioncontroller.permission.utils.KotlinUtils.isPermissionRationaleEnabled
+import com.android.permissioncontroller.permission.utils.PermissionMapping
+import com.android.permissioncontroller.permission.utils.SafetyLabelPermissionMapping
import com.android.permissioncontroller.permission.utils.SafetyNetLogger
import com.android.permissioncontroller.permission.utils.Utils
@@ -126,10 +152,12 @@ class GrantPermissionsViewModel(
private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
private val user = Process.myUserHandle()
private val packageInfoLiveData = LightPackageInfoLiveData[packageName, user]
+ private val safetyLabelInfoLiveData = SafetyLabelInfoLiveData[packageName, user]
private val dpm = app.getSystemService(DevicePolicyManager::class.java)!!
private val permissionPolicy = dpm.getPermissionPolicy(null)
private val permGroupsToSkip = mutableListOf<String>()
private var groupStates = mutableMapOf<Pair<String, Boolean>, GroupState>()
+ private val permissionRationaleEnabled: Boolean by lazy { isPermissionRationaleEnabled() }
private var autoGrantNotifier: AutoGrantPermissionsNotifier? = null
private fun getAutoGrantNotifier(): AutoGrantPermissionsNotifier {
@@ -138,6 +166,7 @@ class GrantPermissionsViewModel(
}
private lateinit var packageInfo: LightPackageInfo
+ private var safetyLabel: SafetyLabel? = null
// All permissions that could possibly be affected by the provided requested permissions, before
// filtering system fixed, auto grant, etc.
@@ -157,7 +186,9 @@ class GrantPermissionsViewModel(
val locationVisibilities: List<Boolean> = List(NEXT_LOCATION_DIALOG) { false },
val message: RequestMessage = RequestMessage.FG_MESSAGE,
val detailMessage: RequestMessage = RequestMessage.NO_MESSAGE,
- val sendToSettingsImmediately: Boolean = false
+ val sendToSettingsImmediately: Boolean = false,
+ val openPhotoPicker: Boolean = false,
+ val showPermissionRationale: Boolean = false
) {
val groupName = groupInfo.name
}
@@ -172,18 +203,33 @@ class GrantPermissionsViewModel(
private val LOG_TAG = GrantPermissionsViewModel::class.java.simpleName
private val packagePermissionsLiveData = PackagePermissionsLiveData[packageName, user]
+ // TODO(b/260873483): only query safety label for supported permission groups. should only
+ // query location, but currently queries for all groups
init {
addSource(packagePermissionsLiveData) { onPackageLoaded() }
addSource(packageInfoLiveData) { onPackageLoaded() }
+ if (permissionRationaleEnabled) {
+ addSource(safetyLabelInfoLiveData) { onPackageLoaded() }
+ }
+
// Load package state, if available
onPackageLoaded()
}
private fun onPackageLoaded() {
- if (packageInfoLiveData.isStale || packagePermissionsLiveData.isStale) {
+ if (packageInfoLiveData.isStale ||
+ packagePermissionsLiveData.isStale ||
+ (permissionRationaleEnabled && safetyLabelInfoLiveData.isStale)) {
return
}
+ safetyLabel =
+ if (permissionRationaleEnabled) {
+ safetyLabelInfoLiveData.value?.safetyLabel
+ } else {
+ null
+ }
+
val groups = packagePermissionsLiveData.value
val pI = packageInfoLiveData.value
if (groups == null || groups.isEmpty() || pI == null) {
@@ -207,7 +253,7 @@ class GrantPermissionsViewModel(
}
unfilteredAffectedPermissions = allAffectedPermissions.toList()
- getAppPermGroups(groups.toMutableMap().apply {
+ setAppPermGroupsLiveDatas(groups.toMutableMap().apply {
remove(PackagePermissionsLiveData.NON_RUNTIME_NORMAL_PERMS)
})
@@ -217,7 +263,7 @@ class GrantPermissionsViewModel(
}
}
- private fun getAppPermGroups(groups: Map<String, List<String>>) {
+ private fun setAppPermGroupsLiveDatas(groups: Map<String, List<String>>) {
val requestedGroups = groups.filter { (_, perms) ->
perms.any { it in unfilteredAffectedPermissions }
@@ -274,10 +320,10 @@ class GrantPermissionsViewModel(
groupStates = getRequiredGroupStates(
appPermGroupLiveDatas.mapNotNull { it.value.value })
}
- getRequestInfosFromGroupStates()
+ setRequestInfosFromGroupStates()
}
- private fun getRequestInfosFromGroupStates() {
+ private fun setRequestInfosFromGroupStates() {
val requestInfos = mutableListOf<RequestInfo>()
for ((key, groupState) in groupStates) {
val groupInfo = groupState.group.permGroupInfo
@@ -323,7 +369,29 @@ class GrantPermissionsViewModel(
// Whether or not to use the foreground, background, or no detail message.
// null ==
var detailMessage = RequestMessage.NO_MESSAGE
- if (groupState.group.packageInfo.targetSdkVersion >=
+
+ if (shouldShowMorePhotosMessage(groupState.group)) {
+ buttonVisibilities[ALLOW_BUTTON] = false
+ buttonVisibilities[DENY_BUTTON] = false
+ buttonVisibilities[ALLOW_MORE_SELECTED_PHOTOS_BUTTON] = true
+ buttonVisibilities[DONT_ALLOW_MORE_SELECTED_PHOTOS_BUTTON] = true
+ message = RequestMessage.MORE_PHOTOS_MESSAGE
+ } else if (KotlinUtils.isPhotoPickerPromptEnabled() &&
+ groupState.group.permGroupName == READ_MEDIA_VISUAL &&
+ groupState.group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU) {
+ // If the USER_SELECTED permission is user fixed and granted, or the app is only
+ // requesting USER_SELECTED, direct straight to photo picker
+ val userPerm = groupState.group.permissions[READ_MEDIA_VISUAL_USER_SELECTED]
+ if ((userPerm?.isUserFixed == true && userPerm.isGrantedIncludingAppOp) ||
+ groupState.affectedPermissions == listOf(READ_MEDIA_VISUAL_USER_SELECTED)) {
+ requestInfos.add(RequestInfo(groupInfo, openPhotoPicker = true))
+ continue
+ } else {
+ buttonVisibilities[ALLOW_BUTTON] = false
+ buttonVisibilities[ALLOW_SELECTED_PHOTOS_BUTTON] = true
+ buttonVisibilities[ALLOW_ALL_PHOTOS_BUTTON] = true
+ }
+ } else if (groupState.group.packageInfo.targetSdkVersion >=
minSdkForOrderedSplitPermissions) {
if (isBackground || Utils.hasPermWithBackgroundModeCompat(groupState.group)) {
if (needFgPermissions) {
@@ -440,7 +508,7 @@ class GrantPermissionsViewModel(
// Show location permission dialogs based on location permissions
val locationVisibilities = MutableList(NEXT_LOCATION_DIALOG) { false }
if (groupState.group.permGroupName == LOCATION && isLocationAccuracyEnabled() &&
- packageInfo.targetSdkVersion >= Build.VERSION_CODES.S) {
+ packageInfo.targetSdkVersion >= Build.VERSION_CODES.S) {
if (needFgPermissions) {
locationVisibilities[LOCATION_ACCURACY_LAYOUT] = true
if (fgState != null &&
@@ -514,19 +582,12 @@ class GrantPermissionsViewModel(
buttonVisibilities,
locationVisibilities,
message,
- detailMessage))
+ detailMessage,
+ showPermissionRationale = shouldShowPermissionRationale(
+ safetyLabel, groupState)))
}
- requestInfos.sortWith(Comparator { rhs, lhs ->
- val rhsHasOneTime = rhs.buttonVisibilities[ALLOW_ONE_TIME_BUTTON]
- val lhsHasOneTime = lhs.buttonVisibilities[ALLOW_ONE_TIME_BUTTON]
- if (rhsHasOneTime && !lhsHasOneTime) {
- -1
- } else if (!rhsHasOneTime && lhsHasOneTime) {
- 1
- } else {
- rhs.groupName.compareTo(lhs.groupName)
- }
- })
+
+ sortPermissionGroups(requestInfos)
value = if (requestInfos.any { it.sendToSettingsImmediately } &&
requestInfos.size > 1) {
@@ -539,6 +600,55 @@ class GrantPermissionsViewModel(
}
}
+ fun sortPermissionGroups(requestInfos: MutableList<RequestInfo>) {
+ requestInfos.sortWith { rhs, lhs ->
+ val rhsHasOneTime = rhs.buttonVisibilities[ALLOW_ONE_TIME_BUTTON]
+ val lhsHasOneTime = lhs.buttonVisibilities[ALLOW_ONE_TIME_BUTTON]
+ if (rhsHasOneTime && !lhsHasOneTime) {
+ -1
+ } else if ((!rhsHasOneTime && lhsHasOneTime) ||
+ isHealthPermissionGroup(rhs.groupName)
+ ) {
+ 1
+ } else {
+ rhs.groupName.compareTo(lhs.groupName)
+ }
+ }
+ }
+
+ private fun shouldShowPermissionRationale(
+ safetyLabel: SafetyLabel?,
+ groupState: GroupState
+ ): Boolean {
+ if (safetyLabel == null || safetyLabel.dataLabel.dataShared.isEmpty()) {
+ return false
+ }
+
+ val groupName = groupState.group.permGroupName
+ val categoriesForPermission: List<String> =
+ SafetyLabelPermissionMapping.getCategoriesForPermissionGroup(groupName)
+ categoriesForPermission.forEach categoryLoop@ { category ->
+ val dataCategory: DataCategory? = safetyLabel.dataLabel.dataShared[category]
+ if (dataCategory == null) {
+ // Continue to next
+ return@categoryLoop
+ }
+ val typesForCategory = DataTypeConstants.getValidDataTypesForCategory(category)
+ typesForCategory.forEach typeLoop@ { type ->
+ val dataType: DataType? = dataCategory.dataTypes[type]
+ if (dataType == null) {
+ // Continue to next
+ return@typeLoop
+ }
+ if (dataType.purposeSet.isNotEmpty()) {
+ return true
+ }
+ }
+ }
+
+ return false
+ }
+
/**
* Converts a list of LightAppPermGroups into a list of GroupStates
*/
@@ -645,6 +755,10 @@ class GrantPermissionsViewModel(
return false
}
+ if (HEALTH_PERMISSION_GROUP == group.permGroupName) {
+ return !(group.permissions[perm]?.isUserFixed ?: true)
+ }
+
val subGroup = if (perm in group.backgroundPermNames) {
group.background
} else {
@@ -666,6 +780,9 @@ class GrantPermissionsViewModel(
// is still grantable.
return true
}
+ } else if (perm == READ_MEDIA_VISUAL_USER_SELECTED) {
+ // If USER_SELECTED is granted as fixed, we should immediately show the photo picker
+ return true
}
reportRequestResult(perm,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED_USER_FIXED)
@@ -716,21 +833,13 @@ class GrantPermissionsViewModel(
}
if ((isBackground && group.background.isGrantedExcludingRWROrAllRWR ||
- !isBackground && group.foreground.isGrantedExcludingRWROrAllRWR)) {
- // If FINE location is not granted, do not grant it automatically when COARSE
- // location is already granted.
- if (group.permGroupName == LOCATION &&
- group.allPermissions[ACCESS_FINE_LOCATION]?.isGrantedIncludingAppOp
- == false) {
- return STATE_UNKNOWN
- }
-
+ !isBackground && group.foreground.isGrantedExcludingRWROrAllRWR) &&
+ canAutoGrantWholeGroup(group)) {
if (group.permissions[perm]?.isGrantedIncludingAppOp == false) {
if (isBackground) {
- KotlinUtils.grantBackgroundRuntimePermissions(app, group, listOf(perm))
+ grantBackgroundRuntimePermissions(app, group, listOf(perm))
} else {
- KotlinUtils.grantForegroundRuntimePermissions(app, group, listOf(perm),
- group.isOneTime)
+ grantForegroundRuntimePermissions(app, group, listOf(perm), group.isOneTime)
}
KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_USER_SET to false,
FLAG_PERMISSION_USER_FIXED to false, filterPermissions = listOf(perm))
@@ -747,6 +856,33 @@ class GrantPermissionsViewModel(
return STATE_UNKNOWN
}
+ /**
+ * Determines if remaining permissions in the group can be auto granted based on
+ * granted permissions in the group.
+ */
+ private fun canAutoGrantWholeGroup(group: LightAppPermGroup): Boolean {
+ // If FINE location is not granted, do not grant it automatically when COARSE
+ // location is already granted.
+ if (group.permGroupName == LOCATION &&
+ group.allPermissions[ACCESS_FINE_LOCATION]?.isGrantedIncludingAppOp == false) {
+ return false
+ }
+ // If READ_MEDIA_VISUAL_USER_SELECTED is the only permission in the group that is granted,
+ // do not grant.
+ if (isVisualUserSelectedOnlyGranted(group) ||
+ HEALTH_PERMISSION_GROUP == group.permGroupName) {
+ return false
+ }
+ return true
+ }
+
+ private fun isVisualUserSelectedOnlyGranted(group: LightAppPermGroup): Boolean {
+ return KotlinUtils.isPhotoPickerPromptEnabled() &&
+ group.permGroupName == READ_MEDIA_VISUAL && group.permissions.values.all {
+ (it.name == READ_MEDIA_VISUAL_USER_SELECTED) || !it.isGrantedIncludingAppOp } &&
+ group.permissions[READ_MEDIA_VISUAL_USER_SELECTED]?.isGrantedIncludingAppOp == true
+ }
+
private fun getStateFromPolicy(perm: String, group: LightAppPermGroup): Int {
val isBackground = perm in group.backgroundPermNames
var skipGroup = false
@@ -756,9 +892,9 @@ class GrantPermissionsViewModel(
if (AdminRestrictedPermissionsUtils.mayAdminGrantPermission(
app, perm, user.identifier)) {
if (isBackground) {
- KotlinUtils.grantBackgroundRuntimePermissions(app, group, listOf(perm))
+ grantBackgroundRuntimePermissions(app, group, listOf(perm))
} else {
- KotlinUtils.grantForegroundRuntimePermissions(app, group, listOf(perm))
+ grantForegroundRuntimePermissions(app, group, listOf(perm))
}
KotlinUtils.setGroupFlags(app, group, FLAG_PERMISSION_POLICY_FIXED to true,
FLAG_PERMISSION_USER_SET to false, FLAG_PERMISSION_USER_FIXED to false,
@@ -831,15 +967,18 @@ class GrantPermissionsViewModel(
val foregroundGroupState = groupStates[groupName to false]
val backgroundGroupState = groupStates[groupName to true]
when (result) {
- GrantPermissionsViewHandler.CANCELED -> {
+ CANCELED -> {
if (foregroundGroupState != null) {
reportRequestResult(foregroundGroupState.affectedPermissions,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED)
+ foregroundGroupState.state = STATE_SKIPPED
}
if (backgroundGroupState != null) {
reportRequestResult(backgroundGroupState.affectedPermissions,
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED)
+ backgroundGroupState.state = STATE_SKIPPED
}
+ requestInfosLiveData.update()
return
}
GRANTED_ALWAYS -> {
@@ -866,7 +1005,7 @@ class GrantPermissionsViewModel(
doNotAskAgain = false)
}
}
- GrantPermissionsViewHandler.GRANTED_ONE_TIME -> {
+ GRANTED_ONE_TIME -> {
if (foregroundGroupState != null) {
onPermissionGrantResultSingleState(foregroundGroupState,
affectedForegroundPermissions, granted = true, isOneTime = true,
@@ -878,6 +1017,11 @@ class GrantPermissionsViewModel(
doNotAskAgain = false)
}
}
+ GRANTED_USER_SELECTED, DENIED_MORE_PHOTOS -> {
+ if (foregroundGroupState != null) {
+ grantUserSelectedVisualGroupPermissions(foregroundGroupState)
+ }
+ }
DENIED -> {
if (foregroundGroupState != null) {
onPermissionGrantResultSingleState(foregroundGroupState,
@@ -905,6 +1049,32 @@ class GrantPermissionsViewModel(
}
}
+ private fun grantUserSelectedVisualGroupPermissions(groupState: GroupState) {
+ val userSelectedPerm =
+ groupState.group.permissions[READ_MEDIA_VISUAL_USER_SELECTED] ?: return
+ val nonSelectedPerms = groupState.affectedPermissions
+ .filter { it != READ_MEDIA_VISUAL_USER_SELECTED }
+ if (userSelectedPerm.isImplicit) {
+ // If the permission is implicit, grant USER_SELECTED as user set, and all other
+ // permissions as one time, and without app ops.
+ KotlinUtils.grantForegroundRuntimePermissions(app, groupState.group,
+ nonSelectedPerms, isOneTime = true, userFixed = false, withoutAppOps = true)
+ KotlinUtils.grantForegroundRuntimePermissions(app, groupState.group,
+ listOf(READ_MEDIA_VISUAL_USER_SELECTED))
+ onPermissionGrantResultSingleState(groupState, listOf(READ_MEDIA_VISUAL_USER_SELECTED),
+ granted = true, isOneTime = false, doNotAskAgain = false)
+ } else {
+ val setUserFixed = userSelectedPerm.isUserFixed || userSelectedPerm.isUserSet
+ KotlinUtils.grantForegroundRuntimePermissions(app, groupState.group,
+ listOf(READ_MEDIA_VISUAL_USER_SELECTED), userFixed = setUserFixed)
+ KotlinUtils.revokeForegroundRuntimePermissions(app, groupState.group,
+ userFixed = setUserFixed, oneTime = false, filterPermissions = nonSelectedPerms)
+ }
+ groupState.state = STATE_ALLOWED
+ reportButtonClickResult(groupState, true,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__PHOTOS_SELECTED)
+ }
+
@SuppressLint("NewApi")
private fun onPermissionGrantResultSingleState(
groupState: GroupState,
@@ -925,11 +1095,11 @@ class GrantPermissionsViewModel(
PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED
}
if (groupState.isBackground) {
- KotlinUtils.grantBackgroundRuntimePermissions(app, groupState.group,
+ grantBackgroundRuntimePermissions(app, groupState.group,
groupState.affectedPermissions)
} else {
if (affectedForegroundPermissions == null) {
- KotlinUtils.grantForegroundRuntimePermissions(app, groupState.group,
+ grantForegroundRuntimePermissions(app, groupState.group,
groupState.affectedPermissions, isOneTime)
// This prevents weird flag state when app targetSDK switches from S+ to R-
if (groupState.affectedPermissions.contains(ACCESS_FINE_LOCATION)) {
@@ -937,7 +1107,7 @@ class GrantPermissionsViewModel(
app, groupState.group, true)
}
} else {
- val newGroup = KotlinUtils.grantForegroundRuntimePermissions(app,
+ val newGroup = grantForegroundRuntimePermissions(app,
groupState.group, affectedForegroundPermissions, isOneTime)
if (!isOneTime || newGroup.isOneTime) {
KotlinUtils.setFlagsWhenLocationAccuracyChanged(app, newGroup,
@@ -969,6 +1139,10 @@ class GrantPermissionsViewModel(
}
groupState.state = STATE_DENIED
}
+ reportButtonClickResult(groupState, granted, result)
+ }
+
+ private fun reportButtonClickResult(groupState: GroupState, granted: Boolean, result: Int) {
reportRequestResult(groupState.affectedPermissions, result)
// group state has changed, reload liveData
requestInfosLiveData.update()
@@ -1081,6 +1255,29 @@ class GrantPermissionsViewModel(
}
}
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private fun isHealthPermissionGroup(permGroupName: String): Boolean {
+ return SdkLevel.isAtLeastU() && HEALTH_PERMISSION_GROUP.equals(permGroupName)
+ }
+
+ fun handleHealthConnectPermissions(activity: Activity) {
+ if (activityResultCallback == null) {
+ activityResultCallback = Consumer {
+ permGroupsToSkip.add(HEALTH_PERMISSION_GROUP)
+ requestInfosLiveData.update()
+ }
+ val healthPermissions = unfilteredAffectedPermissions.filter { permission ->
+ isHealthPermission(activity, permission)
+ }.toTypedArray()
+ val intent: Intent = Intent(ACTION_REQUEST_HEALTH_PERMISSIONS)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ .putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, healthPermissions)
+ .putExtra(Intent.EXTRA_USER, Process.myUserHandle())
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE)
+ }
+ }
+
/**
* Send the user directly to the AppPermissionFragment. Used for R+ apps.
*
@@ -1089,7 +1286,6 @@ class GrantPermissionsViewModel(
*/
fun sendDirectlyToSettings(activity: Activity, groupName: String) {
if (activityResultCallback == null) {
- startAppPermissionFragment(activity, groupName)
activityResultCallback = Consumer { data ->
if (data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED) == null) {
// User didn't interact, count against rate limit
@@ -1108,9 +1304,36 @@ class GrantPermissionsViewModel(
// Update our liveData now that there is a new skipped group
requestInfosLiveData.update()
}
+ startAppPermissionFragment(activity, groupName)
}
}
+ private fun shouldShowMorePhotosMessage(group: LightAppPermGroup): Boolean {
+ return isVisualUserSelectedOnlyGranted(group) &&
+ group.permissions[READ_MEDIA_VISUAL_USER_SELECTED]?.isImplicit == true &&
+ group.packageInfo.targetSdkVersion >= Build.VERSION_CODES.TIRAMISU
+ }
+
+ fun openPhotoPicker(activity: Activity, result: Int) {
+ if (activityResultCallback != null) {
+ return
+ }
+ val permissions = groupStates[READ_MEDIA_VISUAL to false]?.affectedPermissions ?: return
+ activityResultCallback = Consumer { data ->
+ val anySelected = data?.getBooleanExtra(INTENT_PHOTOS_SELECTED, true) == true
+ if (anySelected) {
+ onPermissionGrantResult(READ_MEDIA_VISUAL, null, result)
+ } else {
+ onPermissionGrantResult(READ_MEDIA_VISUAL, null, CANCELED)
+ }
+ logPhotoPickerInteraction(result)
+ requestInfosLiveData.update()
+ }
+ activity.startActivityForResult(Intent(MediaStore.ACTION_USER_SELECT_IMAGES_FOR_APP)
+ .putExtra(Intent.EXTRA_UID, packageInfo.uid)
+ .setType(KotlinUtils.getMimeTypeForPermissions(permissions)), PHOTO_PICKER_REQUEST_CODE)
+ }
+
/**
* Send the user to the AppPermissionFragment from a link. Used for Q- apps
*
@@ -1130,6 +1353,34 @@ class GrantPermissionsViewModel(
}
}
+ /**
+ * Shows the Permission Rationale Dialog. For use with U+ only, otherwise no-op.
+ *
+ * @param activity The current activity
+ * @param groupName The name of the permission group whose fragment should be opened
+ */
+ fun showPermissionRationaleActivity(activity: Activity, groupName: String) {
+ if (!SdkLevel.isAtLeastU()) {
+ return
+ }
+
+ val intent = Intent(activity, PermissionRationaleActivity::class.java).apply {
+ putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
+ putExtra(Constants.EXTRA_SESSION_ID, sessionId)
+ }
+ activityResultCallback = Consumer { data ->
+ val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED)
+ if (returnGroupName != null) {
+ permGroupsToSkip.add(returnGroupName)
+ val result = data.getIntExtra(EXTRA_RESULT_PERMISSION_RESULT, CANCELED)
+ logSettingsInteraction(returnGroupName, result)
+ requestInfosLiveData.update()
+ }
+ }
+ activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE)
+ }
+
private fun startAppPermissionFragment(activity: Activity, groupName: String) {
val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSION)
.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
@@ -1146,6 +1397,20 @@ class GrantPermissionsViewModel(
return "${this::class.java.name}_${groupName}_$isBackground"
}
+ private fun logPhotoPickerInteraction(result: Int) {
+ val foregroundGroupState = groupStates[READ_MEDIA_VISUAL to false] ?: return
+ when (result) {
+ GRANTED_USER_SELECTED -> {
+ reportRequestResult(foregroundGroupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__PHOTOS_SELECTED)
+ }
+ CANCELED -> {
+ reportRequestResult(foregroundGroupState.affectedPermissions,
+ PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__IGNORED)
+ }
+ }
+ }
+
private fun logSettingsInteraction(groupName: String, result: Int) {
val foregroundGroupState = groupStates[groupName to false]
val backgroundGroupState = groupStates[groupName to true]
@@ -1250,7 +1515,8 @@ class GrantPermissionsViewModel(
}
companion object {
- private const val APP_PERMISSION_REQUEST_CODE = 1
+ const val APP_PERMISSION_REQUEST_CODE = 1
+ const val PHOTO_PICKER_REQUEST_CODE = 2
private const val STATE_UNKNOWN = 0
private const val STATE_ALLOWED = 1
private const val STATE_DENIED = 2
@@ -1269,10 +1535,11 @@ class GrantPermissionsViewModel(
FG_FINE_LOCATION_MESSAGE(4),
FG_COARSE_LOCATION_MESSAGE(5),
STORAGE_SUPERGROUP_MESSAGE_Q_TO_S(6),
- STORAGE_SUPERGROUP_MESSAGE_PRE_Q(7);
+ STORAGE_SUPERGROUP_MESSAGE_PRE_Q(7),
+ MORE_PHOTOS_MESSAGE(8)
}
- fun filterNotificationPermissionIfNeededSync(
+ fun filterPermissionsIfNeededSync(
packageName: String,
permissions: Array<String>?
): Array<String>? {
@@ -1280,17 +1547,19 @@ class GrantPermissionsViewModel(
return null
}
- try {
- val targetSdk = PermissionControllerApplication.get().packageManager
+ val targetSdk = try {
+ PermissionControllerApplication.get().packageManager
.getPackageInfo(packageName, 0).applicationInfo.targetSdkVersion
- if (targetSdk > Build.VERSION_CODES.S_V2) {
- return permissions
- }
} catch (e: PackageManager.NameNotFoundException) {
- return permissions
+ Build.VERSION_CODES.TIRAMISU
+ }
+ var permsList = permissions.toMutableList()
+
+ if (targetSdk < Build.VERSION_CODES.TIRAMISU) {
+ permsList.remove(POST_NOTIFICATIONS)
}
- return permissions.toList().filter { it != POST_NOTIFICATIONS }.toTypedArray()
+ return permsList.toTypedArray()
}
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
index 682ec108f..dd89b7470 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ManageStandardPermissionsViewModel.kt
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.permission.ui.model
import android.Manifest
import android.app.Application
import android.content.Intent
+import android.healthconnect.HealthPermissions.HEALTH_PERMISSION_GROUP
import android.os.Bundle
import androidx.fragment.app.Fragment
import androidx.lifecycle.AndroidViewModel
@@ -73,6 +74,11 @@ class ManageStandardPermissionsViewModel(
Utils.navigateToNotificationSettings(fragment.context!!)
return
}
+ if (Utils.isHealthPermissionUiEnabled() &&
+ groupName == HEALTH_PERMISSION_GROUP) {
+ Utils.navigateToHealthConnectSettings(fragment.context!!)
+ return
+ }
fragment.findNavController().navigateSafe(R.id.manage_to_perm_apps, args)
}
@@ -99,4 +105,4 @@ class NumCustomPermGroupsWithPackagesLiveData() :
override fun onUpdate() {
value = customPermGroupPackages.value?.size ?: 0
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
index 9814a115f..cd1a936f7 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionAppsViewModel.kt
@@ -38,25 +38,25 @@ import androidx.savedstate.SavedStateRegistryOwner
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.PermissionControllerStatsLog
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED
-import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND
import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__DENIED
+import com.android.permissioncontroller.PermissionControllerStatsLog.PERMISSION_APPS_FRAGMENT_VIEWED__CATEGORY__UNDEFINED
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.AllPackageInfosLiveData
import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData
import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState
import com.android.permissioncontroller.permission.data.SinglePermGroupPackagesUiInfoLiveData
-import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState
+import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
import com.android.permissioncontroller.permission.ui.Category
import com.android.permissioncontroller.permission.ui.LocationProviderInterceptDialog
-import com.android.permissioncontroller.permission.ui.handheld.v31.is7DayToggleEnabled
import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.CREATION_LOGGED_KEY
import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.HAS_SYSTEM_APPS_KEY
import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.SHOULD_SHOW_SYSTEM_KEY
import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel.Companion.SHOW_ALWAYS_ALLOWED
import com.android.permissioncontroller.permission.utils.KotlinUtils.getPackageUid
+import com.android.permissioncontroller.permission.utils.KotlinUtils.is7DayToggleEnabled
import com.android.permissioncontroller.permission.utils.LocationUtils
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.permission.utils.navigateSafe
@@ -169,8 +169,8 @@ class PermissionAppsViewModel(
}
}
- inner class CategorizedAppsLiveData(groupName: String)
- : MediatorLiveData<@kotlin.jvm.JvmSuppressWildcards
+ inner class CategorizedAppsLiveData(groupName: String) :
+ MediatorLiveData<@kotlin.jvm.JvmSuppressWildcards
Map<Category, List<Pair<String, UserHandle>>>>() {
private val packagesUiInfoLiveData = SinglePermGroupPackagesUiInfoLiveData[groupName]
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java
index b69fb66a7..4006bcfd3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreference.java
@@ -44,8 +44,12 @@ class PermissionGroupPreference extends Preference {
setTitle(label);
setIcon(tintedIcon);
setIntent(managePgIntent);
- updateSummary(permissionGroupInfo.getNonSystemGranted(),
- permissionGroupInfo.getNonSystemUserSetOrPreGranted());
+ updateSummary(permissionGroupInfo);
+ }
+
+ void updateSummary(PermGroupPackagesUiInfo info) {
+ updateSummary(info.getNonSystemGranted(),
+ info.getNonSystemTotal());
}
void updateSummary(int granted, int used) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java
index 49a01efaf..3541edead 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/PermissionGroupPreferenceUtils.java
@@ -60,8 +60,7 @@ public final class PermissionGroupPreferenceUtils {
if (preference == null) {
preference = new PermissionGroupPreference(context, info);
} else {
- preference.updateSummary(info.getNonSystemGranted(),
- info.getNonSystemUserSetOrPreGranted());
+ preference.updateSummary(info);
// Reset the ordering back to default, so that when we add it back it falls into the
// right place, and the preferences are ordered as we add them.
preference.setOrder(Preference.DEFAULT_ORDER);
@@ -99,8 +98,7 @@ public final class PermissionGroupPreferenceUtils {
final PermissionGroupPreference preference =
(PermissionGroupPreference) preferenceGroup.getPreference(i);
final PermGroupPackagesUiInfo info = permissionGroups.get(i);
- preference.updateSummary(info.getNonSystemGranted(),
- info.getNonSystemUserSetOrPreGranted());
+ preference.updateSummary(info);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewOngoingUsageViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewOngoingUsageViewModel.kt
index e65c5c1ff..309b9eba1 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewOngoingUsageViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewOngoingUsageViewModel.kt
@@ -47,15 +47,15 @@ import com.android.permissioncontroller.permission.data.PermGroupUsageLiveData
import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
import com.android.permissioncontroller.permission.data.micMutedLiveData
-import com.android.permissioncontroller.permission.ui.handheld.v31.shouldShowLocationIndicators
-import com.android.permissioncontroller.permission.ui.handheld.v31.shouldShowPermissionsDashboard
import com.android.permissioncontroller.permission.ui.handheld.v31.ReviewOngoingUsageFragment.PHONE_CALL
import com.android.permissioncontroller.permission.ui.handheld.v31.ReviewOngoingUsageFragment.VIDEO_CALL
import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.KotlinUtils.shouldShowLocationIndicators
+import com.android.permissioncontroller.permission.utils.KotlinUtils.shouldShowPermissionsDashboard
import com.android.permissioncontroller.permission.utils.Utils
-import kotlinx.coroutines.Job
import java.time.Instant
import kotlin.math.max
+import kotlinx.coroutines.Job
private const val FIRST_OPENED_KEY = "FIRST_OPENED"
private const val CALL_OP_USAGE_KEY = "CALL_OP_USAGE"
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewPermissionsViewModel.kt
index 97c5d7596..4e1fc1861 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewPermissionsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/ReviewPermissionsViewModel.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.permissioncontroller.permission.ui.model.v33
+package com.android.permissioncontroller.permission.ui.model
import android.app.Application
import android.content.Context
@@ -26,13 +26,13 @@ import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.data.LightAppPermGroupLiveData
import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
import com.android.permissioncontroller.permission.data.get
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
+import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.permission.utils.navigateSafe
import com.android.settingslib.RestrictedLockUtils
@@ -326,4 +326,4 @@ class ReviewPermissionViewModelFactory(
@Suppress("UNCHECKED_CAST")
return ReviewPermissionsViewModel(app, packageInfo = packageInfo) as T
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageControlPreferenceUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageControlPreferenceUtils.kt
index b3442645d..db79165c3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageControlPreferenceUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageControlPreferenceUtils.kt
@@ -61,9 +61,13 @@ object PermissionUsageControlPreferenceUtils {
if (count == 0) {
isEnabled = false
val permissionUsageSummaryNotUsed = if (show7Days) {
- R.string.permission_usage_preference_summary_not_used_7d
+ StringUtils.getIcuPluralsString(context,
+ R.string.permission_usage_preference_summary_not_used_in_past_n_days,
+ 7)
} else {
- R.string.permission_usage_preference_summary_not_used_24h
+ StringUtils.getIcuPluralsString(context,
+ R.string.permission_usage_preference_summary_not_used_in_past_n_hours,
+ 24)
}
setSummary(permissionUsageSummaryNotUsed)
} else if (SENSOR_DATA_PERMISSIONS.contains(groupName)) {
@@ -102,4 +106,4 @@ object PermissionUsageControlPreferenceUtils {
}
PermissionControllerStatsLog.write(PERMISSION_USAGE_FRAGMENT_INTERACTION, sessionId, act)
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt
index 26a6db247..f9852d24f 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt
@@ -22,92 +22,66 @@ import android.app.Application
import android.app.LoaderManager
import android.app.role.RoleManager
import android.content.Context
+import android.content.pm.ApplicationInfo
import android.content.res.Resources
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.UserHandle
-import android.text.format.DateFormat
import androidx.annotation.RequiresApi
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
-import androidx.preference.Preference
-import androidx.preference.PreferenceCategory
-import androidx.preference.PreferenceScreen
import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.R
-import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.model.AppPermissionGroup
+import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp
import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
-import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage.GroupUsage
-import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage.GroupUsage.AttributionLabelledGroupUsage
import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage.TimelineUsage
import com.android.permissioncontroller.permission.model.v31.PermissionUsages
-import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp
import com.android.permissioncontroller.permission.ui.handheld.v31.getDurationUsedStr
-import com.android.permissioncontroller.permission.ui.handheld.v31.is7DayToggleEnabled
import com.android.permissioncontroller.permission.ui.handheld.v31.shouldShowSubattributionInPermissionsDashboard
+import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.KotlinUtils.getPackageLabel
+import com.android.permissioncontroller.permission.utils.PermissionMapping
+import com.android.permissioncontroller.permission.utils.StringUtils
import com.android.permissioncontroller.permission.utils.SubattributionUtils
import com.android.permissioncontroller.permission.utils.Utils
-import java.time.Clock
import java.time.Instant
-import java.time.ZonedDateTime
-import java.time.temporal.ChronoUnit
-import java.util.Objects
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.DAYS
-import java.util.concurrent.atomic.AtomicBoolean
-import java.util.concurrent.atomic.AtomicReference
-import java.util.stream.Collectors
import kotlin.math.max
-/**
- * View model for the permission details fragment.
- */
+/** View model for the permission details fragment. */
@RequiresApi(Build.VERSION_CODES.S)
class PermissionUsageDetailsViewModel(
val application: Application,
val roleManager: RoleManager,
- private val filterGroup: String,
+ private val permissionGroup: String,
val sessionId: Long
) : ViewModel() {
companion object {
private const val ONE_HOUR_MS = 3_600_000
private const val ONE_MINUTE_MS = 60_000
- private const val CLUSTER_MINUTES_APART = 1
+ private const val CLUSTER_SPACING_MINUTES: Long = 1L
private val TIME_7_DAYS_DURATION: Long = DAYS.toMillis(7)
private val TIME_24_HOURS_DURATION: Long = DAYS.toMillis(1)
- private val ALLOW_CLUSTERING_PERMISSION_GROUPS = listOf(
- Manifest.permission_group.LOCATION,
- Manifest.permission_group.CAMERA,
- Manifest.permission_group.MICROPHONE
- )
}
- private val filterTimes = mutableListOf<TimeFilterItem>()
-
- // Truncate to midnight in current timezone.
- private val midnightToday = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS)
- .toEpochSecond() * 1000L
- private val midnightYesterday = ZonedDateTime.now().minusDays(1).truncatedTo(ChronoUnit.DAYS)
- .toEpochSecond() * 1000L
+ private val mTimeFilterItemMs = mutableListOf<TimeFilterItemMs>()
init {
- initializeTimeFilter(application)
+ initializeTimeFilterItems(application)
}
- /**
- * Loads permission usages using [PermissionUsages]. Response is returned to the [callback].
- */
+ /** Loads permission usages using [PermissionUsages]. Response is returned to the [callback]. */
fun loadPermissionUsages(
loaderManager: LoaderManager,
permissionUsages: PermissionUsages,
callback: PermissionUsages.PermissionsUsagesChangeCallback,
filterTimesIndex: Int
) {
- val timeFilterItem: TimeFilterItem = filterTimes[filterTimesIndex]
- val filterTimeBeginMillis = max(System.currentTimeMillis() - timeFilterItem.time, 0)
+ val timeFilterItemMs: TimeFilterItemMs = mTimeFilterItemMs[filterTimesIndex]
+ val filterTimeBeginMillis = max(System.currentTimeMillis() - timeFilterItemMs.timeMs, 0)
permissionUsages.load(
/* filterPackageName= */ null,
/* filterPermissionGroups= */ null,
@@ -122,450 +96,452 @@ class PermissionUsageDetailsViewModel(
}
/**
- * Returns whether app subattribution should be shown.
- */
- private fun shouldShowSubattributionForApp(appPermissionUsage: AppPermissionUsage): Boolean {
- return shouldShowSubattributionInPermissionsDashboard() &&
- SubattributionUtils.isSubattributionSupported(application,
- appPermissionUsage.app.appInfo)
- }
-
- /**
- * Create a list of [AppPermissionUsageEntry]s based on the provided data.
+ * Create a [PermissionUsageDetailsUiData] based on the provided data.
*
* @param appPermissionUsages data about app permission usages
- * @param exemptedPackages packages whose usage should not be included in the out
- * @param permApps mutable list of [PermissionApp] for keeping track of information about apps.
- * This field is updated as a side effect of running this method.
- * @param seenSystemApp mutable field to track whether a system app has recent usage. Updated
- * as a side effect of running this method.
- * @param showSystemApp whether System apps should be shown
+ * @param showSystem whether system apps should be shown
* @param show7Days whether the last 7 days of history should be shown
*/
- fun parseUsages(
+ fun buildPermissionUsageDetailsUiData(
appPermissionUsages: List<AppPermissionUsage>,
- exemptedPackages: Set<String>,
- permApps: MutableList<PermissionApp>,
- seenSystemApp: AtomicBoolean,
- showSystemApp: Boolean,
+ showSystem: Boolean,
show7Days: Boolean
- ): List<AppPermissionUsageEntry> {
- val curTime = System.currentTimeMillis()
- val showPermissionUsagesDuration = if (is7DayToggleEnabled() && show7Days) {
- TIME_7_DAYS_DURATION
- } else {
- TIME_24_HOURS_DURATION
- }
- val startTime = (curTime - showPermissionUsagesDuration)
- .coerceAtLeast(Instant.EPOCH.toEpochMilli())
-
- return appPermissionUsages
- .asSequence()
- .filter { appUsage: AppPermissionUsage ->
- !exemptedPackages.contains(appUsage.packageName)
- }
- .map { appUsage: AppPermissionUsage ->
- filterAndConvert(appUsage, filterGroup)
+ ): PermissionUsageDetailsUiData {
+ val showPermissionUsagesDuration =
+ if (KotlinUtils.is7DayToggleEnabled() && show7Days) {
+ TIME_7_DAYS_DURATION
+ } else {
+ TIME_24_HOURS_DURATION
}
- .flatten()
- .map { usageData: UsageData ->
- // Fetch the access time list of the app accesses mFilterGroup permission group
- // The DiscreteAccessTime is a Triple of (access time, access duration,
- // proxy) of that app
- val discreteAccessTimeList:
- MutableList<Triple<Long, Long, AppOpsManager.OpEventProxyInfo>> =
- mutableListOf()
- val timelineUsages = usageData.timelineUsages
- val numGroups = timelineUsages.size
- for (groupIndex in 0 until numGroups) {
- val timelineUsage = timelineUsages[groupIndex]
- if (!timelineUsage.hasDiscreteData()) {
- continue
- }
- val isSystemApp = !Utils.isGroupOrBgGroupUserSensitive(timelineUsage.group)
- seenSystemApp.set(seenSystemApp.get() || isSystemApp)
- if (isSystemApp && !showSystemApp) {
- continue
- }
- for (discreteAccessTime in timelineUsage.allDiscreteAccessTime) {
- if (discreteAccessTime.first == 0L ||
- discreteAccessTime.first < startTime) {
- continue
- }
- discreteAccessTimeList.add(discreteAccessTime)
- }
- }
- discreteAccessTimeList.sortWith { x, y -> y.first.compareTo(x.first) }
- if (discreteAccessTimeList.size > 0) {
- permApps.add(usageData.app)
- }
+ val startTime =
+ (System.currentTimeMillis() - showPermissionUsagesDuration).coerceAtLeast(
+ Instant.EPOCH.toEpochMilli())
+ val appPermissionTimelineUsages: List<AppPermissionTimelineUsage> =
+ extractAppPermissionTimelineUsagesForGroup(appPermissionUsages, permissionGroup)
+ val shouldDisplayShowSystemToggle =
+ shouldDisplayShowSystemToggle(appPermissionTimelineUsages)
+ val permissionApps: List<PermissionApp> =
+ getPermissionAppsWithRecentDiscreteUsage(
+ appPermissionTimelineUsages, showSystem, startTime)
+ val appPermissionUsageEntries =
+ buildDiscreteAccessClusterData(appPermissionTimelineUsages, showSystem, startTime)
+
+ return PermissionUsageDetailsUiData(
+ permissionApps, shouldDisplayShowSystemToggle, appPermissionUsageEntries)
+ }
- // If the current permission group is not LOCATION or there's only one access
- // for the app, return individual entry early.
- if (!ALLOW_CLUSTERING_PERMISSION_GROUPS.contains(filterGroup) ||
- discreteAccessTimeList.size <= 1) {
- return@map discreteAccessTimeList.map { time ->
- AppPermissionUsageEntry(usageData, time.first, mutableListOf(time))
- }
- }
+ private fun getHistoryPreferenceData(
+ discreteAccessClusterData: DiscreteAccessClusterData,
+ ): HistoryPreferenceData {
+ val context = application
+ val accessTimeList =
+ discreteAccessClusterData.discreteAccessDataList.map { p -> p.accessTimeMs }
+ val durationSummaryLabel =
+ getDurationSummary(discreteAccessClusterData, accessTimeList, context)
+ val proxyLabel = getProxyPackageLabel(discreteAccessClusterData)
+ val subattributionLabel = getSubattributionLabel(discreteAccessClusterData)
+ val showingSubattribution =
+ subattributionLabel != null && subattributionLabel!!.isNotEmpty()
+ val summary =
+ buildUsageSummary(durationSummaryLabel, proxyLabel, subattributionLabel, context)
+
+ return HistoryPreferenceData(
+ UserHandle.getUserHandleForUid(
+ discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.uid),
+ discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.packageName,
+ discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.icon,
+ discreteAccessClusterData.appPermissionTimelineUsage.permissionApp.label,
+ permissionGroup,
+ discreteAccessClusterData.discreteAccessDataList.last().accessTimeMs,
+ discreteAccessClusterData.discreteAccessDataList.first().accessTimeMs,
+ summary,
+ showingSubattribution,
+ discreteAccessClusterData.appPermissionTimelineUsage.attributionTags,
+ sessionId)
+ }
- // Group access time list
- val usageEntries = mutableListOf<AppPermissionUsageEntry>()
- var ongoingEntry: AppPermissionUsageEntry? = null
- for (time in discreteAccessTimeList) {
- if (ongoingEntry == null) {
- ongoingEntry = AppPermissionUsageEntry(usageData, time.first,
- mutableListOf(time))
- } else {
- val ongoingAccessTimeList:
- MutableList<Triple<Long, Long, AppOpsManager.OpEventProxyInfo>> =
- ongoingEntry.clusteredAccessTimeList
- if (time.first / ONE_HOUR_MS !=
- ongoingAccessTimeList[0].first / ONE_HOUR_MS ||
- ongoingAccessTimeList[ongoingAccessTimeList.size - 1].first /
- ONE_MINUTE_MS - time.first / ONE_MINUTE_MS > CLUSTER_MINUTES_APART
- ) {
- // If the current access time is not in the same hour nor within
- // CLUSTER_MINUTES_APART, add the ongoing entry to the usage list
- // and start a new ongoing entry.
- usageEntries.add(ongoingEntry)
- ongoingEntry = AppPermissionUsageEntry(usageData, time.first,
- mutableListOf(time))
- } else {
- ongoingAccessTimeList.add(time)
- }
- }
- }
- ongoingEntry?.let { usageEntries.add(it) }
- usageEntries
+ /**
+ * Returns whether the provided [AppPermissionUsage] instances contains the provided platform
+ * permission group.
+ */
+ fun containsPlatformAppPermissionGroup(
+ appPermissionUsages: List<AppPermissionUsage>,
+ groupName: String,
+ ) = appPermissionUsages.extractAllPlatformAppPermissionGroups().any { it.name == groupName }
+
+ /** Extracts a list of [AppPermissionTimelineUsage] for a particular permission group. */
+ private fun extractAppPermissionTimelineUsagesForGroup(
+ appPermissionUsages: List<AppPermissionUsage>,
+ group: String
+ ): List<AppPermissionTimelineUsage> =
+ appPermissionUsages
+ .filter { !Utils.getExemptedPackages(roleManager).contains(it.packageName) }
+ .map { appPermissionUsage ->
+ getAppPermissionTimelineUsages(
+ appPermissionUsage.app,
+ appPermissionUsage.groupUsages.firstOrNull { it.group.name == group })
}
.flatten()
- .sortedWith { x, y ->
- // Sort all usage entries by startTime desc, and then by app name.
- val timeCompare = java.lang.Long.compare(y.endTime, x.endTime)
- if (timeCompare != 0) {
- return@sortedWith timeCompare
- }
- x.usageData.app.label.compareTo(y.usageData.app.label)
- }
- .toList()
- }
+
+ /** Returns whether the show/hide system toggle should be displayed in the UI. */
+ private fun shouldDisplayShowSystemToggle(
+ appPermissionTimelineUsages: List<AppPermissionTimelineUsage>,
+ ): Boolean =
+ appPermissionTimelineUsages
+ .map { it.timelineUsage }
+ .filter { it.hasDiscreteData() }
+ .any { it.group.isSystem() }
/**
- * Render [usages] into the [preferenceScreen] UI.
+ * Returns a list of [PermissionApp] instances which had recent discrete permission usage
+ * (recent here refers to usages occurring after the provided start time).
*/
- fun renderTimelinePreferences(
- usages: List<AppPermissionUsageEntry>,
- category: AtomicReference<PreferenceCategory>,
- preferenceScreen: PreferenceScreen,
- historyPreferenceFactory: HistoryPreferenceFactory
- ) {
- val context = application
- var hasADateLabel = false
- var lastDateLabel = 0L
- usages.forEachIndexed { usageNum, usage ->
- val usageTimestamp = usage.endTime
- val usageDateLabel = ZonedDateTime.ofInstant(Instant.ofEpochMilli(usageTimestamp),
- Clock.systemDefaultZone().zone)
- .truncatedTo(ChronoUnit.DAYS).toEpochSecond() * 1000L
- if (!hasADateLabel || usageDateLabel != lastDateLabel) {
- if (hasADateLabel) {
- category.set(historyPreferenceFactory.createDayCategoryPreference())
- preferenceScreen.addPreference(category.get())
- }
- val formattedDateTitle = DateFormat.getDateFormat(context)
- .format(usageDateLabel)
- if (usageTimestamp > midnightToday) {
- category.get().setTitle(R.string.permission_history_category_today)
- } else if (usageTimestamp > midnightYesterday) {
- category.get().setTitle(R.string.permission_history_category_yesterday)
- } else {
- category.get().setTitle(formattedDateTitle)
- }
- hasADateLabel = true
- }
+ private fun getPermissionAppsWithRecentDiscreteUsage(
+ appPermissionTimelineUsageList: List<AppPermissionTimelineUsage>,
+ showSystem: Boolean,
+ startTime: Long,
+ ): List<PermissionApp> =
+ appPermissionTimelineUsageList
+ .filter { it.timelineUsage.hasDiscreteData() }
+ .filter { showSystem || !it.timelineUsage.group.isSystem() }
+ .filter { it.timelineUsage.allDiscreteAccessTime.any { it.first >= startTime } }
+ .map { it.permissionApp }
- lastDateLabel = usageDateLabel
-
- val accessTime = DateFormat.getTimeFormat(context).format(usage.endTime)
- var durationLong: Long = usage.clusteredAccessTimeList
- .map { p -> p.second }
- .filter { dur -> dur > 0 }
- .sum()
-
- val accessTimeList: List<Long> = usage.clusteredAccessTimeList.map { p -> p.first }
-
- // Determine the preference summary. Start with the duration string
- var summaryLabel: String? = null
- // Since Location accesses are atomic, we manually calculate the access duration
- // by comparing the first and last access within the cluster
- if (filterGroup == Manifest.permission_group.LOCATION) {
- if (accessTimeList.size > 1) {
- durationLong = (accessTimeList[0] - accessTimeList[accessTimeList.size - 1])
-
- // Similar to other history items, only show the duration if it's longer
- // than the clustering granularity.
- if (durationLong
- >= TimeUnit.MINUTES.toMillis(CLUSTER_MINUTES_APART.toLong()) + 1) {
- summaryLabel = getDurationUsedStr(context, durationLong)
+ /**
+ * Builds a list of [DiscreteAccessClusterData] from the provided list of
+ * [AppPermissionTimelineUsage].
+ */
+ private fun buildDiscreteAccessClusterData(
+ appPermissionTimelineUsageList: List<AppPermissionTimelineUsage>,
+ showSystem: Boolean,
+ startTime: Long,
+ ): List<DiscreteAccessClusterData> =
+ appPermissionTimelineUsageList
+ .map { appPermissionTimelineUsages ->
+ val accessDataList =
+ extractRecentDiscreteAccessData(
+ appPermissionTimelineUsages.timelineUsage, showSystem, startTime)
+
+ if (accessDataList.size <= 1) {
+ return@map accessDataList.map {
+ DiscreteAccessClusterData(appPermissionTimelineUsages, listOf(it))
}
}
- } else {
- // Only show the duration if it is at least (cluster + 1) minutes. Displaying
- // times that are the same as the cluster granularity does not convey useful
- // information.
- if (durationLong != null &&
- durationLong >=
- TimeUnit.MINUTES.toMillis((CLUSTER_MINUTES_APART + 1).toLong())) {
- summaryLabel = getDurationUsedStr(context, durationLong)
- }
- }
-
- var proxyPackageLabel: String? = null
- for (clusteredAccessTime in usage.clusteredAccessTimeList) {
- val proxy = clusteredAccessTime.third
- if (proxy != null && proxy.packageName != null) {
- proxyPackageLabel = getPackageLabel(
- PermissionControllerApplication.get(), proxy.packageName!!,
- UserHandle.getUserHandleForUid(proxy.uid))
- break
- }
- }
- // fetch the subattribution label for this usage.
- var subattributionLabel: String? = null
- if (usage.usageData.label != Resources.ID_NULL) {
- val attributionLabels: Map<Int, String>? = usage.usageData.app.attributionLabels
- if (attributionLabels != null) {
- subattributionLabel = attributionLabels[usage.usageData.label]
- }
+ clusterDiscreteAccessData(appPermissionTimelineUsages, accessDataList)
}
+ .flatten()
+ .sortedWith(
+ compareBy(
+ { -it.discreteAccessDataList.first().accessTimeMs },
+ { it.appPermissionTimelineUsage.permissionApp.label }))
+ .toList()
- // create subtext string.
- val subTextStrings: MutableList<String?> = mutableListOf()
- val showingAttribution = subattributionLabel != null && subattributionLabel.isNotEmpty()
- if (showingAttribution) {
- subTextStrings.add(subattributionLabel)
- }
- if (proxyPackageLabel != null) {
- subTextStrings.add(proxyPackageLabel)
- }
- if (summaryLabel != null) {
- subTextStrings.add(summaryLabel)
- }
- var subText: String? = null
- when (subTextStrings.size) {
- 3 -> {
- subText = context.getString(
- R.string.history_preference_subtext_3,
- subTextStrings[0],
- subTextStrings[1],
- subTextStrings[2])
- }
- 2 -> {
- subText = context.getString(R.string.history_preference_subtext_2,
- subTextStrings[0],
- subTextStrings[1])
- }
- 1 -> {
- subText = subTextStrings[0]
- }
+ /**
+ * Clusters a list of [DiscreteAccessData] into a list of [DiscreteAccessClusterData] instances.
+ *
+ * [DiscreteAccessData] which have accesses sufficiently close together in time will be places
+ * in the same cluster.
+ */
+ private fun clusterDiscreteAccessData(
+ appPermissionTimelineUsage: AppPermissionTimelineUsage,
+ discreteAccessDataList: List<DiscreteAccessData>
+ ): List<DiscreteAccessClusterData> {
+ val clusterDataList = mutableListOf<DiscreteAccessClusterData>()
+ val currentDiscreteAccessDataList: MutableList<DiscreteAccessData> = mutableListOf()
+ for (discreteAccessData in discreteAccessDataList) {
+ if (currentDiscreteAccessDataList.isEmpty()) {
+ currentDiscreteAccessDataList.add(discreteAccessData)
+ } else if (!canAccessBeAddedToCluster(
+ discreteAccessData, currentDiscreteAccessDataList)) {
+ clusterDataList.add(
+ DiscreteAccessClusterData(
+ appPermissionTimelineUsage, currentDiscreteAccessDataList.toMutableList()))
+ currentDiscreteAccessDataList.clear()
+ currentDiscreteAccessDataList.add(discreteAccessData)
+ } else {
+ currentDiscreteAccessDataList.add(discreteAccessData)
}
-
- val permissionUsagePreference = historyPreferenceFactory
- .createPermissionHistoryPreference(
- HistoryPreferenceData(
- UserHandle.getUserHandleForUid(usage.usageData.app.getUid()),
- usage.usageData.app.packageName,
- usage.usageData.app.icon,
- usage.usageData.app.label,
- filterGroup, accessTime, subText,
- showingAttribution, accessTimeList,
- usage.usageData.attributionTags,
- usageNum == usages.size - 1,
- sessionId)
- )
- category.get().addPreference(permissionUsagePreference)
}
+ if (currentDiscreteAccessDataList.isNotEmpty()) {
+ clusterDataList.add(
+ DiscreteAccessClusterData(
+ appPermissionTimelineUsage, currentDiscreteAccessDataList))
+ }
+ return clusterDataList
}
/**
- * Filter the usage data from [appPermissionUsage] into a list of [UsageData].
+ * Extract recent [DiscreteAccessData] from a list of [TimelineUsage] instances, and return them
+ * ordered descending by access time (recent here refers to accesses occurring after the
+ * provided start time).
*/
- private fun filterAndConvert(
- appPermissionUsage: AppPermissionUsage,
- filterGroup: String
- ): List<UsageData> {
- if (shouldShowSubattributionForApp(appPermissionUsage)) {
- return appPermissionUsage.groupUsages
- .filter { groupUsage: GroupUsage -> groupUsage.group.name == filterGroup }
- .map(GroupUsage::getAttributionLabelledGroupUsages)
- .flatten()
- .map { labelledGroupUsage: AttributionLabelledGroupUsage ->
- UsageData(filterGroup, appPermissionUsage.app,
- listOf<TimelineUsage>(labelledGroupUsage),
- labelledGroupUsage.label)
- }
+ private fun extractRecentDiscreteAccessData(
+ timelineUsages: TimelineUsage,
+ showSystem: Boolean,
+ startTime: Long
+ ): List<DiscreteAccessData> {
+ return if (timelineUsages.hasDiscreteData() &&
+ (showSystem || !timelineUsages.group.isSystem())) {
+ getRecentDiscreteAccessData(timelineUsages, startTime)
+ .sortedWith(compareBy { -it.accessTimeMs })
+ .toList()
+ } else {
+ listOf()
}
- val groupUsages = appPermissionUsage.groupUsages
- .filter { groupUsage: GroupUsage -> groupUsage.group.name == filterGroup }
- return listOf(
- UsageData(filterGroup, appPermissionUsage.app, groupUsages,
- Resources.ID_NULL)
- )
}
/**
- * Get an AppPermissionGroup that represents the given permission group (and an arbitrary app).
- *
- * @param groupName The name of the permission group.
- *
- * @return an AppPermissionGroup representing the given permission group or null if no such
- * AppPermissionGroup is found.
+ * Extract recent [DiscreteAccessData] from a [TimelineUsage]. (recent here refers to accesses
+ * occurring after the provided start time).
*/
- fun getGroup(
- groupName: String,
- appPermissionUsages: List<AppPermissionUsage>
- ): AppPermissionGroup? {
- val groups = getOSPermissionGroups(appPermissionUsages)
- return groups.firstOrNull { it.name == groupName }
+ private fun getRecentDiscreteAccessData(
+ timelineUsage: TimelineUsage,
+ startTime: Long
+ ): List<DiscreteAccessData> {
+ return timelineUsage.allDiscreteAccessTime
+ .filter { it.first >= startTime }
+ .map {
+ DiscreteAccessData(
+ it.first,
+ it.second,
+ it.third,
+ )
+ }
}
/**
- * Get the permission groups declared by the OS.
+ * Returns whether the provided [DiscreteAccessData] occurred close enough to those in the
+ * clustered list that it can be added to the cluster
+ */
+ private fun canAccessBeAddedToCluster(
+ accessData: DiscreteAccessData,
+ clusteredAccessDataList: List<DiscreteAccessData>
+ ): Boolean =
+ accessData.accessTimeMs / ONE_HOUR_MS ==
+ clusteredAccessDataList.first().accessTimeMs / ONE_HOUR_MS &&
+ clusteredAccessDataList.last().accessTimeMs / ONE_MINUTE_MS -
+ accessData.accessTimeMs / ONE_MINUTE_MS > CLUSTER_SPACING_MINUTES
+
+ /**
+ * Returns whether the provided [AppPermissionGroup] is considered a system group.
*
- * TODO: theianchen change the method name to make that clear,
- * and return a list of string group names, not AppPermissionGroups.
- * @return a list of the permission groups declared by the OS.
+ * For the purpose of Permissions Hub UI, non user-sensitive [AppPermissionGroup]s are
+ * considered "system" and should be hidden from the main page unless requested by the user
+ * through the "show/hide system" toggle.
*/
- private fun getOSPermissionGroups(
- appPermissionUsages: List<AppPermissionUsage>
- ): List<AppPermissionGroup> {
- val groups: MutableList<AppPermissionGroup> = mutableListOf()
- val seenGroups: MutableSet<String> = mutableSetOf()
- for (appUsage in appPermissionUsages) {
- val groupUsages = appUsage.groupUsages
- for (groupUsage in groupUsages) {
- if (PermissionMapping.isPlatformPermissionGroup(groupUsage.group.name)) {
- if (seenGroups.add(groupUsage.group.name)) {
- groups.add(groupUsage.group)
- }
- }
+ private fun AppPermissionGroup.isSystem() = !Utils.isGroupOrBgGroupUserSensitive(this)
+
+ /** Returns whether app subattribution should be shown. */
+ private fun shouldShowSubattributionForApp(appInfo: ApplicationInfo): Boolean {
+ return shouldShowSubattributionInPermissionsDashboard() &&
+ SubattributionUtils.isSubattributionSupported(application, appInfo)
+ }
+
+ /** Returns a summary of the duration the permission was accessed for. */
+ private fun getDurationSummary(
+ usage: DiscreteAccessClusterData,
+ accessTimeList: List<Long>,
+ context: Context
+ ): String? {
+ if (accessTimeList.isEmpty()) {
+ return null
+ }
+
+ var durationMs: Long
+
+ // Since Location accesses are atomic, we manually calculate the access duration
+ // by comparing the first and last access within the cluster.
+ if (permissionGroup == Manifest.permission_group.LOCATION) {
+ durationMs = accessTimeList[0] - accessTimeList[accessTimeList.size - 1]
+ } else {
+ durationMs =
+ usage.discreteAccessDataList.map { it.accessDurationMs }.filter { it > 0 }.sum()
+ }
+ // Only show the duration summary if it is at least (CLUSTER_SPACING_MINUTES + 1) minutes.
+ // Displaying a time that is shorter than the cluster granularity
+ // (CLUSTER_SPACING_MINUTES) will not convey useful information.
+ if (durationMs >= TimeUnit.MINUTES.toMillis(CLUSTER_SPACING_MINUTES + 1)) {
+ return getDurationUsedStr(context, durationMs)
+ }
+
+ return null
+ }
+
+ /** Returns the proxied package label if the permission access was proxied. */
+ private fun getProxyPackageLabel(usage: DiscreteAccessClusterData): String? =
+ usage.discreteAccessDataList
+ .firstOrNull { it.proxy?.packageName != null }
+ ?.let {
+ getPackageLabel(
+ PermissionControllerApplication.get(),
+ it.proxy!!.packageName!!,
+ UserHandle.getUserHandleForUid(it.proxy!!.uid))
}
+
+ /** Returns the attribution label for the permission access, if any. */
+ private fun getSubattributionLabel(usage: DiscreteAccessClusterData): String? =
+ if (usage.appPermissionTimelineUsage.label == Resources.ID_NULL) null
+ else
+ usage.appPermissionTimelineUsage.permissionApp.attributionLabels?.let {
+ it[usage.appPermissionTimelineUsage.label]
+ }
+
+ /** Builds a summary of the permission access. */
+ private fun buildUsageSummary(
+ subattributionLabel: String?,
+ proxyPackageLabel: String?,
+ durationSummary: String?,
+ context: Context
+ ): String? {
+ val subTextStrings: MutableList<String?> = mutableListOf()
+
+ subattributionLabel?.let { subTextStrings.add(subattributionLabel) }
+ proxyPackageLabel?.let { subTextStrings.add(it) }
+ durationSummary?.let { subTextStrings.add(it) }
+ return when (subTextStrings.size) {
+ 3 ->
+ context.getString(
+ R.string.history_preference_subtext_3,
+ subTextStrings[0],
+ subTextStrings[1],
+ subTextStrings[2])
+ 2 ->
+ context.getString(
+ R.string.history_preference_subtext_2, subTextStrings[0], subTextStrings[1])
+ 1 -> subTextStrings[0]
+ else -> null
}
- return groups
}
/**
- * Initialize the time filter to show the smallest entry greater than the time passed in as an
- * argument. If nothing is passed, this simply initializes the possible values.
+ * Builds a list of [AppPermissionTimelineUsage] from the provided
+ * [AppPermissionUsage.GroupUsage].
*/
- private fun initializeTimeFilter(context: Context) {
- filterTimes.add(
- TimeFilterItem(Long.MAX_VALUE,
- context.getString(R.string.permission_usage_any_time))
- )
- filterTimes.add(
- TimeFilterItem(DAYS.toMillis(7),
- context.getString(R.string.permission_usage_last_7_days))
- )
- filterTimes.add(
- TimeFilterItem(DAYS.toMillis(1),
- context.getString(R.string.permission_usage_last_day))
- )
- filterTimes.add(
- TimeFilterItem(TimeUnit.HOURS.toMillis(1),
- context.getString(R.string.permission_usage_last_hour))
- )
- filterTimes.add(
- TimeFilterItem(TimeUnit.MINUTES.toMillis(15),
- context.getString(R.string.permission_usage_last_15_minutes))
- )
- filterTimes.add(
- TimeFilterItem(TimeUnit.MINUTES.toMillis(1),
- context.getString(R.string.permission_usage_last_minute))
- )
+ private fun getAppPermissionTimelineUsages(
+ app: PermissionApp,
+ groupUsage: AppPermissionUsage.GroupUsage?
+ ): List<AppPermissionTimelineUsage> {
+ if (groupUsage == null) {
+ return listOf()
+ }
- // TODO: theianchen add code for filtering by time here.
+ if (shouldShowSubattributionForApp(app.appInfo)) {
+ return groupUsage.attributionLabelledGroupUsages.map {
+ AppPermissionTimelineUsage(permissionGroup, app, it, it.label)
+ }
+ }
+
+ return listOf(
+ AppPermissionTimelineUsage(permissionGroup, app, groupUsage, Resources.ID_NULL))
}
- /**
- * Factory for creating preferences to be added to the screen.
- */
- interface HistoryPreferenceFactory {
- /**
- * Returns a new [PreferenceCategory] representing a day of permission usage.
- */
- fun createDayCategoryPreference(): PreferenceCategory
-
- /**
- * Returns a preference representing an app's permission usage, including its timestamp and
- * usage details.
- */
- fun createPermissionHistoryPreference(
- historyPreferenceData: HistoryPreferenceData
- ): Preference
+ /** Extracts to a set all the permission groups declared by the platform. */
+ private fun List<AppPermissionUsage>.extractAllPlatformAppPermissionGroups():
+ Set<AppPermissionGroup> =
+ this.flatMap { it.groupUsages }
+ .map { it.group }
+ .filter { PermissionMapping.isPlatformPermissionGroup(it.name) }
+ .toSet()
+
+ /** Initialize all relevant [TimeFilterItemMs] values. */
+ private fun initializeTimeFilterItems(context: Context) {
+ mTimeFilterItemMs.add(
+ TimeFilterItemMs(Long.MAX_VALUE, context.getString(R.string.permission_usage_any_time)))
+ mTimeFilterItemMs.add(
+ TimeFilterItemMs(
+ DAYS.toMillis(7),
+ StringUtils.getIcuPluralsString(context, R.string.permission_usage_last_n_days, 7)))
+ mTimeFilterItemMs.add(
+ TimeFilterItemMs(
+ DAYS.toMillis(1),
+ StringUtils.getIcuPluralsString(context, R.string.permission_usage_last_n_days, 1)))
+
+ // TODO: theianchen add code for filtering by time here.
}
- /**
- * Data used to create a preference for an app's permission usage.
- */
+ /** Data used to create a preference for an app's permission usage. */
data class HistoryPreferenceData(
val userHandle: UserHandle,
val pkgName: String,
val appIcon: Drawable?,
val preferenceTitle: String,
val permissionGroup: String,
- val accessTime: String,
+ val accessStartTime: Long,
+ val accessEndTime: Long,
val summaryText: CharSequence?,
val showingAttribution: Boolean,
- val accessTimeList: List<Long>,
val attributionTags: ArrayList<String>,
- val isLastUsage: Boolean,
val sessionId: Long
)
/**
* A class representing a given time, e.g., "in the last hour".
*
- * @param time the time represented by this object in milliseconds.
+ * @param timeMs the time represented by this object in milliseconds.
* @param label the label to describe the timeframe
*/
- data class TimeFilterItem(
- val time: Long,
- val label: String
+ data class TimeFilterItemMs(val timeMs: Long, val label: String)
+
+ /**
+ * Class containing all the information needed by the permission usage details fragments to
+ * render UI.
+ */
+ inner class PermissionUsageDetailsUiData(
+ /** List of [PermissionApp] instances */
+ // Note that these are used only to cache app data for the permission usage details
+ // fragment, and have no bearing on the UI on the main permission usage page.
+ val permissionApps: List<PermissionApp>,
+ /** Whether to show the "show/hide system" toggle. */
+ val shouldDisplayShowSystemToggle: Boolean,
+ /** [DiscreteAccessClusterData] instances ordered for display in UI */
+ private val discreteAccessClusterDataList: List<DiscreteAccessClusterData>,
+ ) {
+ // Note that the HistoryPreferenceData are not initialized within the
+ // PermissionUsageDetailsUiData instance as the need to be constructed only after the
+ // calling fragment loads the necessary PermissionApp instances. We will attempt to remove
+ // this dependency in b/240978905.
+ /** Builds a list of [HistoryPreferenceData] to be displayed in the UI. */
+ fun getHistoryPreferenceDataList(): List<HistoryPreferenceData> {
+ return discreteAccessClusterDataList.map {
+ this@PermissionUsageDetailsViewModel.getHistoryPreferenceData(it)
+ }
+ }
+ }
+
+ /**
+ * Data class representing a cluster of accesses, to be represented as a single entry in the UI.
+ */
+ data class DiscreteAccessClusterData(
+ val appPermissionTimelineUsage: AppPermissionTimelineUsage,
+ val discreteAccessDataList: List<DiscreteAccessData>
)
- /** A class representing an app's usage for a group. */
- data class UsageData(
- val group: String,
+ /** Data class representing a discrete permission access. */
+ data class DiscreteAccessData(
+ val accessTimeMs: Long,
+ val accessDurationMs: Long,
+ val proxy: AppOpsManager.OpEventProxyInfo?
+ )
+
+ /** Data class representing an app's permissions usages for a particular permission group. */
+ data class AppPermissionTimelineUsage(
+ /** Permission group whose usage is being tracked. */
+ val permissionGroup: String,
// we need a PermissionApp because the loader takes the PermissionApp
// object and loads the icon and label information asynchronously
- val app: PermissionApp,
- val timelineUsages: List<TimelineUsage>,
+ /** App whose permissions are being tracked. */
+ val permissionApp: PermissionApp,
+ /** Timeline usage for the given app and permission. */
+ val timelineUsage: TimelineUsage,
val label: Int
) {
val attributionTags: java.util.ArrayList<String>
- get() = timelineUsages.stream()
- .map { obj: TimelineUsage -> obj.attributionTags }
- .filter { obj: List<String>? -> Objects.nonNull(obj) }
- .flatMap { obj: List<String> -> obj.stream() }
- .collect(Collectors.toCollection { ArrayList() })
+ get() = ArrayList(timelineUsage.attributionTags)
}
-
- /**
- * A class representing an app usage entry in Permission Usage.
- */
- data class AppPermissionUsageEntry(
- val usageData: UsageData,
- val endTime: Long,
- val clusteredAccessTimeList: MutableList<Triple<Long, Long, AppOpsManager.OpEventProxyInfo>>
- )
}
-/**
- * Factory for an [PermissionUsageDetailsViewModel]
- */
+/** Factory for an [PermissionUsageDetailsViewModel] */
@RequiresApi(Build.VERSION_CODES.S)
class PermissionUsageDetailsViewModelFactory(
private val application: Application,
@@ -576,7 +552,7 @@ class PermissionUsageDetailsViewModelFactory(
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
- return PermissionUsageDetailsViewModel(application, roleManager, filterGroup,
- sessionId) as T
+ return PermissionUsageDetailsViewModel(application, roleManager, filterGroup, sessionId)
+ as T
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModelNew.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModelNew.kt
new file mode 100644
index 000000000..e7f1af5a8
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModelNew.kt
@@ -0,0 +1,632 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.model.v31
+
+import android.Manifest
+import android.app.AppOpsManager
+import android.app.Application
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.res.Resources
+import android.os.Build
+import android.os.Bundle
+import android.os.UserHandle
+import androidx.annotation.RequiresApi
+import androidx.lifecycle.AbstractSavedStateViewModelFactory
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.savedstate.SavedStateRegistryOwner
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData
+import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
+import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
+import com.android.permissioncontroller.permission.data.v31.AllLightHistoricalPackageOpsLiveData
+import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
+import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps
+import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.AppPermissionDiscreteAccesses
+import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.AppPermissionId
+import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.AttributedAppPermissionDiscreteAccesses
+import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightHistoricalPackageOps.DiscreteAccess
+import com.android.permissioncontroller.permission.ui.handheld.v31.getDurationUsedStr
+import com.android.permissioncontroller.permission.ui.handheld.v31.shouldShowSubattributionInPermissionsDashboard
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getPackageLabel
+import com.android.permissioncontroller.permission.utils.PermissionMapping
+import com.android.permissioncontroller.permission.utils.SubattributionUtils
+import com.android.permissioncontroller.permission.utils.Utils
+import java.time.Instant
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeUnit.DAYS
+
+/** [ViewModel] for the Permission Usage Details page. */
+@RequiresApi(Build.VERSION_CODES.S)
+class PermissionUsageDetailsViewModelNew(
+ val application: Application,
+ private val state: SavedStateHandle,
+ private val permissionGroup: String,
+) : ViewModel() {
+
+ val allLightHistoricalPackageOpsLiveData =
+ AllLightHistoricalPackageOpsLiveData(application, opNames)
+ private val appPermGroupUiInfoLiveDataList =
+ mutableMapOf<AppPermissionId, AppPermGroupUiInfoLiveData>()
+ private val lightPackageInfoLiveDataMap =
+ mutableMapOf<Pair<String, UserHandle>, LightPackageInfoLiveData>()
+ val showSystemLiveData = state.getLiveData(SHOULD_SHOW_SYSTEM_KEY, false)
+ val show7DaysLiveData = state.getLiveData(SHOULD_SHOW_7_DAYS_KEY, false)
+
+ private val roleManager =
+ Utils.getSystemServiceSafe(application.applicationContext, RoleManager::class.java)
+
+ /** Updates whether system app permissions usage should be displayed in the UI. */
+ fun updateShowSystem(showSystem: Boolean) {
+ if (showSystem != state[PermissionUsageViewModelNew.SHOULD_SHOW_SYSTEM_KEY]) {
+ state[PermissionUsageViewModelNew.SHOULD_SHOW_SYSTEM_KEY] = showSystem
+ }
+ }
+
+ /** Updates whether 7 days usage or 1 day usage should be displayed in the UI. */
+ fun updateShow7Days(show7Days: Boolean) {
+ if (show7Days != state[PermissionUsageViewModelNew.SHOULD_SHOW_7_DAYS_KEY]) {
+ state[PermissionUsageViewModelNew.SHOULD_SHOW_7_DAYS_KEY] = show7Days
+ }
+ }
+
+ /** Creates a [PermissionUsageDetailsUiInfo] containing all information to render the UI. */
+ fun buildPermissionUsageDetailsUiInfo(): PermissionUsageDetailsUiInfo {
+ val showSystem: Boolean = state[SHOULD_SHOW_SYSTEM_KEY] ?: false
+ val show7Days: Boolean = state[SHOULD_SHOW_7_DAYS_KEY] ?: false
+ val showPermissionUsagesDuration =
+ if (KotlinUtils.is7DayToggleEnabled() && show7Days) {
+ TIME_7_DAYS_DURATION
+ } else {
+ TIME_24_HOURS_DURATION
+ }
+ val startTime =
+ (System.currentTimeMillis() - showPermissionUsagesDuration).coerceAtLeast(
+ Instant.EPOCH.toEpochMilli())
+
+ return PermissionUsageDetailsUiInfo(
+ buildAppPermissionAccessUiInfoList(
+ allLightHistoricalPackageOpsLiveData, startTime, showSystem),
+ shouldDisplayShowSystemToggle(allLightHistoricalPackageOpsLiveData, startTime),
+ show7Days)
+ }
+
+ /**
+ * Returns whether the "show/hide system" toggle should be displayed in the UI for the provided
+ * [AllLightHistoricalPackageOpsLiveData].
+ */
+ private fun shouldDisplayShowSystemToggle(
+ allLightHistoricalPackageOpsLiveData: AllLightHistoricalPackageOpsLiveData,
+ startTime: Long
+ ): Boolean {
+ return allLightHistoricalPackageOpsLiveData
+ .getLightHistoricalPackageOps()
+ ?.flatMap {
+ it.appPermissionDiscreteAccesses
+ .map { it.withLabel() }
+ .filterOutExemptAppPermissions(true)
+ .filterAccessesLaterThan(startTime)
+ }
+ ?.any { isAppPermissionSystem(it.appPermissionId) }
+ ?: false
+ }
+
+ private fun isAppPermissionSystem(appPermissionId: AppPermissionId): Boolean {
+ val appPermGroupUiInfo = appPermGroupUiInfoLiveDataList[appPermissionId]?.value
+
+ if (appPermGroupUiInfo != null) {
+ return appPermGroupUiInfo.isSystem
+ } else
+ // The AppPermGroupUiInfo may be null if it has either not loaded yet or if the app has not
+ // requested any permissions from the permission group in question.
+ // The Telecom doesn't request microphone or camera permissions. However, telecom app may
+ // use these permissions and they are considered system app permissions, so we return true
+ // even if the AppPermGroupUiInfo is unavailable.
+ if (appPermissionId.packageName == TELECOM_PACKAGE &&
+ (appPermissionId.permGroup == Manifest.permission_group.CAMERA ||
+ appPermissionId.permGroup == Manifest.permission_group.MICROPHONE)) {
+ return true
+ }
+ return false
+ }
+
+ /**
+ * Extracts access data from [AllLightHistoricalPackageOpsLiveData] and composes
+ * [AppPermissionAccessUiInfo]s to be displayed in the UI.
+ */
+ private fun buildAppPermissionAccessUiInfoList(
+ allLightHistoricalPackageOpsLiveData: AllLightHistoricalPackageOpsLiveData,
+ startTime: Long,
+ showSystem: Boolean
+ ): List<AppPermissionAccessUiInfo> {
+ return allLightHistoricalPackageOpsLiveData
+ .getLightHistoricalPackageOps()
+ ?.flatMap { it.clusterAccesses(startTime, showSystem) }
+ ?.sortedBy { -1 * it.discreteAccesses.first().accessTimeMs }
+ ?.map { it.buildAppPermissionAccessUiInfo() }
+ ?: listOf()
+ }
+
+ private fun LightHistoricalPackageOps.clusterAccesses(
+ startTime: Long,
+ showSystem: Boolean
+ ): List<AppPermissionDiscreteAccessCluster> {
+ return if (!shouldShowSubAttributionForApp(getLightPackageInfo(packageName, userHandle)))
+ this.clusterAccessesWithoutAttribution(startTime, showSystem)
+ else {
+ this.clusterAccessesWithAttribution(startTime, showSystem)
+ }
+ }
+
+ /**
+ * Clusters accesses that are close enough together in time such that they can be displayed as a
+ * single access to the user.
+ *
+ * Accesses are clustered taking into account any app subattribution, so each cluster will
+ * pertain a particular attribution label.
+ */
+ private fun LightHistoricalPackageOps.clusterAccessesWithAttribution(
+ startTime: Long,
+ showSystem: Boolean
+ ): List<AppPermissionDiscreteAccessCluster> =
+ this.attributedAppPermissionDiscreteAccesses
+ .flatMap { it.groupAccessesByLabel(getLightPackageInfo(packageName, userHandle)) }
+ .filterOutExemptAppPermissions(showSystem)
+ .filterAccessesLaterThan(startTime)
+ .flatMap { createAccessClusters(it) }
+
+ /**
+ * Clusters accesses that are close enough together in time such that they can be displayed as a
+ * single access to the user.
+ *
+ * Accesses are clustered disregarding any app subattribution.
+ */
+ private fun LightHistoricalPackageOps.clusterAccessesWithoutAttribution(
+ startTime: Long,
+ showSystem: Boolean
+ ): List<AppPermissionDiscreteAccessCluster> =
+ this.appPermissionDiscreteAccesses
+ .map { it.withLabel() }
+ .filterOutExemptAppPermissions(showSystem)
+ .filterAccessesLaterThan(startTime)
+ .flatMap { createAccessClusters(it) }
+
+ /** Filters out accesses earlier than the provided start time. */
+ private fun List<AppPermissionDiscreteAccessesWithLabel>.filterAccessesLaterThan(
+ startTime: Long,
+ ): List<AppPermissionDiscreteAccessesWithLabel> =
+ this.mapNotNull {
+ val updatedDiscreteAccesses =
+ it.discreteAccesses.filter { access -> access.accessTimeMs > startTime }
+ if (updatedDiscreteAccesses.isEmpty()) null
+ else
+ AppPermissionDiscreteAccessesWithLabel(
+ it.appPermissionId,
+ it.attributionLabel,
+ it.attributionTags,
+ updatedDiscreteAccesses)
+ }
+
+ /** Filters out data for apps and permissions that don't need to be displayed in the UI. */
+ private fun List<AppPermissionDiscreteAccessesWithLabel>.filterOutExemptAppPermissions(
+ showSystem: Boolean
+ ): List<AppPermissionDiscreteAccessesWithLabel> {
+ return this.filter {
+ !Utils.getExemptedPackages(roleManager).contains(it.appPermissionId.packageName)
+ }
+ .filter { it.appPermissionId.permGroup == permissionGroup }
+ .filter { showSystem || !isAppPermissionSystem(it.appPermissionId) }
+ }
+
+ /**
+ * Converts the provided [AppPermissionDiscreteAccesses] to a
+ * [AppPermissionDiscreteAccessesWithLabel] by adding a label.
+ */
+ private fun AppPermissionDiscreteAccesses.withLabel(): AppPermissionDiscreteAccessesWithLabel =
+ AppPermissionDiscreteAccessesWithLabel(
+ this.appPermissionId,
+ Resources.ID_NULL,
+ attributionTags = emptyList(),
+ this.discreteAccesses)
+
+ /** Groups tag-attributed accesses for the provided app and permission by attribution label. */
+ private fun AttributedAppPermissionDiscreteAccesses.groupAccessesByLabel(
+ lightPackageInfo: LightPackageInfo?
+ ): List<AppPermissionDiscreteAccessesWithLabel> {
+ if (lightPackageInfo == null) return emptyList()
+
+ val appPermissionId = this.appPermissionId
+ val labelsToDiscreteAccesses = mutableMapOf<Int, MutableList<DiscreteAccess>>()
+ val labelsToTags = mutableMapOf<Int, MutableList<String>>()
+
+ val appPermissionDiscreteAccessWithLabels =
+ mutableListOf<AppPermissionDiscreteAccessesWithLabel>()
+
+ for ((tag, discreteAccesses) in this.attributedDiscreteAccesses) {
+ val label: Int? = lightPackageInfo.attributionTagsToLabels[tag]
+
+ if (label != null && !labelsToDiscreteAccesses.containsKey(label)) {
+ labelsToDiscreteAccesses[label] = mutableListOf()
+ }
+ labelsToDiscreteAccesses[label]?.addAll(discreteAccesses)
+
+ if (label != null && !labelsToTags.containsKey(label)) {
+ labelsToTags[label] = mutableListOf()
+ }
+ labelsToTags[label]?.add(tag)
+ }
+
+ for ((label, discreteAccesses) in labelsToDiscreteAccesses.entries) {
+ val tags = labelsToTags[label]?.toList() ?: listOf()
+
+ appPermissionDiscreteAccessWithLabels.add(
+ AppPermissionDiscreteAccessesWithLabel(
+ appPermissionId,
+ label,
+ tags,
+ discreteAccesses.sortedBy { -1 * it.accessTimeMs }))
+ }
+
+ return appPermissionDiscreteAccessWithLabels
+ }
+
+ /**
+ * Clusters [DiscreteAccess]es represented by a [AppPermissionDiscreteAccessesWithLabel] into
+ * smaller groups to form a list of [AppPermissionDiscreteAccessCluster] instances.
+ *
+ * [DiscreteAccess]es which have accesses sufficiently close together in time will be places in
+ * the same cluster.
+ */
+ private fun createAccessClusters(
+ appPermAccesses: AppPermissionDiscreteAccessesWithLabel,
+ ): List<AppPermissionDiscreteAccessCluster> {
+ val clusters = mutableListOf<AppPermissionDiscreteAccessCluster>()
+ val currentDiscreteAccesses = mutableListOf<DiscreteAccess>()
+ for (discreteAccess in appPermAccesses.discreteAccesses) {
+ if (currentDiscreteAccesses.isEmpty()) {
+ currentDiscreteAccesses.add(discreteAccess)
+ } else if (!canAccessBeAddedToCluster(discreteAccess, currentDiscreteAccesses)) {
+ clusters.add(
+ AppPermissionDiscreteAccessCluster(
+ appPermAccesses.appPermissionId,
+ appPermAccesses.attributionLabel,
+ appPermAccesses.attributionTags,
+ currentDiscreteAccesses.toMutableList()))
+ currentDiscreteAccesses.clear()
+ currentDiscreteAccesses.add(discreteAccess)
+ } else {
+ currentDiscreteAccesses.add(discreteAccess)
+ }
+ }
+
+ if (currentDiscreteAccesses.isNotEmpty()) {
+ clusters.add(
+ AppPermissionDiscreteAccessCluster(
+ appPermAccesses.appPermissionId,
+ appPermAccesses.attributionLabel,
+ appPermAccesses.attributionTags,
+ currentDiscreteAccesses.toMutableList()))
+ }
+ return clusters
+ }
+
+ /**
+ * Returns whether the provided [DiscreteAccess] occurred close enough to those in the clustered
+ * list that it can be added to the cluster.
+ */
+ private fun canAccessBeAddedToCluster(
+ discreteAccess: DiscreteAccess,
+ clusteredAccesses: List<DiscreteAccess>
+ ): Boolean =
+ discreteAccess.accessTimeMs / ONE_HOUR_MS ==
+ clusteredAccesses.first().accessTimeMs / ONE_HOUR_MS &&
+ clusteredAccesses.last().accessTimeMs / ONE_MINUTE_MS -
+ discreteAccess.accessTimeMs / ONE_MINUTE_MS > CLUSTER_SPACING_MINUTES
+
+ /**
+ * Composes all UI information from a [AppPermissionDiscreteAccessCluster] into a
+ * [AppPermissionAccessUiInfo].
+ */
+ private fun AppPermissionDiscreteAccessCluster.buildAppPermissionAccessUiInfo():
+ AppPermissionAccessUiInfo {
+ val context = application
+ val accessTimeList = this.discreteAccesses.map { it.accessTimeMs }
+ val durationSummaryLabel = getDurationSummary(context, this, accessTimeList)
+ val proxyLabel = getProxyPackageLabel(this)
+ val subAttributionLabel = getSubAttributionLabel(this)
+ val showingSubAttribution = subAttributionLabel != null && subAttributionLabel.isNotEmpty()
+ val summary =
+ buildUsageSummary(context, durationSummaryLabel, proxyLabel, subAttributionLabel)
+
+ return AppPermissionAccessUiInfo(
+ this.appPermissionId.userHandle,
+ this.appPermissionId.packageName,
+ permissionGroup,
+ this.discreteAccesses.last().accessTimeMs,
+ this.discreteAccesses.first().accessTimeMs,
+ summary,
+ showingSubAttribution,
+ ArrayList(this.attributionTags))
+ }
+
+ /** Builds a summary of the permission access. */
+ private fun buildUsageSummary(
+ context: Context,
+ subAttributionLabel: String?,
+ proxyPackageLabel: String?,
+ durationSummary: String?
+ ): String? {
+ val subTextStrings: MutableList<String> = mutableListOf()
+
+ subAttributionLabel?.let { subTextStrings.add(subAttributionLabel) }
+ proxyPackageLabel?.let { subTextStrings.add(it) }
+ durationSummary?.let { subTextStrings.add(it) }
+ return when (subTextStrings.size) {
+ 3 ->
+ context.getString(
+ R.string.history_preference_subtext_3,
+ subTextStrings[0],
+ subTextStrings[1],
+ subTextStrings[2])
+ 2 ->
+ context.getString(
+ R.string.history_preference_subtext_2, subTextStrings[0], subTextStrings[1])
+ 1 -> subTextStrings[0]
+ else -> null
+ }
+ }
+
+ /** Returns whether app subattribution should be shown. */
+ private fun shouldShowSubAttributionForApp(lightPackageInfo: LightPackageInfo?): Boolean {
+ return lightPackageInfo != null &&
+ shouldShowSubattributionInPermissionsDashboard() &&
+ SubattributionUtils.isSubattributionSupported(lightPackageInfo)
+ }
+
+ /** Returns a summary of the duration the permission was accessed for. */
+ private fun getDurationSummary(
+ context: Context,
+ accessCluster: AppPermissionDiscreteAccessCluster,
+ accessTimeList: List<Long>,
+ ): String? {
+ if (accessTimeList.isEmpty()) {
+ return null
+ }
+
+ val durationMs: Long =
+ // Since Location accesses are atomic, we manually calculate the access duration
+ // by comparing the first and last access within the cluster.
+ if (permissionGroup == Manifest.permission_group.LOCATION) {
+ accessTimeList[0] - accessTimeList[accessTimeList.size - 1]
+ } else {
+ accessCluster.discreteAccesses
+ .filter { it.accessDurationMs > 0 }
+ .sumOf { it.accessDurationMs }
+ }
+
+ // Only show the duration summary if it is at least (CLUSTER_SPACING_MINUTES + 1) minutes.
+ // Displaying a time that is shorter than the cluster granularity
+ // (CLUSTER_SPACING_MINUTES) will not convey useful information.
+ if (durationMs >= TimeUnit.MINUTES.toMillis(CLUSTER_SPACING_MINUTES + 1)) {
+ return getDurationUsedStr(context, durationMs)
+ }
+
+ return null
+ }
+
+ /** Returns the proxied package label if the permission access was proxied. */
+ private fun getProxyPackageLabel(accessCluster: AppPermissionDiscreteAccessCluster): String? =
+ accessCluster.discreteAccesses
+ .firstOrNull { it.proxy?.packageName != null }
+ ?.let {
+ getPackageLabel(
+ PermissionControllerApplication.get(),
+ it.proxy!!.packageName!!,
+ UserHandle.getUserHandleForUid(it.proxy.uid))
+ }
+
+ /** Returns the attribution label for the permission access, if any. */
+ private fun getSubAttributionLabel(accessCluster: AppPermissionDiscreteAccessCluster): String? =
+ if (accessCluster.attributionLabel == Resources.ID_NULL) null
+ else {
+ val lightPackageInfo = getLightPackageInfo(accessCluster.appPermissionId)
+ getSubAttributionLabels(lightPackageInfo)?.get(accessCluster.attributionLabel)
+ }
+
+ private fun getSubAttributionLabels(lightPackageInfo: LightPackageInfo?): Map<Int, String>? =
+ if (lightPackageInfo == null) null
+ else SubattributionUtils.getAttributionLabels(application, lightPackageInfo)
+
+ private fun getLightPackageInfo(appPermissionId: AppPermissionId) =
+ lightPackageInfoLiveDataMap[Pair(appPermissionId.packageName, appPermissionId.userHandle)]
+ ?.value
+
+ private fun getLightPackageInfo(packageName: String, userHandle: UserHandle) =
+ lightPackageInfoLiveDataMap[Pair(packageName, userHandle)]?.value
+
+ private fun AllLightHistoricalPackageOpsLiveData.getLightHistoricalPackageOps() =
+ this.value?.values
+
+ /** Data used to create a preference for an app's permission usage. */
+ data class AppPermissionAccessUiInfo(
+ val userHandle: UserHandle,
+ val pkgName: String,
+ val permissionGroup: String,
+ val accessStartTime: Long,
+ val accessEndTime: Long,
+ val summaryText: CharSequence?,
+ val showingAttribution: Boolean,
+ val attributionTags: ArrayList<String>,
+ )
+
+ /**
+ * Class containing all the information needed by the permission usage details fragments to
+ * render UI.
+ */
+ data class PermissionUsageDetailsUiInfo(
+ /** List of [AppPermissionAccessUiInfo]s to be displayed in the UI. */
+ val appPermissionAccessUiInfoList: List<AppPermissionAccessUiInfo>,
+ /** Whether to show the "show/hide system" toggle. */
+ val shouldDisplayShowSystemToggle: Boolean,
+ /** Whether to show data over the last 7 days. */
+ val show7Days: Boolean
+ )
+
+ /**
+ * Data class representing a cluster of permission accesses close enough together to be
+ * displayed as a single access in the UI.
+ */
+ private data class AppPermissionDiscreteAccessCluster(
+ val appPermissionId: AppPermissionId,
+ val attributionLabel: Int,
+ val attributionTags: List<String>,
+ val discreteAccesses: List<DiscreteAccess>,
+ )
+
+ /**
+ * Data class representing all permission accesses for a particular package, user, permission
+ * and attribution label.
+ */
+ private data class AppPermissionDiscreteAccessesWithLabel(
+ val appPermissionId: AppPermissionId,
+ val attributionLabel: Int,
+ val attributionTags: List<String>,
+ val discreteAccesses: List<DiscreteAccess>
+ )
+
+ /** [LiveData] object for [PermissionUsageDetailsUiInfo]. */
+ val permissionUsagesDetailsInfoUiLiveData =
+ object : SmartUpdateMediatorLiveData<@JvmSuppressWildcards PermissionUsageDetailsUiInfo>() {
+
+ private var appPermGroupListPopulated: Boolean = false
+ private val getAppPermGroupUiInfoLiveData = { appPermissionId: AppPermissionId ->
+ AppPermGroupUiInfoLiveData[
+ Triple(
+ appPermissionId.packageName,
+ appPermissionId.permGroup,
+ appPermissionId.userHandle,
+ )]
+ }
+ private val getLightPackageInfoLiveData =
+ { packageWithUserHandle: Pair<String, UserHandle> ->
+ LightPackageInfoLiveData[packageWithUserHandle]
+ }
+
+ init {
+ addSource(allLightHistoricalPackageOpsLiveData) { update() }
+ addSource(showSystemLiveData) { update() }
+ addSource(show7DaysLiveData) { update() }
+ }
+
+ override fun onUpdate() {
+ if (!allLightHistoricalPackageOpsLiveData.isInitialized) {
+ return
+ }
+
+ if (appPermGroupUiInfoLiveDataList.isEmpty()) {
+ val appPermissionIds = mutableSetOf<AppPermissionId>()
+ val allPackages: Set<Pair<String, UserHandle>> =
+ allLightHistoricalPackageOpsLiveData.value?.keys ?: setOf()
+ for (packageWithUserHandle: Pair<String, UserHandle> in allPackages) {
+ val appPermGroupIds =
+ allLightHistoricalPackageOpsLiveData.value
+ ?.get(packageWithUserHandle)
+ ?.appPermissionDiscreteAccesses
+ ?.map { it.appPermissionId }
+ ?.toSet()
+ ?: setOf()
+
+ appPermissionIds.addAll(appPermGroupIds)
+ }
+
+ setSourcesToDifference(
+ appPermissionIds,
+ appPermGroupUiInfoLiveDataList,
+ getAppPermGroupUiInfoLiveData) {
+ update()
+ }
+ setSourcesToDifference(
+ allPackages, lightPackageInfoLiveDataMap, getLightPackageInfoLiveData) {
+ update()
+ }
+ appPermGroupListPopulated = true
+
+ return
+ }
+
+ if (appPermGroupUiInfoLiveDataList.any { !it.value.isInitialized }) {
+ return
+ }
+
+ if (lightPackageInfoLiveDataMap.any { !it.value.isInitialized }) {
+ return
+ }
+
+ if (isInitialized && appPermGroupUiInfoLiveDataList.any { it.value.isStale }) {
+ return
+ }
+
+ if (isInitialized && lightPackageInfoLiveDataMap.any { it.value.isStale }) {
+ return
+ }
+
+ value = buildPermissionUsageDetailsUiInfo()
+ }
+ }
+
+ /** Companion object for [PermissionUsageViewModelNew]. */
+ companion object {
+ private const val ONE_HOUR_MS = 3_600_000
+ private const val ONE_MINUTE_MS = 60_000
+ private const val CLUSTER_SPACING_MINUTES: Long = 1L
+ private const val TELECOM_PACKAGE = "com.android.server.telecom"
+ private val TIME_7_DAYS_DURATION: Long = DAYS.toMillis(7)
+ private val TIME_24_HOURS_DURATION: Long = DAYS.toMillis(1)
+ internal const val SHOULD_SHOW_SYSTEM_KEY = "showSystem"
+ internal const val SHOULD_SHOW_7_DAYS_KEY = "show7Days"
+
+ /** Returns all op names for all permissions in a list of permission groups. */
+ val opNames =
+ listOf(
+ Manifest.permission_group.CAMERA,
+ Manifest.permission_group.LOCATION,
+ Manifest.permission_group.MICROPHONE)
+ .flatMap { group -> PermissionMapping.getPlatformPermissionNamesOfGroup(group) }
+ .mapNotNull { permName -> AppOpsManager.permissionToOp(permName) }
+ .toSet()
+ }
+
+ /** Factory for [PermissionUsageViewModelNew]. */
+ @RequiresApi(Build.VERSION_CODES.S)
+ class PermissionUsageDetailsViewModelNewFactory(
+ val app: Application,
+ owner: SavedStateRegistryOwner,
+ private val permissionGroup: String,
+ ) : AbstractSavedStateViewModelFactory(owner, Bundle()) {
+ override fun <T : ViewModel?> create(
+ key: String,
+ modelClass: Class<T>,
+ handle: SavedStateHandle,
+ ): T {
+ @Suppress("UNCHECKED_CAST")
+ return PermissionUsageDetailsViewModelNew(app, handle, permissionGroup) as T
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModel.kt
index 7aa6e1728..6287be2be 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModel.kt
@@ -21,9 +21,6 @@ import android.app.LoaderManager
import android.app.role.RoleManager
import android.content.Context
import android.os.Build
-import android.util.ArrayMap
-import android.util.ArraySet
-import android.util.Log
import androidx.annotation.RequiresApi
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
@@ -31,7 +28,7 @@ import com.android.permissioncontroller.permission.model.AppPermissionGroup
import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp
import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage
import com.android.permissioncontroller.permission.model.v31.PermissionUsages
-import com.android.permissioncontroller.permission.ui.handheld.v31.is7DayToggleEnabled
+import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
import com.android.permissioncontroller.permission.utils.PermissionMapping
import com.android.permissioncontroller.permission.utils.Utils
@@ -39,20 +36,22 @@ import java.time.Instant
import java.util.concurrent.TimeUnit
import kotlin.math.max
+/** [ViewModel] for Permission Usage fragments. */
@RequiresApi(Build.VERSION_CODES.S)
class PermissionUsageViewModel(val roleManager: RoleManager) : ViewModel() {
- companion object {
- private const val LOG_TAG = "PermissionUsageViewModel"
+ private val exemptedPackages: Set<String> = Utils.getExemptedPackages(roleManager)
+ /** Companion object for [PermissionUsageViewModel]. */
+ companion object {
/** TODO(ewol): Use the config setting to determine amount of time to show. */
private val TIME_FILTER_MILLIS = TimeUnit.DAYS.toMillis(7)
private val TIME_7_DAYS_DURATION = TimeUnit.DAYS.toMillis(7)
private val TIME_24_HOURS_DURATION = TimeUnit.DAYS.toMillis(1)
/** Permission groups that should be hidden from the permissions usage UI. */
private val EXEMPTED_PERMISSION_GROUPS = setOf(Manifest.permission_group.NOTIFICATIONS)
-
@JvmStatic
+ /** Map to represent ordering for permission groups in the permissions usage UI. */
val PERMISSION_GROUP_ORDER: Map<String, Int> =
mapOf(
Manifest.permission_group.LOCATION to 0,
@@ -61,6 +60,7 @@ class PermissionUsageViewModel(val roleManager: RoleManager) : ViewModel() {
private const val DEFAULT_ORDER = 3
}
+ /** Loads data from [PermissionUsages] using the [LoaderManager] pattern. */
fun loadPermissionUsages(
loaderManager: LoaderManager,
permissionUsages: PermissionUsages,
@@ -81,177 +81,157 @@ class PermissionUsageViewModel(val roleManager: RoleManager) : ViewModel() {
false /*sync*/)
}
- fun extractUsages(
- permissionUsages: List<AppPermissionUsage>,
+ /**
+ * Parses the provided list of [AppPermissionUsage] instances to build data for the UI to
+ * display.
+ */
+ fun buildPermissionUsagesUiData(
+ appPermissionUsages: List<AppPermissionUsage>,
show7Days: Boolean,
- showSystem: Boolean
- ): Triple<MutableMap<String, Int>, ArrayList<PermissionApp>, Boolean> {
+ showSystem: Boolean,
+ context: Context,
+ ): PermissionUsagesUiData {
val curTime = System.currentTimeMillis()
val showPermissionUsagesDuration =
- if (is7DayToggleEnabled() && show7Days) {
+ if (KotlinUtils.is7DayToggleEnabled() && show7Days) {
TIME_7_DAYS_DURATION
} else {
TIME_24_HOURS_DURATION
}
val startTime = max(curTime - showPermissionUsagesDuration, Instant.EPOCH.toEpochMilli())
- // Permission group to count mapping.
- val usages: MutableMap<String, Int> = HashMap()
- val permissionGroups: List<AppPermissionGroup> =
- getOSPermissionGroupsToDisplay(permissionUsages)
- for (i in permissionGroups.indices) {
- usages[permissionGroups[i].name] = 0
- }
- val permApps = ArrayList<PermissionApp>()
-
- val exemptedPackages = Utils.getExemptedPackages(roleManager)
-
- val seenSystemApp: Boolean =
- extractPermissionUsage(
- exemptedPackages, usages, permApps, startTime, permissionUsages, showSystem)
-
- return Triple(usages, permApps, seenSystemApp)
+ val filteredAppPermissionUsages =
+ appPermissionUsages.filter { !exemptedPackages.contains(it.packageName) }
+ val displayShowSystemToggle: Boolean =
+ filteredAppPermissionUsages.displayShowSystemToggle(startTime)
+ val permissionApps = filteredAppPermissionUsages.getRecentPermissionApps(startTime)
+ val orderedPermissionGroupsWithUsage =
+ filteredAppPermissionUsages.buildOrderedPermissionGroupsWithUsageCount(
+ context,
+ startTime,
+ showSystem
+ )
+
+ return PermissionUsagesUiData(
+ permissionApps,
+ displayShowSystemToggle,
+ orderedPermissionGroupsWithUsage
+ )
}
- fun createGroupUsagesList(
+ /**
+ * Creates an ordered list of [PermissionGroupWithUsageCount] instances to show in the UI,
+ * representing a mapping of permission groups to the number of apps that recently accessed
+ * them.
+ *
+ * The list is ordered as follows:
+ *
+ * 1. Location
+ * 2. Camera
+ * 3. Microphone
+ * 4. Remaining permission groups, ordered alphabetically
+ */
+ private fun List<AppPermissionUsage>.buildOrderedPermissionGroupsWithUsageCount(
context: Context,
- usages: Map<String, Int>
- ): List<Map.Entry<String, Int>> {
- val groupUsageNameToLabel: MutableMap<String, CharSequence> = HashMap()
- val groupUsagesList: MutableList<Map.Entry<String, Int>> = ArrayList(usages.entries)
- val usagesEntryCount = groupUsagesList.size
- for (usageEntryIndex in 0 until usagesEntryCount) {
- val (key) = groupUsagesList[usageEntryIndex]
- groupUsageNameToLabel[key] = getPermGroupLabel(context, key)
+ startTime: Long,
+ showSystem: Boolean
+ ): List<PermissionGroupWithUsageCount> {
+ val permissionGroupsUsageCountMap: MutableMap<String, Int> = HashMap()
+ extractPlatformAppPermissionGroupsToDisplay().forEach {
+ permissionGroupsUsageCountMap[it] = 0
}
- groupUsagesList.sortWith { e1: Map.Entry<String, Int>, e2: Map.Entry<String, Int> ->
- comparePermissionGroupUsage(e1, e2, groupUsageNameToLabel)
+ for (appUsage in this) {
+ appUsage.groupUsages
+ .filter { showSystem || !it.group.isSystem() }
+ .filter { !EXEMPTED_PERMISSION_GROUPS.contains(it.group.name) }
+ .filter { it.lastAccessTime >= startTime }
+ .forEach {
+ permissionGroupsUsageCountMap[it.group.name] =
+ permissionGroupsUsageCountMap.getOrDefault(it.group.name, 0) + 1
+ }
}
-
- return groupUsagesList
- }
-
- private fun comparePermissionGroupUsage(
- first: Map.Entry<String, Int>,
- second: Map.Entry<String, Int>,
- groupUsageNameToLabelMapping: Map<String, CharSequence>
- ): Int {
- val firstPermissionOrder = PERMISSION_GROUP_ORDER.getOrDefault(first.key, DEFAULT_ORDER)
- val secondPermissionOrder = PERMISSION_GROUP_ORDER.getOrDefault(second.key, DEFAULT_ORDER)
- return if (firstPermissionOrder != secondPermissionOrder) {
- firstPermissionOrder - secondPermissionOrder
- } else
- groupUsageNameToLabelMapping[first.key]
- .toString()
- .compareTo(groupUsageNameToLabelMapping[second.key].toString())
+ return permissionGroupsUsageCountMap.entries.map {
+ PermissionGroupWithUsageCount(
+ it.key,
+ it.value
+ )
+ }
+ .sortedWith(
+ compareBy(
+ { PERMISSION_GROUP_ORDER.getOrDefault(it.permGroup, DEFAULT_ORDER) },
+ { getPermGroupLabel(context, it.permGroup).toString() })
+ )
}
- /** Returns the permission groups declared by the OS that should be displayed in the UI. */
- private fun getOSPermissionGroupsToDisplay(
- permissionUsages: List<AppPermissionUsage>
- ): List<AppPermissionGroup> {
- val groups: MutableList<AppPermissionGroup> = java.util.ArrayList()
- val seenGroups: MutableSet<String> = ArraySet()
- val numGroups: Int = permissionUsages.size
- for (i in 0 until numGroups) {
- val appUsage: AppPermissionUsage = permissionUsages.get(i)
- val groupUsages = appUsage.groupUsages
- val groupUsageCount = groupUsages.size
- for (j in 0 until groupUsageCount) {
- val groupUsage = groupUsages[j]
- if (EXEMPTED_PERMISSION_GROUPS.contains(groupUsage.group.name)) {
- continue
- }
- if (PermissionMapping.isPlatformPermissionGroup(groupUsage.group.name)) {
- if (seenGroups.add(groupUsage.group.name)) {
- groups.add(groupUsage.group)
- }
- }
+ /** Extracts [PermissionApp] where there has been recent permission usage. */
+ private fun List<AppPermissionUsage>.getRecentPermissionApps(
+ startTime: Long,
+ ): java.util.ArrayList<PermissionApp> {
+ return ArrayList(
+ filter { appPermissionUsage ->
+ appPermissionUsage.groupUsages
+ .filter { !EXEMPTED_PERMISSION_GROUPS.contains(it.group.name) }
+ .any { it.lastAccessTime >= startTime || it.lastAccessTime == 0L }
}
- }
- return groups
+ .map { it.app })
}
/**
- * Extract the permission usages from mAppPermissionUsages and put the extracted usages into
- * usages and permApps. Returns whether we have seen a system app during the process.
- *
- * TODO: theianchen It's doing two things at the same method which is violating the SOLID
- * principle. We should fix this.
- *
- * @param exemptedPackages packages that are the role holders for exempted roles
- * @param usages an empty List that will be filled with permission usages.
- * @param permApps an empty List that will be filled with permission apps.
- * @return whether we have seen a system app.
+ * Returns whether there are any user-sensitive app permission groups with recent usage, and
+ * therefore if the "show/hide system" toggle needs to be displayed in the UI
*/
- private fun extractPermissionUsage(
- exemptedPackages: Set<String>,
- usages: MutableMap<String, Int>,
- permApps: java.util.ArrayList<PermissionApp>,
+ private fun List<AppPermissionUsage>.displayShowSystemToggle(
startTime: Long,
- permissionUsages: List<AppPermissionUsage>,
- showSystem: Boolean
): Boolean {
+ return flatMap { it.groupUsages }
+ .filter { !EXEMPTED_PERMISSION_GROUPS.contains(it.group.name) }
+ .filter { it.lastAccessTime > startTime && it.lastAccessTime > 0L }
+ .any { it.group.isSystem() }
+ }
- val mGroupAppCounts: ArrayMap<String?, Int> = ArrayMap()
- var seenSystemApp = false
- val numApps: Int = permissionUsages.size
- for (appNum in 0 until numApps) {
- val appUsage: AppPermissionUsage = permissionUsages.get(appNum)
- if (exemptedPackages.contains(appUsage.packageName)) {
- continue
- }
- var used = false
- val appGroups = appUsage.groupUsages
- val numGroups = appGroups.size
- for (groupNum in 0 until numGroups) {
- val groupUsage = appGroups[groupNum]
- val groupName = groupUsage.group.name
- if (EXEMPTED_PERMISSION_GROUPS.contains(groupName)) {
- continue
- }
- val lastAccessTime = groupUsage.lastAccessTime
- if (lastAccessTime == 0L) {
- Log.w(
- LOG_TAG,
- "Unexpected access time of 0 for ${appUsage.app.key} " +
- groupUsage.group.name)
- continue
- }
- if (lastAccessTime < startTime) {
- continue
- }
- val isSystemApp = !Utils.isGroupOrBgGroupUserSensitive(groupUsage.group)
- seenSystemApp = seenSystemApp || isSystemApp
+ /**
+ * Extracts to a set all the permission groups declared by the platform that should be displayed
+ * in the UI.
+ */
+ private fun List<AppPermissionUsage>.extractPlatformAppPermissionGroupsToDisplay():
+ Set<String> =
+ this.flatMap { it.groupUsages }
+ .map { it.group.name }
+ .filter { PermissionMapping.isPlatformPermissionGroup(it) }
+ .filter { !EXEMPTED_PERMISSION_GROUPS.contains(it) }
+ .toSet()
- // If not showing system apps, skip.
- if (!showSystem && isSystemApp) {
- continue
- }
- used = true
- addGroupUser(mGroupAppCounts, groupName)
- usages[groupName] = usages.getOrDefault(groupName, 0) + 1
- }
- if (used) {
- permApps.add(appUsage.app)
- addGroupUser(mGroupAppCounts, null)
- }
- }
- return seenSystemApp
- }
+ /**
+ * Returns whether the [AppPermissionGroup] is considered a system group.
+ *
+ * For the purpose of Permissions Hub UI, non user-sensitive [AppPermissionGroup]s are
+ * considered "system" and should be hidden from the main page unless requested by the user
+ * through the "show/hide system" toggle.
+ */
+ private fun AppPermissionGroup.isSystem() = !Utils.isGroupOrBgGroupUserSensitive(this)
+
+ /** Data class to hold all the information required to configure the UI. */
+ data class PermissionUsagesUiData(
+ /** List of [PermissionApp] instances */
+ // Note that these are used only to cache app data for the permission usage details
+ // fragment, and have no bearing on the UI on the main permission usage page.
+ val permissionApps: ArrayList<PermissionApp>,
+ /** Whether to show the "show/hide system" toggle. */
+ val displayShowSystemToggle: Boolean,
+ // TODO(b/243970988): Consider moving ordering logic to fragment.
+ /** [PermissionGroupWithUsageCount] instances ordered for display in UI */
+ val orderedPermissionGroupsWithUsageCount: List<PermissionGroupWithUsageCount>,
+ )
- private fun addGroupUser(groupAppCounts: ArrayMap<String?, Int>, app: String?) {
- val count: Int? = groupAppCounts[app]
- if (count == null) {
- groupAppCounts[app] = 1
- } else {
- groupAppCounts[app] = count + 1
- }
- }
+ /**
+ * Data class to associate permission groups with the number of apps that recently accessed
+ * them.
+ */
+ data class PermissionGroupWithUsageCount(val permGroup: String, val appCount: Int)
}
-/** Factory for a PermissionUsageViewModel */
+/** Factory for [PermissionUsageViewModel]. */
@RequiresApi(Build.VERSION_CODES.S)
class PermissionUsageViewModelFactory(private val roleManager: RoleManager) :
ViewModelProvider.Factory {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModelNew.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModelNew.kt
new file mode 100644
index 000000000..223ebefb2
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModelNew.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.model.v31
+
+import android.Manifest
+import android.app.Application
+import android.app.role.RoleManager
+import android.os.Build
+import android.os.Bundle
+import android.os.UserHandle
+import androidx.annotation.RequiresApi
+import androidx.lifecycle.AbstractSavedStateViewModelFactory
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.savedstate.SavedStateRegistryOwner
+import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData
+import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
+import com.android.permissioncontroller.permission.data.v31.AllLightPackageOpsLiveData
+import com.android.permissioncontroller.permission.model.livedatatypes.v31.LightPackageOps
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.Utils
+import java.time.Instant
+import java.util.concurrent.TimeUnit
+import kotlin.math.max
+
+/**
+ * [ViewModel] for handheld Permissions Usage UI.
+ *
+ * Note that this class replaces [PermissionUsageViewModel] to rely on [LiveData] instead of
+ * [PermissionUsages] loader.
+ */
+// TODO(b/257317510): Remove "new" suffix and deprecate PermissionUsageViewModel.
+class PermissionUsageViewModelNew(
+ private val state: SavedStateHandle,
+ app: Application,
+) : AndroidViewModel(app) {
+ private val roleManager =
+ Utils.getSystemServiceSafe(app.applicationContext, RoleManager::class.java)
+ private val exemptedPackages: Set<String> = Utils.getExemptedPackages(roleManager)
+
+ private val mAllLightPackageOpsLiveData = AllLightPackageOpsLiveData(app)
+ private val appPermGroupUiInfoLiveDataList =
+ mutableMapOf<AppPermissionId, AppPermGroupUiInfoLiveData>()
+
+ val showSystemLiveData = state.getLiveData(SHOULD_SHOW_SYSTEM_KEY, false)
+ val show7DaysLiveData = state.getLiveData(SHOULD_SHOW_7_DAYS_KEY, false)
+
+ /** Updates whether system app permissions usage should be displayed in the UI. */
+ fun updateShowSystem(showSystem: Boolean) {
+ if (showSystem != state[SHOULD_SHOW_SYSTEM_KEY]) {
+ state[SHOULD_SHOW_SYSTEM_KEY] = showSystem
+ }
+ }
+
+ /** Updates whether 7 days usage or 1 day usage should be displayed in the UI. */
+ fun updateShow7Days(show7Days: Boolean) {
+ if (show7Days != state[SHOULD_SHOW_7_DAYS_KEY]) {
+ state[SHOULD_SHOW_7_DAYS_KEY] = show7Days
+ }
+ }
+
+ /** Builds a [PermissionUsagesUiData] containing all the data necessary to render the UI. */
+ private fun buildPermissionUsagesUiData(): PermissionUsagesUiData {
+ val curTime = System.currentTimeMillis()
+ val showSystem: Boolean = state[SHOULD_SHOW_SYSTEM_KEY] ?: false
+ val show7Days: Boolean = state[SHOULD_SHOW_7_DAYS_KEY] ?: false
+ val showPermissionUsagesDuration =
+ if (KotlinUtils.is7DayToggleEnabled() && show7Days) {
+ TIME_7_DAYS_DURATION
+ } else {
+ TIME_24_HOURS_DURATION
+ }
+ val startTime = max(curTime - showPermissionUsagesDuration, Instant.EPOCH.toEpochMilli())
+ return PermissionUsagesUiData(
+ mAllLightPackageOpsLiveData.displayShowSystemToggle(startTime),
+ showSystem,
+ show7Days,
+ mAllLightPackageOpsLiveData.buildPermissionGroupsWithUsageCounts(startTime, showSystem))
+ }
+
+ /** Builds a map of permission groups to the number of apps that recently accessed them. */
+ private fun AllLightPackageOpsLiveData.buildPermissionGroupsWithUsageCounts(
+ startTime: Long,
+ showSystem: Boolean,
+ ): Map<String, Int> {
+ val permissionUsageCountMap: MutableMap<String, Int> = HashMap()
+ for (permissionGroup: String in getAllEligiblePermissionGroups()) {
+ permissionUsageCountMap[permissionGroup] = 0
+ }
+
+ val eligibleLightPackageOpsList: List<LightPackageOps> =
+ getAllLightPackageOps()?.filterOutExemptedApps() ?: listOf()
+
+ for (lightPackageOps: LightPackageOps in eligibleLightPackageOpsList) {
+ val permGroupsToLastAccess: List<Map.Entry<String, Long>> =
+ lightPackageOps.lastPermissionGroupAccessTimesMs.entries
+ .filterOutExemptedPermissionGroupsFromKeys()
+ .filterOutSystemAppPermissionsIfNecessary(
+ showSystem, lightPackageOps.packageName, lightPackageOps.userHandle)
+ .filterAccessTimeLaterThan(startTime)
+ val recentlyUsedPermissions: List<String> = permGroupsToLastAccess.map { it.key }
+
+ for (permissionGroup: String in recentlyUsedPermissions) {
+ permissionUsageCountMap[permissionGroup] =
+ permissionUsageCountMap.getOrDefault(permissionGroup, 0) + 1
+ }
+ }
+ return permissionUsageCountMap
+ }
+
+ /**
+ * Determines whether there are any system app permissions with recent usage, in which case the
+ * "show/hide system" toggle should be displayed in the UI.
+ */
+ private fun AllLightPackageOpsLiveData.displayShowSystemToggle(startTime: Long): Boolean {
+ val eligibleLightPackageOpsList: List<LightPackageOps> =
+ getAllLightPackageOps()?.filterOutExemptedApps() ?: listOf()
+
+ for (lightPackageOps: LightPackageOps in eligibleLightPackageOpsList) {
+ val recentlyUsedPermissions: Set<String> =
+ lightPackageOps.lastPermissionGroupAccessTimesMs.entries
+ .filterAccessTimeLaterThan(startTime)
+ .map { it.key }
+ .toSet()
+ if (recentlyUsedPermissions
+ .filterOutExemptedPermissionGroups()
+ .containsSystemAppPermission(
+ lightPackageOps.packageName, lightPackageOps.userHandle)) {
+ return true
+ }
+ }
+
+ return false
+ }
+
+ /**
+ * Returns all permission groups tracked in the [AllLightPackageOpsLiveData] eligible for
+ * display in the UI.
+ */
+ private fun AllLightPackageOpsLiveData.getAllEligiblePermissionGroups(): Set<String> {
+ val eligibleLightPackageOpsList =
+ getAllLightPackageOps()?.filterOutExemptedApps() ?: listOf()
+
+ val allPermissionGroups: Set<String> =
+ eligibleLightPackageOpsList.flatMap { it.lastPermissionGroupAccessTimesMs.keys }.toSet()
+
+ return allPermissionGroups.filterOutExemptedPermissionGroups().toSet()
+ }
+
+ private fun isAppPermissionSystem(appPermissionId: AppPermissionId): Boolean {
+ val appPermGroupUiInfo = appPermGroupUiInfoLiveDataList[appPermissionId]?.value
+
+ if (appPermGroupUiInfo != null) {
+ return appPermGroupUiInfo.isSystem
+ } else
+ // The AppPermGroupUiInfo may be null if it has either not loaded yet or if the app has not
+ // requested any permissions from the permission group in question.
+ // The Telecom doesn't request microphone or camera permissions. However, telecom app may
+ // use these permissions and they are considered system app permissions, so we return true
+ // even if the AppPermGroupUiInfo is unavailable.
+ if (appPermissionId.packageName == TELECOM_PACKAGE &&
+ (appPermissionId.permissionGroup == Manifest.permission_group.CAMERA ||
+ appPermissionId.permissionGroup == Manifest.permission_group.MICROPHONE)) {
+ return true
+ }
+ return false
+ }
+
+ private fun AllLightPackageOpsLiveData.getAllLightPackageOps() = value?.values
+
+ /**
+ * Filters out accesses earlier than the provided start time from a map of permission last
+ * accesses.
+ */
+ private fun Collection<Map.Entry<String, Long>>.filterAccessTimeLaterThan(startTime: Long) =
+ filter {
+ it.value > startTime
+ }
+
+ /**
+ * Filters out system app permissions from a map of permission last accesses, if showSystem is
+ * false.
+ */
+ private fun Collection<Map.Entry<String, Long>>.filterOutSystemAppPermissionsIfNecessary(
+ showSystem: Boolean,
+ packageName: String,
+ userHandle: UserHandle
+ ) = filter {
+ showSystem || !isAppPermissionSystem(AppPermissionId(packageName, userHandle, it.key))
+ }
+
+ /**
+ * Filters out permission groups that are exempt from permission usage tracking from a map of
+ * permission last accesses.
+ */
+ private fun Collection<Map.Entry<String, Long>>.filterOutExemptedPermissionGroupsFromKeys() =
+ filter {
+ !EXEMPTED_PERMISSION_GROUPS.contains(it.key)
+ }
+
+ /**
+ * Filters out permission groups that are exempt from permission usage tracking from a map of
+ * permission last accesses.
+ */
+ private fun Collection<String>.filterOutExemptedPermissionGroups() = filter {
+ !EXEMPTED_PERMISSION_GROUPS.contains(it)
+ }
+
+ /** Filters out [LightPackageOps] for apps that are exempt from permission usage tracking. */
+ private fun Collection<LightPackageOps>.filterOutExemptedApps() = filter {
+ !exemptedPackages.contains(it.packageName)
+ }
+
+ /**
+ * Returns from a list of permissions whether any permission along with the provided package
+ * name and user handle are considered a system app permission.
+ */
+ private fun Collection<String>.containsSystemAppPermission(
+ packageName: String,
+ userHandle: UserHandle
+ ) = any { isAppPermissionSystem(AppPermissionId(packageName, userHandle, it)) }
+
+ /** Identifier for an app permission group combination. */
+ data class AppPermissionId(
+ val packageName: String,
+ val userHandle: UserHandle,
+ val permissionGroup: String
+ )
+
+ /** Data class to hold all the information required to configure the UI. */
+ data class PermissionUsagesUiData(
+ /** Whether to show the "show/hide system" toggle. */
+ val displayShowSystemToggle: Boolean,
+ /** Whether to show system app permissions in the UI. */
+ val showSystemAppPermissions: Boolean,
+ /** Whether to show usage data for 7 days or 1 day. */
+ val show7DaysUsage: Boolean,
+ /** Map instances for display in UI */
+ val permissionGroupsWithUsageCount: Map<String, Int>,
+ )
+
+ /** LiveData object for [PermissionUsagesUiData]. */
+ val permissionUsagesUiLiveData =
+ object : SmartUpdateMediatorLiveData<@JvmSuppressWildcards PermissionUsagesUiData>() {
+
+ private var appPermGroupListPopulated: Boolean = false
+ private val getAppPermGroupUiInfoLiveData = { appPermissionId: AppPermissionId ->
+ AppPermGroupUiInfoLiveData[
+ Triple(
+ appPermissionId.packageName,
+ appPermissionId.permissionGroup,
+ appPermissionId.userHandle,
+ )]
+ }
+
+ init {
+ addSource(mAllLightPackageOpsLiveData) { update() }
+ addSource(showSystemLiveData) { update() }
+ addSource(show7DaysLiveData) { update() }
+ }
+
+ override fun onUpdate() {
+ if (!mAllLightPackageOpsLiveData.isInitialized) {
+ return
+ }
+
+ if (appPermGroupUiInfoLiveDataList.isEmpty()) {
+ val appPermissionIds = mutableListOf<AppPermissionId>()
+ val allPackages = mAllLightPackageOpsLiveData.value?.keys ?: setOf()
+ for (packageWithUserHandle: Pair<String, UserHandle> in allPackages) {
+ val lastPermissionGroupAccessTimesMs =
+ mAllLightPackageOpsLiveData.value
+ ?.get(packageWithUserHandle)
+ ?.lastPermissionGroupAccessTimesMs
+ ?: mapOf()
+
+ for (permissionGroupToAccess in lastPermissionGroupAccessTimesMs) {
+ appPermissionIds.add(
+ AppPermissionId(
+ packageWithUserHandle.first,
+ packageWithUserHandle.second,
+ permissionGroupToAccess.key,
+ ))
+ }
+ }
+
+ setSourcesToDifference(
+ appPermissionIds,
+ appPermGroupUiInfoLiveDataList,
+ getAppPermGroupUiInfoLiveData) {
+ update()
+ }
+ appPermGroupListPopulated = true
+
+ return
+ }
+
+ if (appPermGroupUiInfoLiveDataList.any { !it.value.isInitialized }) {
+ return
+ }
+
+ if (isInitialized && appPermGroupUiInfoLiveDataList.any { it.value.isStale }) {
+ return
+ }
+
+ val uiData = buildPermissionUsagesUiData()
+ // We include this check as we don't want UX updates unless the data to be displayed
+ // has changed. SmartUpdateMediatorLiveData sends updates if the data has changed OR
+ // if the data has changed from stale to fresh.
+ if (value != uiData) {
+ value = uiData
+ }
+ }
+ }
+
+ /** Companion class for [PermissionUsageViewModelNew]. */
+ companion object {
+ private val TIME_7_DAYS_DURATION = TimeUnit.DAYS.toMillis(7)
+ private val TIME_24_HOURS_DURATION = TimeUnit.DAYS.toMillis(1)
+ internal const val SHOULD_SHOW_SYSTEM_KEY = "showSystem"
+ internal const val SHOULD_SHOW_7_DAYS_KEY = "show7Days"
+ private const val TELECOM_PACKAGE = "com.android.server.telecom"
+
+ /** Permission groups that should be hidden from the permissions usage UI. */
+ private val EXEMPTED_PERMISSION_GROUPS = setOf(Manifest.permission_group.NOTIFICATIONS)
+ }
+
+ /** Factory for [PermissionUsageViewModelNew]. */
+ @RequiresApi(Build.VERSION_CODES.S)
+ class PermissionUsageViewModelFactory(
+ val app: Application,
+ owner: SavedStateRegistryOwner,
+ defaultArgs: Bundle
+ ) : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
+ override fun <T : ViewModel?> create(
+ key: String,
+ modelClass: Class<T>,
+ handle: SavedStateHandle
+ ): T {
+ @Suppress("UNCHECKED_CAST") return PermissionUsageViewModelNew(handle, app) as T
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v33/SafetyCenterQsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v33/SafetyCenterQsViewModel.kt
index e263d310b..1d434fe7d 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v33/SafetyCenterQsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v33/SafetyCenterQsViewModel.kt
@@ -235,7 +235,7 @@ class SafetyCenterQsViewModel(
}
/**
- * Factory for a SafetyCenterQsViewModel
+ * Factory for a SafetyCenterViewModel
*
* @param app The current application
* @param sessionId A session ID used in logs to identify this particular session
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/AppDataSharingUpdatesViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/AppDataSharingUpdatesViewModel.kt
new file mode 100644
index 000000000..decae06d5
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/AppDataSharingUpdatesViewModel.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.model.v34
+
+import android.Manifest
+import android.app.Activity
+import android.app.Application
+import android.content.Intent
+import android.content.Intent.ACTION_MANAGE_APP_PERMISSIONS
+import android.content.Intent.ACTION_MANAGE_PERMISSIONS
+import android.content.Intent.EXTRA_PACKAGE_NAME
+import android.content.Intent.EXTRA_USER
+import android.os.Build
+import android.os.Process
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import androidx.annotation.RequiresApi
+import com.android.permissioncontroller.permission.data.SinglePermGroupPackagesUiInfoLiveData
+import com.android.permissioncontroller.permission.data.SmartAsyncMediatorLiveData
+import com.android.permissioncontroller.permission.data.v34.AppDataSharingUpdatesLiveData
+import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo
+import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState.PERMS_ALLOWED_ALWAYS
+import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY
+import com.android.permissioncontroller.permission.model.v34.AppDataSharingUpdate.Companion.LOCATION_CATEGORY
+import com.android.permissioncontroller.permission.model.v34.DataSharingUpdateType
+import kotlinx.coroutines.Job
+
+/** View model for data sharing updates UI. */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class AppDataSharingUpdatesViewModel(app: Application) {
+
+ private val appDataSharingUpdatesLiveData = AppDataSharingUpdatesLiveData(app)
+ private val locationPermGroupPackagesUiInfoLiveData =
+ SinglePermGroupPackagesUiInfoLiveData[Manifest.permission_group.LOCATION]
+
+ /** Opens the Safety Label Help Center web page. */
+ fun openSafetyLabelsHelpCenterPage(activity: Activity) {
+ // TODO(b/263838996): Link to Safety Label Help Center.
+ activity.startActivity(Intent(ACTION_MANAGE_PERMISSIONS))
+ }
+
+ /** Start the App Permissions fragment for the provided packageName and userHandle. */
+ fun startAppPermissionsPage(activity: Activity, packageName: String, userHandle: UserHandle) {
+ activity.startActivity(
+ Intent(ACTION_MANAGE_APP_PERMISSIONS).apply {
+ putExtra(EXTRA_PACKAGE_NAME, packageName)
+ putExtra(EXTRA_USER, userHandle)
+ })
+ }
+
+ /**
+ * Builds a list of [AppLocationDataSharingUpdateUiInfo], containing all the information
+ * required to render the app data sharing updates.
+ */
+ private fun buildAppLocationDataSharingUpdateUiInfoList():
+ List<AppLocationDataSharingUpdateUiInfo> {
+ // TODO(b/264830559): Add deterministic ordering for updates.
+ val updateUiInfoList = mutableListOf<AppLocationDataSharingUpdateUiInfo>()
+ // TODO(b/264947954): Move placeholder data to its own file.
+ // TODO(b/264811607): This code serves to ensures that there is some UI to see when testing
+ // feature locally. Remove when app stores start providing safety labels.
+ if (DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY, PLACEHOLDER_SAFETY_LABEL_UPDATES_FLAG, false)) {
+ updateUiInfoList.add(
+ AppLocationDataSharingUpdateUiInfo(
+ PLACEHOLDER_PACKAGE_NAME_1,
+ Process.myUserHandle(),
+ DataSharingUpdateType.ADDS_SHARING_WITH_ADVERTISING_PURPOSE))
+ updateUiInfoList.add(
+ AppLocationDataSharingUpdateUiInfo(
+ PLACEHOLDER_PACKAGE_NAME_2,
+ Process.myUserHandle(),
+ DataSharingUpdateType.ADDS_SHARING_WITHOUT_ADVERTISING_PURPOSE))
+ }
+
+ updateUiInfoList.addAll(
+ appDataSharingUpdatesLiveData.value
+ ?.map { appDataSharingUpdate ->
+ val locationDataSharingUpdate =
+ appDataSharingUpdate.categorySharingUpdates[LOCATION_CATEGORY]
+
+ if (locationDataSharingUpdate == null) {
+ emptyList()
+ } else {
+ val users =
+ locationPermGroupPackagesUiInfoLiveData.getUsersWithPermGrantedForApp(
+ appDataSharingUpdate.packageName)
+ users.map { user ->
+ // For each user profile under the current user, display one entry.
+ AppLocationDataSharingUpdateUiInfo(
+ appDataSharingUpdate.packageName, user, locationDataSharingUpdate)
+ }
+ }
+ }
+ ?.flatten()
+ ?: listOf())
+
+ return updateUiInfoList
+ }
+
+ private fun SinglePermGroupPackagesUiInfoLiveData.getUsersWithPermGrantedForApp(
+ packageName: String
+ ): List<UserHandle> {
+ return value
+ ?.filter {
+ packageToPermInfoEntry: Map.Entry<Pair<String, UserHandle>, AppPermGroupUiInfo> ->
+ val appPermGroupUiInfo = packageToPermInfoEntry.value
+
+ appPermGroupUiInfo.isPermissionGranted()
+ }
+ ?.keys
+ ?.filter { packageUser: Pair<String, UserHandle> -> packageUser.first == packageName }
+ ?.map { packageUser: Pair<String, UserHandle> -> packageUser.second }
+ ?: listOf()
+ }
+
+ private fun AppPermGroupUiInfo.isPermissionGranted() =
+ permGrantState == PERMS_ALLOWED_ALWAYS || permGrantState == PERMS_ALLOWED_FOREGROUND_ONLY
+
+ /** All the information necessary to display an app's data sharing update in the UI. */
+ data class AppLocationDataSharingUpdateUiInfo(
+ val packageName: String,
+ val userHandle: UserHandle,
+ val dataSharingUpdateType: DataSharingUpdateType
+ )
+
+ /** LiveData for all data sharing updates to be displayed in the UI. */
+ val appLocationDataSharingUpdateUiInfoLiveData =
+ object : SmartAsyncMediatorLiveData<List<AppLocationDataSharingUpdateUiInfo>>() {
+
+ init {
+ addSource(appDataSharingUpdatesLiveData) { onUpdate() }
+ addSource(locationPermGroupPackagesUiInfoLiveData) { onUpdate() }
+ }
+
+ override suspend fun loadDataAndPostValue(job: Job) {
+ if (locationPermGroupPackagesUiInfoLiveData.isStale) {
+ return
+ }
+
+ if (appDataSharingUpdatesLiveData.isStale) {
+ return
+ }
+
+ postValue(buildAppLocationDataSharingUpdateUiInfoList())
+ }
+ }
+
+ /** Companion object for [AppDataSharingUpdatesViewModel]. */
+ companion object {
+ private const val PLACEHOLDER_PACKAGE_NAME_1 = "com.android.systemui"
+ private const val PLACEHOLDER_PACKAGE_NAME_2 = "com.android.bluetooth"
+ private const val PLACEHOLDER_SAFETY_LABEL_UPDATES_FLAG =
+ "placeholder_safety_label_updates_flag"
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt
new file mode 100644
index 000000000..7ffce1249
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.model.v34
+
+import android.app.Activity
+import android.app.Application
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.Process
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.permission.safetylabel.DataCategory
+import com.android.permission.safetylabel.DataType
+import com.android.permission.safetylabel.DataTypeConstants
+import com.android.permission.safetylabel.SafetyLabel
+import com.android.permissioncontroller.Constants
+import com.android.permissioncontroller.permission.data.SafetyLabelInfoLiveData
+import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
+import com.android.permissioncontroller.permission.data.get
+import com.android.permissioncontroller.permission.model.livedatatypes.SafetyLabelInfo.Companion.UNAVAILABLE
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED
+import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT
+import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getAppStoreIntent
+import com.android.permissioncontroller.permission.utils.SafetyLabelPermissionMapping
+
+/**
+ * [ViewModel] for the [PermissionRationaleActivity]. Gets all information required safety label and
+ * links required to inform user of data sharing usages by the app when granting this permission
+ *
+ * @param app: The current application
+ * @param packageName: The packageName permissions are being requested for
+ * @param permissionGroupName: The permission group requested
+ * @param sessionId: A long to identify this session
+ * @param storedState: Previous state, if this activity was stopped and is being recreated
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class PermissionRationaleViewModel(
+ private val app: Application,
+ private val packageName: String,
+ private val permissionGroupName: String,
+ // TODO(b/259961958): add PermissionRationale metrics
+ private val sessionId: Long,
+ private val storedState: Bundle?
+) : ViewModel() {
+ private val user = Process.myUserHandle()
+ private val safetyLabelInfoLiveData = SafetyLabelInfoLiveData[packageName, user]
+
+ /** Interface for forwarding onActivityResult to this view model */
+ interface ActivityResultCallback {
+ /**
+ * Should be invoked by base activity when a valid onActivityResult is received
+ *
+ * @param data [Intent] which may contain result data from a started Activity
+ * (various data can be attached to Intent "extras")
+ * @return {@code true} if Activity should finish after processing this result
+ */
+ fun shouldFinishActivityForResult(data: Intent?): Boolean
+ }
+ var activityResultCallback: ActivityResultCallback? = null
+
+ /**
+ * A class which represents a permission rationale for permission group, and messages which
+ * should be shown with it.
+ */
+ data class PermissionRationaleInfo(
+ val groupName: String,
+ val installSourcePackageName: String?,
+ val installSourceLabel: CharSequence?,
+ val purposeSet: Set<Int>
+ )
+
+ /** A [LiveData] which holds the currently pending PermissionRationaleInfo */
+ val permissionRationaleInfoLiveData =
+ object : SmartUpdateMediatorLiveData<PermissionRationaleInfo>() {
+
+ init {
+ addSource(safetyLabelInfoLiveData) { onUpdate() }
+
+ // Load package state, if available
+ onUpdate()
+ }
+
+ override fun onUpdate() {
+ if (safetyLabelInfoLiveData.isStale) {
+ return
+ }
+
+ val safetyLabelInfo = safetyLabelInfoLiveData.value
+ val safetyLabel = safetyLabelInfo?.safetyLabel
+
+ if (safetyLabelInfo == null ||
+ safetyLabelInfo == UNAVAILABLE ||
+ safetyLabel == null) {
+ Log.e(LOG_TAG, "Safety label for $packageName not found")
+ value = null
+ return
+ }
+
+ val installSourcePackageName = safetyLabelInfo.installSourcePackageName
+ val installSourceLabel: CharSequence? =
+ installSourcePackageName?.let {
+ KotlinUtils.getPackageLabel(app, it, Process.myUserHandle())
+ }
+
+ value =
+ PermissionRationaleInfo(
+ permissionGroupName,
+ installSourcePackageName,
+ installSourceLabel,
+ getSafetyLabelSharingPurposesForGroup(safetyLabel, permissionGroupName))
+ }
+
+ private fun getSafetyLabelSharingPurposesForGroup(
+ safetyLabel: SafetyLabel,
+ groupName: String
+ ): Set<Int> {
+ val purposeSet = mutableSetOf<Int>()
+ val categoriesForPermission: List<String> =
+ SafetyLabelPermissionMapping.getCategoriesForPermissionGroup(groupName)
+ categoriesForPermission.forEach categoryLoop@{ category ->
+ val dataCategory: DataCategory? = safetyLabel.dataLabel.dataShared[category]
+ if (dataCategory == null) {
+ // Continue to next
+ return@categoryLoop
+ }
+ val typesForCategory = DataTypeConstants.getValidDataTypesForCategory(category)
+ typesForCategory.forEach typeLoop@{ type ->
+ val dataType: DataType? = dataCategory.dataTypes[type]
+ if (dataType == null) {
+ // Continue to next
+ return@typeLoop
+ }
+ if (dataType.purposeSet.isNotEmpty()) {
+ purposeSet.addAll(dataType.purposeSet)
+ }
+ }
+ }
+
+ return purposeSet
+ }
+ }
+
+ fun canLinkToAppStore(context: Context, installSourcePackageName: String): Boolean {
+ return getAppStoreIntent(context, installSourcePackageName, packageName) != null
+ }
+
+ fun sendToAppStore(context: Context, installSourcePackageName: String) {
+ val storeIntent = getAppStoreIntent(context, installSourcePackageName, packageName)
+ context.startActivity(storeIntent)
+ }
+
+ /**
+ * Send the user to the AppPermissionFragment
+ *
+ * @param activity The current activity
+ * @param groupName The name of the permission group whose fragment should be opened
+ */
+ fun sendToSettingsForPermissionGroup(activity: Activity, groupName: String) {
+ if (activityResultCallback != null) {
+ return
+ }
+ activityResultCallback = object : ActivityResultCallback {
+ override fun shouldFinishActivityForResult(data: Intent?): Boolean {
+ // TODO(b/259961958): metrics for settings return event
+ val returnGroupName = data?.getStringExtra(EXTRA_RESULT_PERMISSION_INTERACTED)
+ return (returnGroupName != null) && data.hasExtra(EXTRA_RESULT_PERMISSION_RESULT)
+ }
+ }
+ startAppPermissionFragment(activity, groupName)
+ }
+
+ /**
+ * Send the user to the Safety Label Android Help Center
+ *
+ * @param activity The current activity
+ */
+ fun sendToLearnMore(activity: Activity) {
+ // TODO(b/259963582): link to safety label help center article
+ Log.d(LOG_TAG, "Link to safety label help center not provided")
+ }
+
+ private fun startAppPermissionFragment(activity: Activity, groupName: String) {
+ val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSION)
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, groupName)
+ .putExtra(Intent.EXTRA_USER, user)
+ .putExtra(ManagePermissionsActivity.EXTRA_CALLER_NAME,
+ PermissionRationaleActivity::class.java.name)
+ .putExtra(Constants.EXTRA_SESSION_ID, sessionId)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ activity.startActivityForResult(intent, APP_PERMISSION_REQUEST_CODE)
+ }
+
+ companion object {
+ private val LOG_TAG = PermissionRationaleViewModel::class.java.simpleName
+
+ const val APP_PERMISSION_REQUEST_CODE = 1
+ }
+}
+
+/** Factory for a [PermissionRationaleViewModel] */
+class PermissionRationaleViewModelFactory(
+ private val app: Application,
+ private val packageName: String,
+ private val permissionGroupName: String,
+ private val sessionId: Long,
+ private val savedState: Bundle?
+) : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ @Suppress("UNCHECKED_CAST")
+ return PermissionRationaleViewModel(
+ app, packageName, permissionGroupName, sessionId, savedState)
+ as T
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/package-info.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/package-info.java
new file mode 100644
index 000000000..4e3697cfc
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/package-info.java
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+package com.android.permissioncontroller.permission.ui.model.v34;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/GrantPermissionsViewHandlerImpl.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/GrantPermissionsViewHandlerImpl.java
index 3c7a90b5b..45dd4c1f2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/GrantPermissionsViewHandlerImpl.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/GrantPermissionsViewHandlerImpl.java
@@ -146,8 +146,11 @@ public final class GrantPermissionsViewHandlerImpl implements GrantPermissionsVi
@Override
public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
- CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities,
+ CharSequence message, CharSequence detailMessage,
+ CharSequence permissionRationaleMessage, boolean[] buttonVisibilities,
boolean[] locationVisibilities) {
+ // permissionRationaleMessage ignored by television
+
// TODO: Handle detailMessage
mGroupName = groupName;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt
index 5f385e064..b21b4cb88 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt
@@ -102,7 +102,7 @@ class TvUnusedAppsFragment : SettingsWithHeader(),
}
private fun createNoUnusedAppsPreference(): Preference {
- val preference = Preference(context)
+ val preference = Preference(requireContext())
preference.title = getString(R.string.zero_unused_apps)
preference.key = UNUSED_PREFERENCE_KEY
preference.isSelectable = false
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java
new file mode 100644
index 000000000..f6e0df504
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleActivity.java
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.v34;
+
+import static android.content.Intent.EXTRA_PERMISSION_GROUP_NAME;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_ACCOUNT_MANAGEMENT;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_ADVERTISING;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_ANALYTICS;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_APP_FUNCTIONALITY;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_DEVELOPER_COMMUNICATIONS;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_FRAUD_PREVENTION_SECURITY;
+import static com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_PERSONALIZATION;
+import static com.android.permissioncontroller.permission.ui.model.v34.PermissionRationaleViewModel.APP_PERMISSION_REQUEST_CODE;
+import static com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler.Result.CANCELLED;
+
+import android.content.Intent;
+import android.icu.lang.UCharacter;
+import android.icu.text.ListFormatter;
+import android.os.Build;
+import android.os.Bundle;
+import android.text.Annotation;
+import android.text.SpannableStringBuilder;
+import android.text.style.ClickableSpan;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import com.android.permission.safetylabel.DataPurposeConstants.Purpose;
+import com.android.permissioncontroller.Constants;
+import com.android.permissioncontroller.DeviceUtils;
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.ui.SettingsActivity;
+import com.android.permissioncontroller.permission.ui.handheld.v34.PermissionRationaleViewHandlerImpl;
+import com.android.permissioncontroller.permission.ui.model.v34.PermissionRationaleViewModel;
+import com.android.permissioncontroller.permission.ui.model.v34.PermissionRationaleViewModel.ActivityResultCallback;
+import com.android.permissioncontroller.permission.ui.model.v34.PermissionRationaleViewModel.PermissionRationaleInfo;
+import com.android.permissioncontroller.permission.ui.model.v34.PermissionRationaleViewModelFactory;
+import com.android.permissioncontroller.permission.utils.KotlinUtils;
+
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * An activity which displays runtime permission rationale on behalf of an app. This activity is
+ * based on GrantPermissionActivity to keep view behavior and theming consistent.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class PermissionRationaleActivity extends SettingsActivity implements
+ PermissionRationaleViewHandler.ResultListener {
+
+ private static final String LOG_TAG = PermissionRationaleActivity.class.getSimpleName();
+
+ private static final String KEY_SESSION_ID = PermissionRationaleActivity.class.getName()
+ + "_SESSION_ID";
+
+ /**
+ * [Annotation] key for span annotations replacement within the permission rationale purposes
+ * string resource
+ */
+ public static final String ANNOTATION_ID_KEY = "id";
+ /**
+ * [Annotation] id value for span annotations replacement of link annotations within the
+ * permission rationale purposes string resource
+ */
+ public static final String LINK_ANNOTATION_ID = "link";
+ /**
+ * [Annotation] id value for span annotations replacement of install source annotations within
+ * the permission rationale purposes string resource
+ */
+ public static final String INSTALL_SOURCE_ANNOTATION_ID = "install_source";
+ /**
+ * [Annotation] id value for span annotations replacement of purpose list annotations within
+ * the permission rationale purposes string resource
+ */
+ public static final String PURPOSE_LIST_ANNOTATION_ID = "purpose_list";
+ /**
+ * [Annotation] id value for span annotations replacement of permission name annotations within
+ * the permission rationale purposes string resource
+ */
+ public static final String PERMISSION_NAME_ANNOTATION_ID = "permission_name";
+
+ /** Unique Id of a request. Inherited from GrantPermissionDialog if provide via intent extra */
+ private long mSessionId;
+ /** Package that shall have permissions granted */
+ private String mTargetPackage;
+ /** The permission group that initiated the permission rationale details activity */
+ private String mPermissionGroupName;
+ /** The permission rationale info resulting from the specified permission and group */
+ private PermissionRationaleInfo mPermissionRationaleInfo;
+
+ private PermissionRationaleViewHandler mViewHandler;
+ private PermissionRationaleViewModel mViewModel;
+
+ private float mOriginalDimAmount;
+ private View mRootView;
+
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+
+ if (!KotlinUtils.INSTANCE.isPermissionRationaleEnabled()) {
+ Log.e(
+ LOG_TAG,
+ "Permission rationale feature disabled");
+ finishAfterTransition();
+ return;
+ }
+
+ if (icicle == null) {
+ mSessionId =
+ getIntent().getLongExtra(Constants.EXTRA_SESSION_ID, new Random().nextLong());
+ } else {
+ mSessionId = icicle.getLong(KEY_SESSION_ID);
+ }
+
+ getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ mPermissionGroupName = getIntent().getStringExtra(EXTRA_PERMISSION_GROUP_NAME);
+ if (mPermissionGroupName == null) {
+ Log.e(
+ LOG_TAG,
+ "null EXTRA_PERMISSION_GROUP_NAME. Must be set for permission rationale");
+ finishAfterTransition();
+ return;
+ }
+
+ mTargetPackage = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ if (mTargetPackage == null) {
+ Log.e(LOG_TAG, "null EXTRA_PACKAGE_NAME. Must be set for permission rationale");
+ finishAfterTransition();
+ return;
+ }
+
+ setFinishOnTouchOutside(false);
+
+ setTitle(R.string.permission_rationale_title);
+
+ if (DeviceUtils.isTelevision(this)
+ || DeviceUtils.isWear(this)
+ || DeviceUtils.isAuto(this)) {
+ finishAfterTransition();
+ } else {
+ mViewHandler = new PermissionRationaleViewHandlerImpl(this, this);
+ }
+
+ PermissionRationaleViewModelFactory factory = new PermissionRationaleViewModelFactory(
+ getApplication(), mTargetPackage, mPermissionGroupName, mSessionId, icicle);
+ mViewModel = factory.create(PermissionRationaleViewModel.class);
+ mViewModel.getPermissionRationaleInfoLiveData()
+ .observe(this, this::onPermissionRationaleInfoLoad);
+
+ mRootView = mViewHandler.createView();
+ mRootView.setVisibility(View.GONE);
+ setContentView(mRootView);
+ Window window = getWindow();
+ WindowManager.LayoutParams layoutParams = window.getAttributes();
+ mOriginalDimAmount = layoutParams.dimAmount;
+ window.setAttributes(layoutParams);
+
+ if (getResources().getBoolean(R.bool.config_useWindowBlur)) {
+ java.util.function.Consumer<Boolean> blurEnabledListener = enabled -> {
+ mViewHandler.onBlurEnabledChanged(window, enabled);
+ };
+ mRootView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ window.getWindowManager().addCrossWindowBlurEnabledListener(
+ blurEnabledListener);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ window.getWindowManager().removeCrossWindowBlurEnabledListener(
+ blurEnabledListener);
+ }
+ });
+ }
+ // Restore UI state after lifecycle events. This has to be before we show the first request,
+ // as the UI behaves differently for updates and initial creations.
+ if (icicle != null) {
+ mViewHandler.loadInstanceState(icicle);
+ } else {
+ // Do not show screen dim until data is loaded
+ window.setDimAmount(0f);
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ if (mViewHandler == null) {
+ return;
+ }
+
+ mViewHandler.saveInstanceState(outState);
+
+ outState.putLong(KEY_SESSION_ID, mSessionId);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ ActivityResultCallback callback = mViewModel.getActivityResultCallback();
+ if (callback == null || (requestCode != APP_PERMISSION_REQUEST_CODE)) {
+ return;
+ }
+ boolean shouldFinishActivity = callback.shouldFinishActivityForResult(data);
+ mViewModel.setActivityResultCallback(null);
+
+ if (shouldFinishActivity) {
+ setResultAndFinish(data);
+ }
+ }
+
+ private void setResultAndFinish(Intent result) {
+ setResult(RESULT_OK, result);
+ finishAfterTransition();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mViewHandler == null) {
+ return;
+ }
+ mViewHandler.onBackPressed();
+ }
+
+ // LINT.IfChange(dispatchTouchEvent)
+ /**
+ * Used to dismiss dialog when tapping outside of dialog bounds
+ * Follows the same logic as GrantPermissionActivity
+ */
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ View rootView = getWindow().getDecorView();
+ if (rootView.getTop() != 0) {
+ // We are animating the top view, need to compensate for that in motion events.
+ ev.setLocation(ev.getX(), ev.getY() - rootView.getTop());
+ }
+ final int x = (int) ev.getX();
+ final int y = (int) ev.getY();
+ if ((x < 0) || (y < 0) || (x > (rootView.getWidth())) || (y > (rootView.getHeight()))) {
+ if (MotionEvent.ACTION_DOWN == ev.getAction()) {
+ mViewHandler.onCancelled();
+ }
+ finishAfterTransition();
+ }
+ return super.dispatchTouchEvent(ev);
+ }
+ // LINT.ThenChange(GrantPermissionsActivity.java:dispatchTouchEvent)
+
+ @Override
+ public void onPermissionRationaleResult(@Nullable String groupName, int result) {
+ if (result == CANCELLED) {
+ finishAfterTransition();
+ }
+ }
+
+ private void onPermissionRationaleInfoLoad(PermissionRationaleInfo permissionRationaleInfo) {
+ if (!mViewModel.getPermissionRationaleInfoLiveData().isInitialized()) {
+ return;
+ }
+
+ if (permissionRationaleInfo == null) {
+ finishAfterTransition();
+ return;
+ }
+
+ mPermissionRationaleInfo = permissionRationaleInfo;
+ showPermissionRationale();
+ }
+
+ private void showPermissionRationale() {
+ List<String> purposesList =
+ new ArrayList<>(mPermissionRationaleInfo.getPurposeSet().size());
+ for (@Purpose int purpose : mPermissionRationaleInfo.getPurposeSet()) {
+ purposesList.add(getStringForPurpose(purpose));
+ }
+
+ // TODO(b/260144215): update purposes join based on l18n feedback, also update ordering
+ // (enum ordering doesn't match expected ux ordering)
+ String purposesString = ListFormatter.getInstance().format(purposesList);
+
+ String installSourcePackageName = mPermissionRationaleInfo.getInstallSourcePackageName();
+ CharSequence installSourceLabel = mPermissionRationaleInfo.getInstallSourceLabel();
+ CharSequence purposeMessage;
+ if (installSourcePackageName == null || installSourcePackageName.length() == 0
+ || installSourceLabel == null || installSourceLabel.length() == 0) {
+ purposeMessage = getString(
+ R.string.permission_rationale_purpose_default_source_message,
+ purposesString);
+ } else {
+ purposeMessage =
+ createPurposeMessageWithSpans(
+ getText(R.string.permission_rationale_purpose_message),
+ installSourceLabel,
+ purposesString,
+ getLinkToAppStore(installSourcePackageName));
+ }
+
+ String groupName = mPermissionRationaleInfo.getGroupName();
+ String permissionGroupLabel =
+ KotlinUtils.INSTANCE.getPermGroupLabel(this, groupName).toString();
+ CharSequence settingsMessage =
+ createSettingsMessageWithSpans(
+ getText(R.string.permission_rationale_permission_settings_message),
+ UCharacter.toLowerCase(permissionGroupLabel),
+ getLinkToSettings()
+ );
+
+ CharSequence learnMoreMessage =
+ setLink(
+ getText(R.string.permission_rationale_permission_learn_more_title),
+ getLearnMoreLink()
+ );
+
+ mViewHandler.updateUi(
+ groupName,
+ purposeMessage,
+ settingsMessage,
+ learnMoreMessage
+ );
+
+ getWindow().setDimAmount(mOriginalDimAmount);
+ if (mRootView.getVisibility() == View.GONE) {
+ InputMethodManager manager = getSystemService(InputMethodManager.class);
+ manager.hideSoftInputFromWindow(mRootView.getWindowToken(), 0);
+ mRootView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private String getStringForPurpose(@Purpose int purpose) {
+ switch (purpose) {
+ case PURPOSE_APP_FUNCTIONALITY:
+ return getString(R.string.permission_rational_purpose_app_functionality);
+ case PURPOSE_ANALYTICS:
+ return getString(R.string.permission_rational_purpose_analytics);
+ case PURPOSE_DEVELOPER_COMMUNICATIONS:
+ return getString(R.string.permission_rational_purpose_developer_communications);
+ case PURPOSE_FRAUD_PREVENTION_SECURITY:
+ return getString(R.string.permission_rational_purpose_fraud_prevention_security);
+ case PURPOSE_ADVERTISING:
+ return getString(R.string.permission_rational_purpose_advertising);
+ case PURPOSE_PERSONALIZATION:
+ return getString(R.string.permission_rational_purpose_personalization);
+ case PURPOSE_ACCOUNT_MANAGEMENT:
+ return getString(R.string.permission_rational_purpose_account_management);
+ default:
+ throw new IllegalArgumentException("Invalid purpose: " + purpose);
+ }
+ }
+
+ private CharSequence createPurposeMessageWithSpans(
+ CharSequence baseText,
+ CharSequence installSourceLabel,
+ CharSequence purposes,
+ ClickableSpan link) {
+ CharSequence updatedText =
+ replaceSpan(baseText, INSTALL_SOURCE_ANNOTATION_ID, installSourceLabel);
+ updatedText = replaceSpan(updatedText, PURPOSE_LIST_ANNOTATION_ID, purposes);
+ updatedText = setLink(updatedText, link);
+ return updatedText;
+ }
+
+ private CharSequence createSettingsMessageWithSpans(
+ CharSequence baseText,
+ CharSequence permissionName,
+ ClickableSpan link) {
+ CharSequence updatedText =
+ replaceSpan(baseText, PERMISSION_NAME_ANNOTATION_ID, permissionName);
+ updatedText = setLink(updatedText, link);
+ return updatedText;
+ }
+
+ private CharSequence replaceSpan(
+ CharSequence baseText,
+ String annotationId,
+ CharSequence replacementText) {
+ SpannableStringBuilder text = SpannableStringBuilder.valueOf(baseText);
+ Annotation[] annotations = text.getSpans(0, text.length(), Annotation.class);
+
+ for (android.text.Annotation annotation : annotations) {
+ if (!annotation.getKey().equals(ANNOTATION_ID_KEY)
+ || !annotation.getValue().equals(annotationId)) {
+ continue;
+ }
+
+ int spanStart = text.getSpanStart(annotation);
+ int spanEnd = text.getSpanEnd(annotation);
+ text.removeSpan(annotation);
+ text.replace(spanStart, spanEnd, replacementText);
+ break;
+ }
+
+ return text;
+ }
+
+ private CharSequence setLink(CharSequence baseText, ClickableSpan link) {
+ SpannableStringBuilder text = SpannableStringBuilder.valueOf(baseText);
+ Annotation[] annotations = text.getSpans(0, text.length(), Annotation.class);
+
+ for (android.text.Annotation annotation : annotations) {
+ if (!annotation.getKey().equals(ANNOTATION_ID_KEY)
+ || !annotation.getValue().equals(LINK_ANNOTATION_ID)) {
+ continue;
+ }
+
+ int spanStart = text.getSpanStart(annotation);
+ int spanEnd = text.getSpanEnd(annotation);
+ text.removeSpan(annotation);
+ text.setSpan(link, spanStart, spanEnd, 0);
+ break;
+ }
+
+ return text;
+ }
+
+ private ClickableSpan getLinkToAppStore(String installSourcePackageName) {
+ boolean canLinkToAppStore = mViewModel
+ .canLinkToAppStore(PermissionRationaleActivity.this, installSourcePackageName);
+ if (!canLinkToAppStore) {
+ return null;
+ }
+ return new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull View widget) {
+ // TODO(b/259961958): metrics for click events
+ mViewModel.sendToAppStore(PermissionRationaleActivity.this,
+ installSourcePackageName);
+ }
+ };
+ }
+
+ private ClickableSpan getLinkToSettings() {
+ return new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull View widget) {
+ // TODO(b/259961958): metrics for click events
+ mViewModel.sendToSettingsForPermissionGroup(PermissionRationaleActivity.this,
+ mPermissionGroupName);
+ }
+ };
+ }
+
+ private ClickableSpan getLearnMoreLink() {
+ return new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull View widget) {
+ // TODO(b/259961958): metrics for click events
+ mViewModel.sendToLearnMore(PermissionRationaleActivity.this);
+ }
+ };
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleViewHandler.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleViewHandler.kt
new file mode 100644
index 000000000..7b2295921
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/PermissionRationaleViewHandler.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.v34
+
+import android.os.Build
+import android.os.Bundle
+import android.view.View
+import android.view.Window
+import androidx.annotation.IntDef
+import androidx.annotation.RequiresApi
+
+/**
+ * Class for managing the presentation and user interaction of the "permission rationale" user
+ * interface.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+interface PermissionRationaleViewHandler {
+ @Retention(AnnotationRetention.SOURCE)
+ @IntDef(Result.CANCELLED)
+ annotation class Result {
+ companion object {
+ const val CANCELLED = -1
+ }
+ }
+
+ /**
+ * Listener interface for getting notified when the user responds to a permission rationale
+ * user action.
+ */
+ interface ResultListener {
+ fun onPermissionRationaleResult(groupName: String?, @Result result: Int)
+ }
+
+ /**
+ * Creates and returns the view hierarchy that is managed by this view handler. This must be
+ * called before [.updateUi].
+ */
+ fun createView(): View
+
+ /**
+ * Updates the view hierarchy to reflect the specified state.
+ *
+ * Note that this must be called at least once before showing the UI to the user to properly
+ * initialize the UI.
+ *
+ * @param groupName the name of the permission group
+ * @param purposeMessage the data usage purposes message to display the user
+ * @param settingsMessage the settings link message to display the user
+ * @param learnMoreMessage the more info about safety labels message to display the user
+ */
+ fun updateUi(
+ groupName: String,
+ purposeMessage: CharSequence,
+ settingsMessage: CharSequence,
+ learnMoreMessage: CharSequence
+ )
+
+ /**
+ * Called by [PermissionRationaleActivity] to save the state of this view handler to the
+ * specified bundle.
+ */
+ fun saveInstanceState(outState: Bundle)
+
+ /**
+ * Called by [PermissionRationaleActivity] to load the state of this view handler from the
+ * specified bundle.
+ */
+ fun loadInstanceState(savedInstanceState: Bundle)
+
+ /** Gives a chance for handling the back key. */
+ fun onBackPressed()
+
+ /**
+ * Handles cancel event for the permission rationale dialog.
+ */
+ fun onCancelled() {}
+
+ /**
+ * Called by [PermissionRationaleActivity] to allow the handler to update the ui when blur is
+ * enabled/disabled.
+ */
+ fun onBlurEnabledChanged(window: Window?, enabled: Boolean) {}
+}
diff --git a/service/java/com/android/access/package-info.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/package-info.java
index 18fcfc69d..0e68b52e0 100644
--- a/service/java/com/android/access/package-info.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/v34/package-info.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,9 +14,5 @@
* limitations under the License.
*/
-/**
- * @hide
- * TODO(b/146466118) remove this javadoc tag
- */
-@android.annotation.Hide
-package com.android.access;
+@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+package com.android.permissioncontroller.permission.ui.v34;
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 1c0af63cc..ccd1ed42e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java
@@ -96,8 +96,11 @@ public final class GrantPermissionsWearViewHandler implements GrantPermissionsVi
@Override
public void updateUi(String groupName, int groupCount, int groupIndex, Icon icon,
- CharSequence message, CharSequence detailMessage, boolean[] buttonVisibilities,
+ CharSequence message, CharSequence detailMessage,
+ CharSequence permissionRationaleMessage, boolean[] buttonVisibilities,
boolean[] locationVisibilities) {
+ // permissionRationaleMessage ignored by wear
+
// TODO: Handle detailMessage
boolean showDoNotAsk = buttonVisibilities[DENY_AND_DONT_ASK_AGAIN_BUTTON];
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index 82ad2494b..3dfffe415 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
@@ -21,6 +21,8 @@ import android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.Manifest.permission.BACKUP
import android.Manifest.permission.POST_NOTIFICATIONS
+import android.Manifest.permission.READ_MEDIA_IMAGES
+import android.Manifest.permission.READ_MEDIA_VIDEO
import android.Manifest.permission_group.NOTIFICATIONS
import android.app.ActivityManager
import android.app.AppOpsManager
@@ -46,16 +48,20 @@ import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
import android.content.pm.PermissionGroupInfo
import android.content.pm.PermissionInfo
+import android.content.pm.ResolveInfo
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
+import android.healthconnect.HealthConnectManager
import android.os.Build
import android.os.Bundle
import android.os.UserHandle
import android.permission.PermissionManager
import android.provider.DeviceConfig
import android.provider.Settings
+import android.safetylabel.SafetyLabelConstants.PERMISSION_RATIONALE_ENABLED
+import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED
import android.text.TextUtils
import android.util.Log
import androidx.annotation.ChecksSdkIntAtLeast
@@ -75,16 +81,18 @@ import com.android.permissioncontroller.permission.model.livedatatypes.LightPerm
import com.android.permissioncontroller.permission.model.livedatatypes.PermState
import com.android.permissioncontroller.permission.service.LocationAccessCheck
import com.android.permissioncontroller.permission.ui.handheld.SettingsWithLargeHeader
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.async
-import kotlinx.coroutines.launch
+import java.time.Duration
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.Continuation
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.launch
+
/**
* A set of util functions designed to work with kotlin, though they can work with java, as well.
*/
@@ -117,6 +125,213 @@ object KotlinUtils {
private val ONE_TIME_PACKAGE_IMPORTANCE_LEVEL_TO_KEEP_SESSION_ALIVE =
ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
+ /** Whether to show the Permissions Hub. */
+ private const val PROPERTY_PERMISSIONS_HUB_2_ENABLED = "permissions_hub_2_enabled"
+
+ /** Whether to show the mic and camera icons. */
+ private const val PROPERTY_CAMERA_MIC_ICONS_ENABLED = "camera_mic_icons_enabled"
+
+ /** Whether to show the location indicators. */
+ private const val PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled"
+
+ /** Whether location accuracy feature is enabled */
+ private const val PROPERTY_LOCATION_ACCURACY_ENABLED = "location_accuracy_enabled"
+
+ /** Whether to show 7-day toggle in privacy hub. */
+ private const val PRIVACY_DASHBOARD_7_DAY_TOGGLE = "privacy_dashboard_7_day_toggle"
+
+ /** Whether to placeholder safety label data in permission settings and grant dialog. */
+ private const val PRIVACY_PLACEHOLDER_SAFETY_LABEL_DATA_ENABLED =
+ "privacy_placeholder_safety_label_data_enabled"
+
+ /** Default location precision */
+ private const val PROPERTY_LOCATION_PRECISION = "location_precision"
+
+ /** Whether to show the photo picker option in permission prompts. */
+ private const val PROPERTY_PHOTO_PICKER_PROMPT_ENABLED = "photo_picker_prompt_enabled"
+
+ /**
+ * The minimum amount of time to wait, after scheduling the safety label changes job, before
+ * the job actually runs for the first time.
+ */
+ private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_DELAY_MILLIS =
+ "safety_label_changes_job_delay_millis"
+
+ /** How often the safety label changes job service will run its job. */
+ private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_INTERVAL_MILLIS =
+ "safety_label_changes_job_interval_millis"
+
+ /** Whether the safety label changes job should only be run when the device is idle. */
+ private const val PROPERTY_SAFETY_LABEL_CHANGES_JOB_RUN_WHEN_IDLE =
+ "safety_label_changes_job_run_when_idle"
+
+ /**
+ * Whether the Permissions Hub 2 flag is enabled
+ *
+ * @return whether the flag is enabled
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun isPermissionsHub2FlagEnabled(): Boolean {
+ return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PERMISSIONS_HUB_2_ENABLED, false)
+ }
+ /**
+ * Whether to show the Permissions Dashboard
+ *
+ * @return whether to show the Permissions Dashboard.
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun shouldShowPermissionsDashboard(): Boolean {
+ return isPermissionsHub2FlagEnabled()
+ }
+
+ /**
+ * Whether the Camera and Mic Icons are enabled by flag.
+ *
+ * @return whether the Camera and Mic Icons are enabled.
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun isCameraMicIconsFlagEnabled(): Boolean {
+ return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_CAMERA_MIC_ICONS_ENABLED, true)
+ }
+
+ /**
+ * Whether to show Camera and Mic Icons. They should be shown if the permission hub, or the icons
+ * specifically, are enabled.
+ *
+ * @return whether to show the icons.
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun shouldShowCameraMicIndicators(): Boolean {
+ return isCameraMicIconsFlagEnabled() || isPermissionsHub2FlagEnabled()
+ }
+
+ /**
+ * Whether the location indicators are enabled by flag.
+ *
+ * @return whether the location indicators are enabled by flag.
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun isLocationIndicatorsFlagEnabled(): Boolean {
+ return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_INDICATORS_ENABLED, false)
+ }
+
+ /**
+ * Whether to show the location indicators. The location indicators are enable if the
+ * permission hub, or location indicator specifically are enabled.
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun shouldShowLocationIndicators(): Boolean {
+ return isLocationIndicatorsFlagEnabled() || isPermissionsHub2FlagEnabled()
+ }
+
+ /**
+ * Whether the location accuracy feature is enabled
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun isLocationAccuracyEnabled(): Boolean {
+ return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_ACCURACY_ENABLED, true)
+ }
+
+ /**
+ * Default state of location precision
+ * true: default is FINE.
+ * false: default is COARSE.
+ */
+ fun getDefaultPrecision(): Boolean {
+ return !SdkLevel.isAtLeastS() || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_LOCATION_PRECISION, true)
+ }
+
+ /**
+ * Whether we should enable the 7-day toggle in privacy dashboard
+ *
+ * @return whether the flag is enabled
+ */
+ @ChecksSdkIntAtLeast(Build.VERSION_CODES.S)
+ fun is7DayToggleEnabled(): Boolean {
+ return SdkLevel.isAtLeastS() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PRIVACY_DASHBOARD_7_DAY_TOGGLE, false)
+ }
+
+ /**
+ * Whether the Photo Picker Prompt is enabled
+ *
+ * @return `true` iff the Location Access Check is enabled.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun isPhotoPickerPromptEnabled(): Boolean {
+ return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PHOTO_PICKER_PROMPT_ENABLED, false)
+ }
+
+ /*
+ * Whether we should enable the permission rationale in permission settings and grant dialog
+ *
+ * @return whether the flag is enabled
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun isPermissionRationaleEnabled(): Boolean {
+ return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PERMISSION_RATIONALE_ENABLED, false)
+ }
+
+ /**
+ * Whether we should use placeholder safety label data
+ *
+ * @return whether the flag is enabled
+ */
+ fun isPlaceholderSafetyLabelDataEnabled(): Boolean {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PRIVACY_PLACEHOLDER_SAFETY_LABEL_DATA_ENABLED, false)
+ }
+
+ /**
+ * Whether we should enable the safety label change notifications and data sharing updates UI.
+ *
+ * This feature has its own [DeviceConfig] flag, however, we also ensure it is only enabled
+ * when its preceding feature, Permission Rationale, is enabled.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun isSafetyLabelChangeNotificationsEnabled(): Boolean {
+ return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED, false) &&
+ isPermissionRationaleEnabled()
+ }
+
+ /**
+ * The minimum amount of time to wait, after scheduling the safety label changes job, before
+ * the job actually runs for the first time.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun getSafetyLabelChangesJobDelayMillis(): Long {
+ return DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_LABEL_CHANGES_JOB_DELAY_MILLIS,
+ Duration.ofMinutes(30).toMillis())
+ }
+
+ /** How often the safety label changes job will run. */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun getSafetyLabelChangesJobIntervalMillis(): Long {
+ return DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_LABEL_CHANGES_JOB_INTERVAL_MILLIS,
+ Duration.ofDays(30).toMillis())
+ }
+
+ /** Whether the safety label changes job should only be run when the device is idle. */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ fun runSafetyLabelChangesJobOnlyWhenDeviceIdle(): Boolean {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_SAFETY_LABEL_CHANGES_JOB_RUN_WHEN_IDLE,
+ true)
+ }
+
/**
* Given a Map, and a List, determines which elements are in the list, but not the map, and
* vice versa. Used primarily for determining which liveDatas are already being watched, and
@@ -385,6 +600,20 @@ object KotlinUtils {
}
/**
+ * Return a specific MIME type, if a set of permissions is associated with one
+ */
+ fun getMimeTypeForPermissions(permissions: List<String>): String? {
+ if (permissions.contains(READ_MEDIA_IMAGES) && !permissions.contains(READ_MEDIA_VIDEO)) {
+ return "image/*"
+ }
+ if (permissions.contains(READ_MEDIA_VIDEO) && !permissions.contains(READ_MEDIA_IMAGES)) {
+ return "video/*"
+ }
+
+ return null
+ }
+
+ /**
* Determines if an app is R or above, or if it is Q-, and has auto revoke enabled
*
* @param app The currenct application
@@ -494,9 +723,12 @@ object KotlinUtils {
app: Application,
group: LightAppPermGroup,
filterPermissions: List<String> = group.permissions.keys.toList(),
- isOneTime: Boolean = false
+ isOneTime: Boolean = false,
+ userFixed: Boolean = false,
+ withoutAppOps: Boolean = false,
): LightAppPermGroup {
- return grantRuntimePermissions(app, group, false, isOneTime, filterPermissions)
+ return grantRuntimePermissions(app, group, false, isOneTime, userFixed,
+ withoutAppOps, filterPermissions)
}
/**
@@ -518,7 +750,8 @@ object KotlinUtils {
group: LightAppPermGroup,
filterPermissions: List<String> = group.permissions.keys.toList()
): LightAppPermGroup {
- return grantRuntimePermissions(app, group, true, false, filterPermissions)
+ return grantRuntimePermissions(app, group, true, false, false, false,
+ filterPermissions)
}
private fun grantRuntimePermissions(
@@ -526,16 +759,18 @@ object KotlinUtils {
group: LightAppPermGroup,
grantBackground: Boolean,
isOneTime: Boolean = false,
- filterPermissions: List<String> = group.permissions.keys.toList()
+ userFixed: Boolean = false,
+ withoutAppOps: Boolean = false,
+ filterPermissions: List<String> = group.permissions.keys.toList(),
): LightAppPermGroup {
- val wasOneTime = group.isOneTime
val newPerms = group.permissions.toMutableMap()
var shouldKillForAnyPermission = false
for (permName in filterPermissions) {
val perm = group.permissions[permName] ?: continue
val isBackgroundPerm = permName in group.backgroundPermNames
if (isBackgroundPerm == grantBackground) {
- val (newPerm, shouldKill) = grantRuntimePermission(app, perm, isOneTime, group)
+ val (newPerm, shouldKill) = grantRuntimePermission(app, perm, group, isOneTime,
+ userFixed, withoutAppOps)
newPerms[newPerm.name] = newPerm
shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
}
@@ -544,7 +779,7 @@ object KotlinUtils {
val user = UserHandle.getUserHandleForUid(group.packageInfo.uid)
for (groupPerm in group.allPermissions.values) {
var permFlags = groupPerm!!.flags
- permFlags = permFlags.clearFlag(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)
+ permFlags = permFlags.clearFlag(FLAG_PERMISSION_AUTO_REVOKED)
if (groupPerm!!.flags != permFlags) {
app.packageManager.updatePermissionFlags(groupPerm!!.name,
group.packageInfo.packageName, PERMISSION_CONTROLLER_CHANGED_FLAG_MASK,
@@ -582,8 +817,12 @@ object KotlinUtils {
*
* @param app The current application
* @param perm The permission which should be granted.
- * @param group An optional app permission group in which to look for background or foreground
+ * @param group An app permission group in which to look for background or foreground
+ * @param isOneTime Whether this is a one-time permission grant
* permissions
+ * @param userFixed Whether to mark the permissions as user fixed when granted
+ * @param withoutAppOps If these permission have app ops associated, and this value is true,
+ * then do not grant the app op when the permission is granted, and add the REVOKED_COMPAT flag.
*
* @return a LightPermission and boolean pair <permission with updated state (or the original
* state, if it wasn't changed), should kill app>
@@ -591,8 +830,10 @@ object KotlinUtils {
private fun grantRuntimePermission(
app: Application,
perm: LightPermission,
+ group: LightAppPermGroup,
isOneTime: Boolean,
- group: LightAppPermGroup
+ userFixed: Boolean = false,
+ withoutAppOps: Boolean = false
): Pair<LightPermission, Boolean> {
val pkgInfo = group.packageInfo
val user = UserHandle.getUserHandleForUid(pkgInfo.uid)
@@ -624,31 +865,39 @@ object KotlinUtils {
shouldKill = true
isGranted = true
}
- newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
+ newFlags = if (withoutAppOps) {
+ newFlags.setFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
+ } else {
+ newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKED_COMPAT)
+ }
newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVOKE_WHEN_REQUESTED)
// If this permission affects an app op, ensure the permission app op is enabled
// before the permission grant.
- if (affectsAppOp) {
+ if (affectsAppOp && !withoutAppOps) {
allowAppOp(app, perm, group)
}
}
// Granting a permission explicitly means the user already
// reviewed it so clear the review flag on every grant.
- newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED)
+ newFlags = newFlags.clearFlag(FLAG_PERMISSION_REVIEW_REQUIRED)
// Update the permission flags
- // Now the apps can ask for the permission as the user
- // no longer has it fixed in a denied state.
- newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
- newFlags = newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_SET)
- newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)
+ if (!withoutAppOps && !userFixed) {
+ // Now the apps can ask for the permission as the user
+ // no longer has it fixed in a denied state.
+ newFlags = newFlags.clearFlag(FLAG_PERMISSION_USER_FIXED)
+ newFlags = newFlags.setFlag(FLAG_PERMISSION_USER_SET)
+ } else if (userFixed) {
+ newFlags = newFlags.setFlag(FLAG_PERMISSION_USER_FIXED)
+ }
+ newFlags = newFlags.clearFlag(FLAG_PERMISSION_AUTO_REVOKED)
newFlags = if (isOneTime) {
- newFlags.setFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
+ newFlags.setFlag(FLAG_PERMISSION_ONE_TIME)
} else {
- newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
+ newFlags.clearFlag(FLAG_PERMISSION_ONE_TIME)
}
// If we newly grant background access to the fine location, double-guess the user some
@@ -1196,6 +1445,44 @@ object KotlinUtils {
context.getDrawable(android.R.drawable.ic_safety_protection) != null &&
!context.getString(android.R.string.safety_protection_display_text).isNullOrEmpty()
}
+
+ fun addHealthPermissions(context: Context) {
+ val permissions = HealthConnectManager.getHealthPermissions(context)
+ PermissionMapping.addHealthPermissionsToPlatform(permissions)
+ }
+
+ /**
+ * Returns an [Intent] to the installer app store for a given package name, or {@code null} if
+ * none found
+ */
+ fun getAppStoreIntent(
+ context: Context,
+ installerPackageName: String?,
+ packageName: String?
+ ): Intent? {
+ val intent: Intent = Intent(Intent.ACTION_SHOW_APP_INFO)
+ .setPackage(installerPackageName)
+ val result: Intent? = resolveActivityForIntent(context, intent)
+ if (result != null) {
+ result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ return result
+ }
+ return null
+ }
+
+ /**
+ * Verify that a component that supports the intent with action and return a new intent with
+ * same action and resolved class name set. Returns null if no activity resolution.
+ */
+ private fun resolveActivityForIntent(context: Context, intent: Intent): Intent? {
+ val result: ResolveInfo? = context.packageManager.resolveActivity(intent, 0)
+ return if (result != null) {
+ Intent(intent.action)
+ .setClassName(result.activityInfo.packageName, result.activityInfo.name)
+ } else {
+ null
+ }
+ }
}
/**
@@ -1267,4 +1554,4 @@ fun NavController.navigateSafe(destResId: Int, args: Bundle? = null) {
navigate(destResId, args)
}
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
index fa7cbec6d..4e70e1bc5 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt
@@ -19,6 +19,8 @@ package com.android.permissioncontroller.permission.utils
import android.Manifest
import android.content.pm.PackageManager
import android.content.pm.PermissionInfo
+import android.healthconnect.HealthPermissions.HEALTH_PERMISSION_GROUP
+import android.util.Log
import com.android.modules.utils.build.SdkLevel
/**
@@ -27,6 +29,8 @@ import com.android.modules.utils.build.SdkLevel
*/
object PermissionMapping {
+ private val LOG_TAG = "PermissionMapping"
+
@JvmField
val SENSOR_DATA_PERMISSIONS: List<String> =
listOf(
@@ -52,6 +56,8 @@ object PermissionMapping {
/** Set of groups that will be able to receive one-time grant */
private val ONE_TIME_PERMISSION_GROUPS: MutableSet<String> = mutableSetOf()
+ private val HEALTH_PERMISSIONS_SET: MutableSet<String> = mutableSetOf()
+
init {
PLATFORM_PERMISSIONS[Manifest.permission.READ_CONTACTS] = Manifest.permission_group.CONTACTS
PLATFORM_PERMISSIONS[Manifest.permission.WRITE_CONTACTS] =
@@ -62,6 +68,9 @@ object PermissionMapping {
PLATFORM_PERMISSIONS[Manifest.permission.WRITE_CALENDAR] =
Manifest.permission_group.CALENDAR
+ // Any updates to the permissions for the SMS permission group must also be made in
+ // Permissions {@link com.android.role.controller.model.Permissions} in the role
+ // library
PLATFORM_PERMISSIONS[Manifest.permission.SEND_SMS] = Manifest.permission_group.SMS
PLATFORM_PERMISSIONS[Manifest.permission.RECEIVE_SMS] = Manifest.permission_group.SMS
PLATFORM_PERMISSIONS[Manifest.permission.READ_SMS] = Manifest.permission_group.SMS
@@ -92,6 +101,11 @@ object PermissionMapping {
Manifest.permission_group.READ_MEDIA_VISUAL
}
+ if (SdkLevel.isAtLeastU()) {
+ PLATFORM_PERMISSIONS[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED] =
+ Manifest.permission_group.READ_MEDIA_VISUAL
+ }
+
PLATFORM_PERMISSIONS[Manifest.permission.ACCESS_FINE_LOCATION] =
Manifest.permission_group.LOCATION
PLATFORM_PERMISSIONS[Manifest.permission.ACCESS_COARSE_LOCATION] =
@@ -114,6 +128,9 @@ object PermissionMapping {
Manifest.permission_group.NEARBY_DEVICES
}
+ // Any updates to the permissions for the CALL_LOG permission group must also be made in
+ // Permissions {@link com.android.role.controller.model.Permissions} in the role
+ // library
PLATFORM_PERMISSIONS[Manifest.permission.READ_CALL_LOG] = Manifest.permission_group.CALL_LOG
PLATFORM_PERMISSIONS[Manifest.permission.WRITE_CALL_LOG] =
Manifest.permission_group.CALL_LOG
@@ -274,4 +291,31 @@ object PermissionMapping {
fun supportsOneTimeGrant(permissionGroup: String?): Boolean {
return ONE_TIME_PERMISSION_GROUPS.contains(permissionGroup)
}
+
+ /**
+ * Adds health permissions as platform permissions.
+ */
+ @JvmStatic
+ fun addHealthPermissionsToPlatform(permissions: Set<String>) {
+ if (permissions.isEmpty()) {
+ Log.w(LOG_TAG, "No health connect permissions found.")
+ return
+ }
+
+ PLATFORM_PERMISSION_GROUPS[HEALTH_PERMISSION_GROUP] = mutableListOf()
+
+ for (permission in permissions) {
+ PLATFORM_PERMISSIONS[permission] = HEALTH_PERMISSION_GROUP
+ PLATFORM_PERMISSION_GROUPS[HEALTH_PERMISSION_GROUP]?.add(permission)
+ HEALTH_PERMISSIONS_SET.add(permission)
+ }
+ }
+
+ /**
+ * Returns true if the given permission is a health platform permission.
+ */
+ @JvmStatic
+ fun isHealthPermission(permissionName: String): Boolean {
+ return HEALTH_PERMISSIONS_SET.contains(permissionName)
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt
new file mode 100644
index 000000000..5fd852bb6
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/SafetyLabelPermissionMapping.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.utils
+
+import android.Manifest
+import com.android.permission.safetylabel.DataCategoryConstants
+
+/**
+ * This file contains the canonical mapping of permission and permission group to Safety Label
+ * categories and types used in the Permission settings screens and grant dialog. It also includes
+ * methods related to that mapping.
+ */
+object SafetyLabelPermissionMapping {
+
+ /**
+ * Get the Safety Label categories pertaining to a specified permission group.
+ *
+ * @param groupName the permission group name
+ *
+ * @return The categories or an empty list if the group does not have supported and mapped group
+ * to safety label category
+ */
+ fun getCategoriesForPermissionGroup(groupName: String): List<String> {
+ return if (groupName == Manifest.permission_group.LOCATION) {
+ listOf(DataCategoryConstants.CATEGORY_LOCATION)
+ } else {
+ emptyList()
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java
index e60b6c18a..2785eca74 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java
@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
+import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo;
import java.util.HashMap;
import java.util.Map;
@@ -48,14 +49,18 @@ public class SubattributionUtils {
return appInfo.areAttributionsUserVisible();
}
+ /** Returns whether the provided package supports subattribution. */
+ public static boolean isSubattributionSupported(LightPackageInfo lightPackageInfo) {
+ return SdkLevel.isAtLeastS() && lightPackageInfo.getAreAttributionsUserVisible();
+ }
+
/**
- * Returns the attribution label map for the package if the app supports subattribtion; Returns
+ * Returns the attribution label map for the package if the app supports subattribution; Returns
* {@code null} otherwise.
*/
@Nullable
@SuppressLint("NewApi") // isSubattributionSupported checks api level
- public static Map<Integer, String> getAttributionLabels(Context context,
- PackageInfo pkgInfo) {
+ public static Map<Integer, String> getAttributionLabels(Context context, PackageInfo pkgInfo) {
if (!isSubattributionSupported(context, pkgInfo.applicationInfo)) {
return null;
}
@@ -63,7 +68,7 @@ public class SubattributionUtils {
}
/**
- * Returns the attribution label map for the package if the app supports subattribtion; Returns
+ * Returns the attribution label map for the package if the app supports subattribtuion; Returns
* {@code null} otherwise.
*/
@Nullable
@@ -106,4 +111,34 @@ public class SubattributionUtils {
}
return attributionLabels;
}
+
+ /** Returns the attribution label map for the package if the app supports subattribution. */
+ @Nullable
+ @SuppressLint("NewApi") // isSubattributionSupported checks api level
+ public static Map<Integer, String> getAttributionLabels(Context context,
+ LightPackageInfo lightPackageInfo) {
+ if (!isSubattributionSupported(lightPackageInfo)) {
+ return null;
+ }
+
+ Context pkgContext;
+ try {
+ pkgContext = context.createPackageContext(lightPackageInfo.getPackageName(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+
+ Map<Integer, String> attributionLabels = new HashMap<>();
+ for (Map.Entry<String, Integer> attribution :
+ lightPackageInfo.getAttributionTagsToLabels().entrySet()) {
+ int label = attribution.getValue();
+ try {
+ String resourceForLabel = pkgContext.getString(attribution.getValue());
+ attributionLabels.put(label, resourceForLabel);
+ } catch (Resources.NotFoundException e) {
+ // should never happen
+ }
+ }
+ return attributionLabels;
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
index 8f5a70ca0..6bc9ca9c2 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -34,12 +34,14 @@ import static android.Manifest.permission_group.STORAGE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.OPSTR_LEGACY_STORAGE;
import static android.content.Context.MODE_PRIVATE;
+import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
+import static android.healthconnect.HealthConnectManager.ACTION_MANAGE_HEALTH_PERMISSIONS;
import static android.os.UserHandle.myUserId;
import static com.android.permissioncontroller.Constants.EXTRA_SESSION_ID;
@@ -87,6 +89,7 @@ import android.util.TypedValue;
import android.view.Menu;
import android.view.MenuItem;
+import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.ColorRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
@@ -168,6 +171,11 @@ public final class Utils {
private static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED =
"location_access_check_enabled";
+ /** Whether to show health permission in various permission controller UIs. */
+ private static final String PROPERTY_HEALTH_PERMISSION_UI_ENABLED =
+ "health_permission_ui_enabled";
+
+
/** How frequently to check permission event store to scrub old data */
public static final String PROPERTY_PERMISSION_EVENTS_CHECK_OLD_FREQUENCY_MILLIS =
"permission_events_check_old_frequency_millis";
@@ -923,6 +931,16 @@ public final class Utils {
}
/**
+ * Whether we should show health permissions as platform permissions in the various
+ * permission controller UI.
+ */
+ @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codename = "UpsideDownCake")
+ public static boolean isHealthPermissionUiEnabled() {
+ return SdkLevel.isAtLeastU() && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_HEALTH_PERMISSION_UI_ENABLED, true);
+ }
+
+ /**
* Get a device protected storage based shared preferences. Avoid storing sensitive data in it.
*
* @param context the context to get the shared preferences
@@ -1236,6 +1254,28 @@ public final class Utils {
}
/**
+ * Navigate to health connect settings for all apps
+ * @param context The current Context
+ */
+ public static void navigateToHealthConnectSettings(@NonNull Context context) {
+ Intent healthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS);
+ context.startActivity(healthConnectIntent);
+ }
+
+ /**
+ * Navigate to health connect settings for an app
+ * @param context The current Context
+ * @param packageName The package's health connect settings to navigate to
+ */
+ public static void navigateToAppHealthConnectSettings(@NonNull Context context,
+ @NonNull String packageName, @NonNull UserHandle user) {
+ Intent appHealthConnectIntent = new Intent(ACTION_MANAGE_HEALTH_PERMISSIONS);
+ appHealthConnectIntent.putExtra(EXTRA_PACKAGE_NAME, packageName);
+ appHealthConnectIntent.putExtra(Intent.EXTRA_USER, user);
+ context.startActivity(appHealthConnectIntent);
+ }
+
+ /**
* Returns if a card should be shown if the sensor is blocked
**/
public static boolean shouldDisplayCardIfBlocked(@NonNull String permissionGroupName) {
diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt
index 1fe1efc6a..3fc03e883 100644
--- a/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt
+++ b/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt
@@ -911,13 +911,12 @@ class AccessibilityJobService : JobService() {
override fun onCreate() {
super.onCreate()
- if (DEBUG) {
- Log.v(LOG_TAG, "accessibility privacy source job created.")
- }
+ Log.v(LOG_TAG, "accessibility privacy source job created.")
mSourceService = AccessibilitySourceService(this)
}
override fun onStartJob(params: JobParameters?): Boolean {
+ Log.v(LOG_TAG, "accessibility privacy source job started.")
synchronized(mLock) {
if (mCurrentJob != null) {
Log.v(LOG_TAG, "Accessibility privacy source job already running")
diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt
index 9c64a9882..e8d272465 100644
--- a/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt
+++ b/PermissionController/src/com/android/permissioncontroller/privacysources/NotificationListenerCheck.kt
@@ -90,8 +90,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
private val TAG = "NotificationListenerCheck"
-private const val DEBUG = false
-
+private const val DEBUG = true
const val SC_NLS_SOURCE_ID = "AndroidNotificationListener"
@VisibleForTesting const val SC_NLS_DISABLE_ACTION_ID = "disable_nls_component"
@@ -551,7 +550,7 @@ internal class NotificationListenerCheckInternal(
.setDeleteIntent(deletePendingIntent)
.setContentIntent(clickPendingIntent)
- if (appLabel.isNotEmpty()) {
+ if (appLabel != null && appLabel.isNotEmpty()) {
val appNameExtras = Bundle()
appNameExtras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appLabel.toString())
b.addExtras(appNameExtras)
@@ -846,7 +845,7 @@ class NotificationListenerCheckJobService : JobService() {
override fun onCreate() {
super.onCreate()
-
+ if (DEBUG) Log.d(TAG, "Nls privacy job created")
if (!checkNotificationListenerCheckEnabled(this)) {
// NotificationListenerCheck not enabled. End job.
return
@@ -871,6 +870,7 @@ class NotificationListenerCheckJobService : JobService() {
* @return `false` if another check is already running, or if SDK Check fails (below T)
*/
override fun onStartJob(params: JobParameters): Boolean {
+ if (DEBUG) Log.d(TAG, "Nls privacy job started")
if (!checkNotificationListenerCheckEnabled(this)) {
// NotificationListenerCheck not enabled. End job.
return false
diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/SafetyCenterReceiver.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/SafetyCenterReceiver.kt
index 89f197a05..aca344c6f 100644
--- a/PermissionController/src/com/android/permissioncontroller/privacysources/SafetyCenterReceiver.kt
+++ b/PermissionController/src/com/android/permissioncontroller/privacysources/SafetyCenterReceiver.kt
@@ -32,26 +32,34 @@ import androidx.annotation.RequiresApi
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.Constants.UNUSED_APPS_SAFETY_CENTER_SOURCE_ID
import com.android.permissioncontroller.PermissionControllerApplication
-import com.android.permissioncontroller.permission.service.LocationAccessCheck
+import com.android.permissioncontroller.permission.service.LocationAccessCheck.BG_LOCATION_SOURCE_ID
import com.android.permissioncontroller.permission.service.v33.SafetyCenterQsTileService
import com.android.permissioncontroller.permission.service.v33.SafetyCenterQsTileService.Companion.QS_TILE_COMPONENT_SETTING_FLAGS
import com.android.permissioncontroller.permission.utils.Utils
import com.android.permissioncontroller.privacysources.WorkPolicyInfo.Companion.WORK_POLICY_INFO_SOURCE_ID
+import com.android.permissioncontroller.privacysources.v34.AppDataSharingUpdatesPrivacySource
+import com.android.permissioncontroller.privacysources.v34.AppDataSharingUpdatesPrivacySource.Companion.APP_DATA_SHARING_UPDATES_SOURCE_ID
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.launch
private fun createMapOfSourceIdsToSources(context: Context): Map<String, PrivacySource> {
- if (!SdkLevel.isAtLeastT()) {
- return emptyMap()
+ val sourceMap: MutableMap<String, PrivacySource> = mutableMapOf()
+
+ if (SdkLevel.isAtLeastT()) {
+ sourceMap[SC_NLS_SOURCE_ID] = NotificationListenerPrivacySource()
+ sourceMap[WORK_POLICY_INFO_SOURCE_ID] = WorkPolicyInfo.create(context)
+ sourceMap[SC_ACCESSIBILITY_SOURCE_ID] = AccessibilitySourceService(context)
+ sourceMap[BG_LOCATION_SOURCE_ID] = LocationAccessPrivacySource()
+ sourceMap[UNUSED_APPS_SAFETY_CENTER_SOURCE_ID] = AutoRevokePrivacySource()
+ }
+
+ if (SdkLevel.isAtLeastU()) {
+ sourceMap[APP_DATA_SHARING_UPDATES_SOURCE_ID] = AppDataSharingUpdatesPrivacySource()
}
- return mapOf(
- SC_NLS_SOURCE_ID to NotificationListenerPrivacySource(),
- WORK_POLICY_INFO_SOURCE_ID to WorkPolicyInfo.create(context),
- SC_ACCESSIBILITY_SOURCE_ID to AccessibilitySourceService(context),
- LocationAccessCheck.BG_LOCATION_SOURCE_ID to LocationAccessPrivacySource(),
- UNUSED_APPS_SAFETY_CENTER_SOURCE_ID to AutoRevokePrivacySource())
+
+ return sourceMap
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING
index d6f74d31b..3d2d5f888 100644
--- a/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/privacysources/TEST_MAPPING
@@ -48,6 +48,16 @@
]
}
],
+ "mainline-presubmit": [
+ {
+ "name": "CtsSafetyCenterTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
"postsubmit": [
{
"name": "CtsPermissionTestCases",
diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/v34/AppDataSharingUpdatesPrivacySource.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/v34/AppDataSharingUpdatesPrivacySource.kt
new file mode 100644
index 000000000..783e4e1e2
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/privacysources/v34/AppDataSharingUpdatesPrivacySource.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.privacysources.v34
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_REVIEW_APP_DATA_SHARING_UPDATES
+import android.os.Build
+import android.safetycenter.SafetyCenterManager
+import android.safetycenter.SafetyEvent
+import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED
+import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED
+import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED
+import android.safetycenter.SafetySourceData
+import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED
+import android.safetycenter.SafetySourceStatus
+import androidx.annotation.RequiresApi
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.Utils
+import com.android.permissioncontroller.privacysources.PrivacySource
+import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent
+import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.EVENT_DEVICE_REBOOTED
+import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.EVENT_REFRESH_REQUESTED
+import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.UNKNOWN
+
+/**
+ * Privacy source providing the App Data Sharing Updates page entry to Safety Center.
+ *
+ * The content of the App Data Sharing Updates page is static, however the entry should only be
+ * displayed if the Safety Label Change Notification feature is enabled.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class AppDataSharingUpdatesPrivacySource : PrivacySource {
+
+ override val shouldProcessProfileRequest: Boolean = false
+
+ override fun safetyCenterEnabledChanged(context: Context, enabled: Boolean) {
+ // Do nothing.
+ }
+
+ override fun rescanAndPushSafetyCenterData(
+ context: Context,
+ intent: Intent,
+ refreshEvent: RefreshEvent
+ ) {
+ val safetyCenterManager: SafetyCenterManager =
+ Utils.getSystemServiceSafe(context, SafetyCenterManager::class.java)
+
+ val safetySourceData =
+ if (KotlinUtils.isSafetyLabelChangeNotificationsEnabled()) {
+ SafetySourceData.Builder()
+ .setStatus(
+ SafetySourceStatus.Builder(
+ context.getString(R.string.data_sharing_updates_title),
+ context.getString(R.string.data_sharing_updates_summary),
+ SEVERITY_LEVEL_UNSPECIFIED)
+ .setPendingIntent(
+ PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ Intent(ACTION_REVIEW_APP_DATA_SHARING_UPDATES),
+ FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE))
+ .build(),
+ )
+ .build()
+ } else {
+ null
+ }
+
+ safetyCenterManager.setSafetySourceData(
+ APP_DATA_SHARING_UPDATES_SOURCE_ID,
+ safetySourceData,
+ createSafetyEventForDataSharingUpdates(refreshEvent, intent))
+ }
+
+ private fun createSafetyEventForDataSharingUpdates(
+ refreshEvent: RefreshEvent,
+ intent: Intent
+ ): SafetyEvent {
+ return when (refreshEvent) {
+ EVENT_REFRESH_REQUESTED -> {
+ val refreshBroadcastId =
+ intent.getStringExtra(
+ SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID)
+ SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId(refreshBroadcastId)
+ .build()
+ }
+ EVENT_DEVICE_REBOOTED -> {
+ SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_REBOOTED).build()
+ }
+ UNKNOWN -> {
+ SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build()
+ }
+ }
+ }
+
+ /** Companion object for [AppDataSharingUpdatesPrivacySource]. */
+ companion object {
+ /** Source id for safety center source for app data sharing updates. */
+ const val APP_DATA_SHARING_UPDATES_SOURCE_ID = "AndroidPrivacyAppDataSharingUpdates"
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/Role.md b/PermissionController/src/com/android/permissioncontroller/role/Role.md
index 9e867e341..bde9f86f0 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/Role.md
+++ b/PermissionController/src/com/android/permissioncontroller/role/Role.md
@@ -61,8 +61,10 @@ is optional and defaults to `false`.
- `label`: The string resource for the label of the role, e.g. `@string/role_sms_label`, which says
"Default SMS app". For default apps, this string will appear in the default app detail page as the
title. This attribute is required if the role is `visible`.
-- `minSdkVersion`: The minimum SDK version for the role to be available, e.g. `31` for Android S.
-This attribute is optional and defaults to `Build.VERSION_CODES.BASE`.
+- `maxSdkVersion`: The maximum SDK version for the role to be available (inclusive), e.g. `31` for
+Android S. This attribute is optional and defaults to `Build.VERSION_CODES.CUR_DEVELOPMENT`.
+- `minSdkVersion`: The minimum SDK version for the role to be available (inclusive), e.g. `31` for
+Android S. This attribute is optional and defaults to `Build.VERSION_CODES.BASE`.
- `requestDescription`: The string resource for the description in the request role dialog, e.g.
`@string/role_sms_request_description`, which says "Gets access to contacts, SMS, phone". This
description should describe to the user the privileges that are going to be granted, and should not
@@ -86,10 +88,13 @@ defaults to `false`.
- `static`: Whether this role is static, i.e. the role will always be assigned to its default
holders. This attribute is optional and defaults to `false`.
- `systemOnly`: Whether this role only allows system apps to hold it. This attribute is optional and
-defaults to `false.
+defaults to `false`.
- `visible`: Whether this role is visible to users. If a role is invisible (a.k.a. hidden) to users,
users won't be able to find it in Settings, and apps won't be able to request it. The role can still
be managed by system APIs and shell command.
+- `uiBehavior`: Optional name of a [`RoleUiBehavior`](ui/behavior/RoleUiBehavior.java) class to
+control certain role UI behavior in Java code, e.g. `DialerRoleUiBehavior`. This can be useful
+when the XML syntax cannot express certain UI behavior specific to the role.
The following tags can be specified inside a `<role>` tag:
@@ -172,6 +177,7 @@ dumpsys role
You can also manage the role holders with `cmd role`:
```bash
+cmd role get-role-holders [--user USER_ID] ROLE
cmd role add-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]
cmd role remove-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]
cmd role clear-role-holders [--user USER_ID] ROLE [FLAGS]
diff --git a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING
index 43d15a49e..d7718a2f2 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING
@@ -8,5 +8,22 @@
}
]
}
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsRoleTestCases[com.google.android.permission.apex]",
+ "options": [
+ // TODO(b/238677748): These two tests currently fails on R base image
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList"
+ },
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/EncryptionUnawareConfirmationMixin.java b/PermissionController/src/com/android/permissioncontroller/role/model/EncryptionUnawareConfirmationMixin.java
index 3f1e7ed0c..567f1c713 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/EncryptionUnawareConfirmationMixin.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/model/EncryptionUnawareConfirmationMixin.java
@@ -25,6 +25,8 @@ import androidx.annotation.Nullable;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
/**
* Mixin for {@link RoleBehavior#getConfirmationMessage(Role, String, Context)}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/WaitForBroadcastIdle.kt b/PermissionController/src/com/android/permissioncontroller/role/model/RoleParserInitializer.java
index dd52cc855..b1c9e3e1d 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/WaitForBroadcastIdle.kt
+++ b/PermissionController/src/com/android/permissioncontroller/role/model/RoleParserInitializer.java
@@ -14,19 +14,22 @@
* limitations under the License.
*/
-package android.safetycenter.cts.testing
+package com.android.permissioncontroller.role.model;
-import android.Manifest.permission.DUMP
-import android.app.ActivityManager
-import android.content.Context
-import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
+import com.android.permissioncontroller.R;
+import com.android.role.controller.model.RoleParser;
-/** A class that allows waiting for the broadcast queue to be idle. */
-object WaitForBroadcastIdle {
+/**
+ * Initialize the function to retrieve the roles.xml resource from a context within
+ * PermissionController APK
+ */
+public class RoleParserInitializer {
- /** Waits for the broadcast queue to be idle. */
- fun Context.waitForBroadcastIdle() {
- val activityManager = getSystemService(ActivityManager::class.java)!!
- callWithShellPermissionIdentity(DUMP) { activityManager.waitForBroadcastIdle() }
+ /**
+ * Initialize the function to retrieve the roles.xml resource from a context within
+ * PermissionController APK
+ */
+ public static void initialize() {
+ RoleParser.sGetRolesXml = context -> context.getResources().getXml(R.xml.roles);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/model/VisibilityMixin.java b/PermissionController/src/com/android/permissioncontroller/role/model/VisibilityMixin.java
index 07afc6031..90cda72ca 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/model/VisibilityMixin.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/model/VisibilityMixin.java
@@ -23,6 +23,9 @@ import android.util.Log;
import androidx.annotation.NonNull;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.RoleBehavior;
+
/**
* Mixin for {@link RoleBehavior#isVisibleAsUser(Role, UserHandle, Context)} that returns whether
* the role should be visible from a corresponding boolean resource.
diff --git a/PermissionController/src/com/android/permissioncontroller/role/service/RoleControllerServiceImpl.java b/PermissionController/src/com/android/permissioncontroller/role/service/RoleControllerServiceImpl.java
index fbb9c2e2e..c1c7da46a 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/service/RoleControllerServiceImpl.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/service/RoleControllerServiceImpl.java
@@ -29,9 +29,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.permissioncontroller.permission.utils.CollectionUtils;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
import java.util.ArrayList;
import java.util.List;
@@ -428,8 +429,8 @@ public class RoleControllerServiceImpl extends RoleControllerService {
return false;
}
ApplicationInfo applicationInfo = PackageUtils.getApplicationInfo(packageName, this);
- if (applicationInfo == null || !role.isApplicationVisibleAsUser(applicationInfo,
- Process.myUserHandle(), this)) {
+ if (applicationInfo == null || !RoleUiBehaviorUtils.isApplicationVisibleAsUser(role,
+ applicationInfo, Process.myUserHandle(), this)) {
return false;
}
return true;
@@ -444,7 +445,8 @@ public class RoleControllerServiceImpl extends RoleControllerService {
if (!role.isAvailable(this)) {
return false;
}
- return role.isVisibleAsUser(Process.myUserHandle(), this);
+
+ return RoleUiBehaviorUtils.isVisibleAsUser(role, Process.myUserHandle(), this);
}
private static boolean checkFlags(int flags, int allowedFlags) {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/service/RoleSearchIndexablesProvider.java b/PermissionController/src/com/android/permissioncontroller/role/service/RoleSearchIndexablesProvider.java
index 7201acffc..1c845ee3a 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/service/RoleSearchIndexablesProvider.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/service/RoleSearchIndexablesProvider.java
@@ -27,8 +27,10 @@ import androidx.annotation.Nullable;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.service.BaseSearchIndexablesProvider;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
+import com.android.permissioncontroller.role.model.RoleParserInitializer;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
/**
* {@link android.provider.SearchIndexablesProvider} for roles.
@@ -41,6 +43,12 @@ public class RoleSearchIndexablesProvider extends BaseSearchIndexablesProvider {
public static final String ACTION_MANAGE_SPECIAL_APP_ACCESS =
"com.android.permissioncontroller.settingssearch.action.MANAGE_SPECIAL_APP_ACCESS";
+ @Override
+ public boolean onCreate() {
+ RoleParserInitializer.initialize();
+ return true;
+ }
+
@Nullable
@Override
public Cursor queryRawData(@Nullable String[] projection) {
@@ -53,7 +61,8 @@ public class RoleSearchIndexablesProvider extends BaseSearchIndexablesProvider {
long token = Binder.clearCallingIdentity();
try {
- if (!role.isAvailable(context) || !role.isVisible(context)) {
+ if (!role.isAvailable(context) || !RoleUiBehaviorUtils.isVisible(role,
+ context)) {
continue;
}
} finally {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppActivity.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppActivity.java
index 0ddb6c3ac..52471cb32 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppActivity.java
@@ -29,10 +29,11 @@ import androidx.fragment.app.Fragment;
import com.android.permissioncontroller.DeviceUtils;
import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
import com.android.permissioncontroller.role.ui.auto.AutoDefaultAppFragment;
import com.android.permissioncontroller.role.ui.handheld.HandheldDefaultAppFragment;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
/**
* Activity for a default app.
@@ -86,7 +87,8 @@ public class DefaultAppActivity extends SettingsActivity {
finish();
return;
}
- if (!role.isVisibleAsUser(user, this)) {
+
+ if (!RoleUiBehaviorUtils.isVisibleAsUser(role, user, this)) {
Log.e(LOG_TAG, "Role is invisible: " + roleName);
finish();
return;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java
index b4e4aaa80..06e5ed264 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppChildFragment.java
@@ -39,8 +39,9 @@ import androidx.preference.TwoStatePreference;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.Utils;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
import java.util.List;
import java.util.Objects;
@@ -208,7 +209,8 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
preference.setChecked(checked);
if (applicationInfo != null) {
- mRole.prepareApplicationPreferenceAsUser(preference, applicationInfo, mUser, context);
+ RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, preference,
+ applicationInfo, mUser, context);
}
preferenceScreen.addPreference(preference);
@@ -238,8 +240,9 @@ public class DefaultAppChildFragment<PF extends PreferenceFragmentCompat
mViewModel.setNoneDefaultApp();
} else {
String packageName = key;
- CharSequence confirmationMessage = mRole.getConfirmationMessage(packageName,
- requireContext());
+ CharSequence confirmationMessage =
+ RoleUiBehaviorUtils.getConfirmationMessage(mRole, packageName,
+ requireContext());
if (confirmationMessage != null) {
DefaultAppConfirmationDialogFragment.show(packageName, confirmationMessage, this);
} else {
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
index 752dfb9cc..3c8af1136 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java
@@ -40,8 +40,9 @@ import androidx.preference.PreferenceScreen;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.Utils;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
import java.util.List;
import java.util.Objects;
@@ -186,8 +187,8 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
preference.setIcon(Utils.getBadgedIcon(context, holderApplicationInfo));
preference.setSummary(Utils.getAppLabel(holderApplicationInfo, context));
}
- role.preparePreferenceAsUser((TwoTargetPreference) preference, user, context);
-
+ RoleUiBehaviorUtils.preparePreferenceAsUser(role, (TwoTargetPreference) preference,
+ user, context);
preferenceGroup.addPreference(preference);
}
}
@@ -198,7 +199,7 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat
Context context = requireContext();
Role role = Roles.get(context).get(roleName);
UserHandle user = preference.getExtras().getParcelable(Intent.EXTRA_USER);
- Intent intent = role.getManageIntentAsUser(user, context);
+ Intent intent = RoleUiBehaviorUtils.getManageIntentAsUser(role, user, context);
if (intent == null) {
intent = DefaultAppActivity.createIntent(roleName, user, context);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java
index 080652055..c89e1f71e 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppViewModel.java
@@ -30,7 +30,7 @@ import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
-import com.android.permissioncontroller.role.model.Role;
+import com.android.role.controller.model.Role;
import java.util.List;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java
index 0d5216991..3a4312c00 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleActivity.java
@@ -34,10 +34,11 @@ import androidx.fragment.app.FragmentActivity;
import com.android.permissioncontroller.PermissionControllerStatsLog;
import com.android.permissioncontroller.permission.utils.CollectionUtils;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
import com.android.permissioncontroller.role.model.UserDeniedManager;
import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
import java.util.List;
import java.util.Objects;
@@ -109,7 +110,7 @@ public class RequestRoleActivity extends FragmentActivity {
return;
}
- if (!role.isVisible(this)) {
+ if (!RoleUiBehaviorUtils.isVisible(role, this)) {
Log.e(LOG_TAG, "Role is invisible: " + mRoleName);
reportRequestResult(
PermissionControllerStatsLog.ROLE_REQUEST_RESULT_REPORTED__RESULT__IGNORED);
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java
index 091a71c7d..a5bd90dc2 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java
@@ -52,10 +52,10 @@ import com.android.permissioncontroller.PermissionControllerStatsLog;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.PackageRemovalMonitor;
import com.android.permissioncontroller.permission.utils.Utils;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
import com.android.permissioncontroller.role.model.UserDeniedManager;
import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
import java.util.ArrayList;
import java.util.List;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleViewModel.java
index d93e8f8d6..ae80f997a 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleViewModel.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleViewModel.java
@@ -23,7 +23,7 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
-import com.android.permissioncontroller.role.model.Role;
+import com.android.role.controller.model.Role;
/**
* {@link ViewModel} for a role request.
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleItem.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleItem.java
index 8bca5170c..dab709030 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleItem.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleItem.java
@@ -20,7 +20,7 @@ import android.content.pm.ApplicationInfo;
import androidx.annotation.NonNull;
-import com.android.permissioncontroller.role.model.Role;
+import com.android.role.controller.model.Role;
import java.util.List;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListLiveData.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListLiveData.java
index 9742ed923..b9011bd78 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListLiveData.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleListLiveData.java
@@ -29,9 +29,10 @@ import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import com.android.permissioncontroller.AsyncTaskLiveData;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
import java.util.ArrayList;
import java.util.List;
@@ -96,7 +97,7 @@ public class RoleListLiveData extends AsyncTaskLiveData<List<RoleItem>>
continue;
}
- if (!role.isVisibleAsUser(mUser, mContext)) {
+ if (!RoleUiBehaviorUtils.isVisibleAsUser(role, mUser, mContext)) {
continue;
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleLiveData.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleLiveData.java
index 31042f974..3ccb1d8bc 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/RoleLiveData.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RoleLiveData.java
@@ -29,8 +29,9 @@ import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import com.android.permissioncontroller.AsyncTaskLiveData;
-import com.android.permissioncontroller.role.model.Role;
import com.android.permissioncontroller.role.utils.PackageUtils;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
import java.util.ArrayList;
import java.util.List;
@@ -94,7 +95,8 @@ public class RoleLiveData extends AsyncTaskLiveData<List<Pair<ApplicationInfo, B
+ qualifyingPackageName);
continue;
}
- if (!mRole.isApplicationVisibleAsUser(qualifyingApplicationInfo, mUser, mContext)) {
+ if (!RoleUiBehaviorUtils.isApplicationVisibleAsUser(mRole, qualifyingApplicationInfo,
+ mUser, mContext)) {
continue;
}
boolean isHolderApplication = holderPackageNames.contains(qualifyingPackageName);
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/TwoTargetPreference.java b/PermissionController/src/com/android/permissioncontroller/role/ui/TwoTargetPreference.java
index a6a453aee..23044b833 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/TwoTargetPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/TwoTargetPreference.java
@@ -49,4 +49,7 @@ public interface TwoTargetPreference {
*/
void onSecondTargetClick(@NonNull TwoTargetPreference preference);
}
+
+ /** @see androidx.preference.Preference#setEnabled(boolean) */
+ void setEnabled(boolean enabled);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppFragment.java
index c86d48db0..dbd4c7c03 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/auto/AutoDefaultAppFragment.java
@@ -27,8 +27,8 @@ import androidx.preference.TwoStatePreference;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.auto.AutoSettingsFrameFragment;
-import com.android.permissioncontroller.role.model.Role;
import com.android.permissioncontroller.role.ui.DefaultAppChildFragment;
+import com.android.role.controller.model.Role;
/** Screen to pick a default app for a particular {@link Role}. */
public class AutoDefaultAppFragment extends AutoSettingsFrameFragment implements
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/AssistantRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/AssistantRoleUiBehavior.java
new file mode 100644
index 000000000..40bd7a33e
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/AssistantRoleUiBehavior.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui.behavior;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.role.model.VisibilityMixin;
+import com.android.role.controller.model.Role;
+
+/***
+ * Class for UI behavior of Assistant role
+ */
+public class AssistantRoleUiBehavior implements RoleUiBehavior {
+
+ @Override
+ public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ return VisibilityMixin.isVisible("config_showDefaultAssistant", context);
+ }
+
+ @Nullable
+ @Override
+ public Intent getManageIntentAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ boolean isAutomotive =
+ context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+
+ if (isAutomotive) {
+ return null;
+ }
+
+ return new Intent(Settings.ACTION_VOICE_INPUT_SETTINGS);
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
+ @NonNull Context context) {
+ return context.getString(R.string.assistant_confirmation_message);
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/BrowserRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/BrowserRoleUiBehavior.java
new file mode 100644
index 000000000..018b0db41
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/BrowserRoleUiBehavior.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui.behavior;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+
+import com.android.permissioncontroller.R;
+import com.android.role.controller.model.Role;
+
+/***
+ * Class for UI behavior of Browser role
+ */
+public class BrowserRoleUiBehavior implements RoleUiBehavior {
+
+ @Override
+ public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_showBrowserRole);
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/DialerRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/DialerRoleUiBehavior.java
new file mode 100644
index 000000000..8a5c8bdc7
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/DialerRoleUiBehavior.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui.behavior;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.UserHandle;
+import android.telecom.TelecomManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.role.model.EncryptionUnawareConfirmationMixin;
+import com.android.role.controller.model.Role;
+
+import java.util.Objects;
+
+/***
+ * Class for UI behavior of Dialer role
+ */
+public class DialerRoleUiBehavior implements RoleUiBehavior {
+
+ @Override
+ public void prepareApplicationPreferenceAsUser(@NonNull Role role,
+ @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
+ @NonNull UserHandle user, @NonNull Context context) {
+ RoleUiBehavior.super.prepareApplicationPreferenceAsUser(
+ role, preference, applicationInfo, user, context);
+
+ TelecomManager telecomManager = context.getSystemService(TelecomManager.class);
+ String systemPackageName = telecomManager.getSystemDialerPackage();
+ if (Objects.equals(applicationInfo.packageName, systemPackageName)) {
+ preference.setSummary(R.string.default_app_system_default);
+ } else {
+ preference.setSummary(null);
+ }
+ }
+
+ @Override
+ public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_showDialerRole);
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
+ @NonNull Context context) {
+ return EncryptionUnawareConfirmationMixin.getConfirmationMessage(role, packageName,
+ context);
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/EmergencyRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/EmergencyRoleUiBehavior.java
new file mode 100644
index 000000000..8a62ee7eb
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/EmergencyRoleUiBehavior.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui.behavior;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.permissioncontroller.role.model.EncryptionUnawareConfirmationMixin;
+import com.android.permissioncontroller.role.model.VisibilityMixin;
+import com.android.role.controller.model.Role;
+
+/***
+ * Class for UI behavior of Emergency role
+ */
+public class EmergencyRoleUiBehavior implements RoleUiBehavior {
+
+ @Override
+ public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ return VisibilityMixin.isVisible("config_showDefaultEmergency", context);
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
+ @NonNull Context context) {
+ return EncryptionUnawareConfirmationMixin.getConfirmationMessage(role, packageName,
+ context);
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java
new file mode 100644
index 000000000..36bbd1cb1
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui.behavior;
+
+import android.app.admin.DevicePolicyResources;
+import android.app.role.RoleManager;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.utils.CollectionUtils;
+import com.android.permissioncontroller.permission.utils.Utils;
+import com.android.permissioncontroller.role.model.VisibilityMixin;
+import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.behavior.HomeRoleBehavior;
+import com.android.role.controller.model.Role;
+
+/***
+ * Class for UI behavior of Home role
+ */
+public class HomeRoleUiBehavior implements RoleUiBehavior {
+
+ private static final String LOG_TAG = HomeRoleUiBehavior.class.getSimpleName();
+
+ @Override
+ public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ return VisibilityMixin.isVisible("config_showDefaultHome", context);
+ }
+
+ @Override
+ public void preparePreferenceAsUser(@NonNull Role role, @NonNull TwoTargetPreference preference,
+ @NonNull UserHandle user, @NonNull Context context) {
+ RoleUiBehavior.super.preparePreferenceAsUser(role, preference, user, context);
+
+ TwoTargetPreference.OnSecondTargetClickListener listener = null;
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ String packageName = CollectionUtils.firstOrNull(roleManager.getRoleHoldersAsUser(
+ role.getName(), user));
+ if (packageName != null) {
+ Intent intent = new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
+ .setPackage(packageName)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ PackageManager userPackageManager = UserUtils.getUserContext(context, user)
+ .getPackageManager();
+ ResolveInfo resolveInfo = userPackageManager.resolveActivity(intent, 0);
+ if (resolveInfo != null && resolveInfo.activityInfo != null
+ && resolveInfo.activityInfo.exported) {
+ listener = preference2 -> {
+ try {
+ context.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Log.e(LOG_TAG, "Cannot start activity for home app preferences", e);
+ }
+ };
+ }
+ }
+ preference.setOnSecondTargetClickListener(listener);
+ }
+
+ @Override
+ public boolean isApplicationVisibleAsUser(@NonNull Role role,
+ @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user,
+ @NonNull Context context) {
+ // Home is not available for work profile, so we can just use the current user.
+ return !HomeRoleBehavior.isSettingsApplication(applicationInfo, context);
+ }
+
+ @Override
+ public void prepareApplicationPreferenceAsUser(@NonNull Role role,
+ @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
+ @NonNull UserHandle user, @NonNull Context context) {
+ RoleUiBehavior.super.prepareApplicationPreferenceAsUser(
+ role, preference, applicationInfo, user, context);
+
+ boolean missingWorkProfileSupport = isMissingWorkProfileSupport(applicationInfo, context);
+ if (preference.isEnabled()) {
+ preference.setEnabled(!missingWorkProfileSupport);
+ }
+ preference.setSummary(missingWorkProfileSupport ? Utils.getEnterpriseString(context,
+ DevicePolicyResources.Strings.DefaultAppSettings
+ .HOME_MISSING_WORK_PROFILE_SUPPORT_MESSAGE,
+ R.string.home_missing_work_profile_support) : null);
+ }
+
+ private boolean isMissingWorkProfileSupport(@NonNull ApplicationInfo applicationInfo,
+ @NonNull Context context) {
+ boolean hasWorkProfile = UserUtils.getWorkProfile(context) != null;
+ if (!hasWorkProfile) {
+ return false;
+ }
+ boolean isWorkProfileSupported = applicationInfo.targetSdkVersion
+ >= Build.VERSION_CODES.LOLLIPOP;
+ return !isWorkProfileSupported;
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java
new file mode 100644
index 000000000..13343e926
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui.behavior;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+import com.android.modules.utils.build.SdkLevel;
+import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.role.controller.model.Role;
+
+/***
+ * Interface for UI behavior for roles
+ */
+public interface RoleUiBehavior {
+
+ /**
+ * Check whether this role should be visible to user.
+ *
+ * @param role the role to check for
+ * @param user the user to check for
+ * @param context the `Context` to retrieve system services
+ *
+ * @return whether this role should be visible to user
+ */
+ default boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ return true;
+ }
+
+ /**
+ * Get the {@link Intent} to manage this role, or {@code null} to use the default UI.
+ *
+ * @param role the role to get the intent for
+ * @param user the user to manage this role for
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return the {@link Intent} to manage this role, or {@code null} to use the default UI.
+ */
+ @Nullable
+ default Intent getManageIntentAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ return null;
+ }
+
+ /**
+ * Prepare a {@link Preference} for this role.
+ *
+ * @param role the role to prepare the preference for
+ * @param preference the {@link Preference} for this role
+ * @param user the user for this role
+ * @param context the {@code Context} to retrieve system services
+ */
+ default void preparePreferenceAsUser(@NonNull Role role,
+ @NonNull TwoTargetPreference preference,
+ @NonNull UserHandle user,
+ @NonNull Context context) {
+ if (SdkLevel.isAtLeastU() && role.isExclusive()) {
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ preference.setEnabled(!userManager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_CONFIG_DEFAULT_APPS, user));
+ }
+ }
+
+ /**
+ * Check whether a qualifying application should be visible to user.
+ *
+ * @param applicationInfo the {@link ApplicationInfo} for the application
+ * @param user the user for the application
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return whether the qualifying application should be visible to user
+ */
+ default boolean isApplicationVisibleAsUser(@NonNull Role role,
+ @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user,
+ @NonNull Context context) {
+ return true;
+ }
+
+ /**
+ * Prepare a {@link Preference} for this role.
+ *
+ * @param role the role to prepare the preference for
+ * @param preference the {@link Preference} for this role
+ * @param user the user for this role
+ * @param context the {@code Context} to retrieve system services
+ */
+ default void prepareApplicationPreferenceAsUser(@NonNull Role role,
+ @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
+ @NonNull UserHandle user, @NonNull Context context) {
+ if (SdkLevel.isAtLeastU() && role.isExclusive()) {
+ final UserManager userManager = context.getSystemService(UserManager.class);
+ preference.setEnabled(!userManager.hasUserRestrictionForUser(
+ UserManager.DISALLOW_CONFIG_DEFAULT_APPS, user));
+ }
+ }
+
+ /**
+ * Get the confirmation message for adding an application as a holder of this role.
+ *
+ * @param role the role to get confirmation message for
+ * @param packageName the package name of the application to get confirmation message for
+ * @param context the {@code Context} to retrieve system services
+ *
+ * @return the confirmation message, or {@code null} if no confirmation is needed
+ */
+ @Nullable
+ default CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
+ @NonNull Context context) {
+ return null;
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/SmsRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/SmsRoleUiBehavior.java
new file mode 100644
index 000000000..9fc9be3d4
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/SmsRoleUiBehavior.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.ui.behavior;
+
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.role.model.EncryptionUnawareConfirmationMixin;
+import com.android.role.controller.model.Role;
+
+/***
+ * Class for UI behavior of SMS role
+ */
+public class SmsRoleUiBehavior implements RoleUiBehavior {
+
+ @Override
+ public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ return context.getResources().getBoolean(R.bool.config_showSmsRole);
+ }
+
+ @Nullable
+ @Override
+ public CharSequence getConfirmationMessage(@NonNull Role role, @NonNull String packageName,
+ @NonNull Context context) {
+ return EncryptionUnawareConfirmationMixin.getConfirmationMessage(role, packageName,
+ context);
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessActivity.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessActivity.java
index 2328bb94e..6ed105149 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessActivity.java
@@ -27,11 +27,12 @@ import androidx.fragment.app.Fragment;
import com.android.permissioncontroller.DeviceUtils;
import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
import com.android.permissioncontroller.role.ui.SettingsActivity;
import com.android.permissioncontroller.role.ui.auto.AutoSpecialAppAccessFragment;
import com.android.permissioncontroller.role.ui.specialappaccess.handheld.HandheldSpecialAppAccessFragment;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
/**
* Activity for a special app access.
@@ -75,7 +76,8 @@ public class SpecialAppAccessActivity extends SettingsActivity {
finish();
return;
}
- if (!role.isVisible(this)) {
+
+ if (!RoleUiBehaviorUtils.isVisible(role, this)) {
Log.e(LOG_TAG, "Role is invisible: " + roleName);
finish();
return;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java
index 66e1e53ff..d75747b52 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessChildFragment.java
@@ -36,9 +36,10 @@ import androidx.preference.PreferenceScreen;
import androidx.preference.TwoStatePreference;
import com.android.permissioncontroller.permission.utils.Utils;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
import java.util.List;
@@ -157,9 +158,8 @@ public class SpecialAppAccessChildFragment<PF extends PreferenceFragmentCompat
preference.setChecked(isHolderPackage);
UserHandle user = UserHandle.getUserHandleForUid(qualifyingApplicationInfo.uid);
- mRole.prepareApplicationPreferenceAsUser(preference, qualifyingApplicationInfo, user,
- context);
-
+ RoleUiBehaviorUtils.prepareApplicationPreferenceAsUser(mRole, preference,
+ qualifyingApplicationInfo, user, context);
preferenceScreen.addPreference(preference);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java
index 52b7aa08d..ec4de84e1 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java
@@ -32,10 +32,11 @@ import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceScreen;
-import com.android.permissioncontroller.role.model.Role;
-import com.android.permissioncontroller.role.model.Roles;
import com.android.permissioncontroller.role.ui.RoleItem;
import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.permissioncontroller.role.utils.RoleUiBehaviorUtils;
+import com.android.role.controller.model.Role;
+import com.android.role.controller.model.Roles;
import java.util.List;
@@ -110,10 +111,9 @@ public class SpecialAppAccessListChildFragment<PF extends PreferenceFragmentComp
preference.setPersistent(false);
preference.setOnPreferenceClickListener(this);
}
-
- role.preparePreferenceAsUser((TwoTargetPreference) preference, Process.myUserHandle(),
+ RoleUiBehaviorUtils.preparePreferenceAsUser(role, (TwoTargetPreference) preference,
+ Process.myUserHandle(),
context);
-
preferenceScreen.addPreference(preference);
}
@@ -126,7 +126,7 @@ public class SpecialAppAccessListChildFragment<PF extends PreferenceFragmentComp
Context context = requireContext();
Role role = Roles.get(context).get(roleName);
UserHandle user = Process.myUserHandle();
- Intent intent = role.getManageIntentAsUser(user, context);
+ Intent intent = RoleUiBehaviorUtils.getManageIntentAsUser(role, user, context);
if (intent == null) {
intent = SpecialAppAccessActivity.createIntent(roleName, context);
}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java
index 03aa5407e..0cc00abc1 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessViewModel.java
@@ -32,11 +32,11 @@ import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
-import com.android.permissioncontroller.role.model.Role;
import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData;
import com.android.permissioncontroller.role.ui.RoleLiveData;
import com.android.permissioncontroller.role.ui.RoleSortFunction;
import com.android.permissioncontroller.role.utils.UserUtils;
+import com.android.role.controller.model.Role;
import java.util.List;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/utils/PackageUtils.java b/PermissionController/src/com/android/permissioncontroller/role/utils/PackageUtils.java
index 67d9ece44..9aaa07ee0 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/utils/PackageUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/utils/PackageUtils.java
@@ -18,7 +18,6 @@ package com.android.permissioncontroller.role.utils;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.UserHandle;
@@ -33,40 +32,6 @@ public final class PackageUtils {
private PackageUtils() {}
/**
- * Retrieve the {@link PackageInfo} of an application.
- *
- * @param packageName the package name of the application
- * @param extraFlags the extra flags to pass to {@link PackageManager#getPackageInfo(String,
- * int)}
- * @param context the {@code Context} to retrieve system services
- *
- * @return the {@link PackageInfo} of the application, or {@code null} if not found
- */
- @Nullable
- public static PackageInfo getPackageInfo(@NonNull String packageName, int extraFlags,
- @NonNull Context context) {
- PackageManager packageManager = context.getPackageManager();
- try {
- return packageManager.getPackageInfo(packageName, PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | extraFlags);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- /**
- * Retrieve if a package is a system package.
- *
- * @param packageName the name of the package
- * @param context the {@code Context} to retrieve system services
- *
- * @return whether the package is a system package
- */
- public static boolean isSystemPackage(@NonNull String packageName, @NonNull Context context) {
- return getPackageInfo(packageName, PackageManager.MATCH_SYSTEM_ONLY, context) != null;
- }
-
- /**
* Retrieve the {@link ApplicationInfo} of an application.
*
* @param packageName the package name of the application
diff --git a/PermissionController/src/com/android/permissioncontroller/role/utils/RoleManagerCompat.java b/PermissionController/src/com/android/permissioncontroller/role/utils/RoleManagerCompat.java
deleted file mode 100644
index 47f96adfb..000000000
--- a/PermissionController/src/com/android/permissioncontroller/role/utils/RoleManagerCompat.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.permissioncontroller.role.utils;
-
-import android.app.role.RoleManager;
-
-import androidx.annotation.NonNull;
-
-import com.android.modules.utils.build.SdkLevel;
-
-/**
- * Helper for accessing features in {@link RoleManager}.
- */
-public class RoleManagerCompat {
-
- private RoleManagerCompat() {}
-
- /**
- * @see RoleManager#isBypassingRoleQualification()
- */
- public static boolean isBypassingRoleQualification(@NonNull RoleManager roleManager) {
- if (SdkLevel.isAtLeastS()) {
- return roleManager.isBypassingRoleQualification();
- } else {
- return false;
- }
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java b/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java
new file mode 100644
index 000000000..e60bc6d76
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.role.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+
+import com.android.permissioncontroller.role.ui.TwoTargetPreference;
+import com.android.permissioncontroller.role.ui.behavior.RoleUiBehavior;
+import com.android.role.controller.model.Role;
+
+/**
+ * Utility methods for Role UI behavior
+ */
+public final class RoleUiBehaviorUtils {
+
+ private static final String LOG_TAG = RoleUiBehaviorUtils.class.getSimpleName();
+
+ /**
+ * Get the role ui behavior if available
+ */
+ @Nullable
+ private static RoleUiBehavior getUiBehavior(@NonNull Role role) {
+ String uiBehaviorName = role.getUiBehaviorName();
+ if (uiBehaviorName == null) {
+ return null;
+ }
+ RoleUiBehavior uiBehavior;
+ String uiBehaviorClassName = RoleUiBehavior.class.getPackage().getName() + '.'
+ + uiBehaviorName;
+ try {
+ uiBehavior = (RoleUiBehavior) Class.forName(uiBehaviorClassName).newInstance();
+ } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
+ Log.e(LOG_TAG, "Unable to instantiate UI behavior: " + uiBehaviorClassName, e);
+ return null;
+ }
+ return uiBehavior;
+ }
+
+ /**
+ * @see RoleUiBehavior#isVisibleAsUser
+ */
+ public static boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ RoleUiBehavior uiBehavior = getUiBehavior(role);
+ if (uiBehavior == null) {
+ return role.isVisible();
+ }
+ return role.isVisible() && uiBehavior.isVisibleAsUser(role, user, context);
+ }
+
+ /**
+ * Check whether this role should be visible to user, for current user.
+ *
+ * @param context the `Context` to retrieve system services
+ *
+ * @return whether this role should be visible to user.
+ */
+ public static boolean isVisible(@NonNull Role role, @NonNull Context context) {
+ return isVisibleAsUser(role, Process.myUserHandle(), context);
+ }
+
+
+ /**
+ * @see RoleUiBehavior#getManageIntentAsUser
+ */
+ @Nullable
+ public static Intent getManageIntentAsUser(@NonNull Role role, @NonNull UserHandle user,
+ @NonNull Context context) {
+ RoleUiBehavior uiBehavior = getUiBehavior(role);
+ if (uiBehavior == null) {
+ return null;
+ }
+ return uiBehavior.getManageIntentAsUser(role, user, context);
+ }
+
+ /**
+ * @see RoleUiBehavior#preparePreferenceAsUser
+ */
+ public static void preparePreferenceAsUser(@NonNull Role role,
+ @NonNull TwoTargetPreference preference, @NonNull UserHandle user,
+ @NonNull Context context) {
+ RoleUiBehavior uiBehavior = getUiBehavior(role);
+ if (uiBehavior == null) {
+ return;
+ }
+ uiBehavior.preparePreferenceAsUser(role, preference, user, context);
+ }
+
+ /**
+ * @see RoleUiBehavior#isApplicationVisibleAsUser
+ */
+ public static boolean isApplicationVisibleAsUser(@NonNull Role role,
+ @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user,
+ @NonNull Context context) {
+ RoleUiBehavior uiBehavior = getUiBehavior(role);
+ if (uiBehavior == null) {
+ return true;
+ }
+ return uiBehavior.isApplicationVisibleAsUser(role, applicationInfo, user, context);
+ }
+
+ /**
+ * @see RoleUiBehavior#prepareApplicationPreferenceAsUser
+ */
+ public static void prepareApplicationPreferenceAsUser(@NonNull Role role,
+ @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo,
+ @NonNull UserHandle user, @NonNull Context context) {
+ RoleUiBehavior uiBehavior = getUiBehavior(role);
+ if (uiBehavior == null) {
+ return;
+ }
+ uiBehavior.prepareApplicationPreferenceAsUser(role, preference, applicationInfo, user,
+ context);
+ }
+
+ /**
+ * @see RoleUiBehavior#getConfirmationMessage
+ */
+ @Nullable
+ public static CharSequence getConfirmationMessage(@NonNull Role role,
+ @NonNull String packageName, @NonNull Context context) {
+ RoleUiBehavior uiBehavior = getUiBehavior(role);
+ if (uiBehavior == null) {
+ return null;
+ }
+ return uiBehavior.getConfirmationMessage(role, packageName, context);
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java b/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java
index cd7a6b8a5..e89470ff6 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java
+++ b/PermissionController/src/com/android/permissioncontroller/role/utils/UserUtils.java
@@ -39,16 +39,41 @@ public class UserUtils {
/**
* Check whether a user is a profile.
*
- * @param user the user to check
+ * @param user the user to check
* @param context the {@code Context} to retrieve system services
- *
* @return whether the user is a profile
*/
public static boolean isProfile(@NonNull UserHandle user, @NonNull Context context) {
+ return isManagedProfile(user, context) || isCloneProfile(user, context);
+ }
+
+ /**
+ * Check whether a user is a managed profile.
+ *
+ * @param user the user to check
+ * @param context the {@code Context} to retrieve system services
+ * @return whether the user is a managed profile
+ */
+ public static boolean isManagedProfile(@NonNull UserHandle user, @NonNull Context context) {
Context userContext = getUserContext(context, user);
UserManager userUserManager = userContext.getSystemService(UserManager.class);
- return userUserManager.isManagedProfile(user.getIdentifier()) || (
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && userUserManager.isCloneProfile());
+ return userUserManager.isManagedProfile(user.getIdentifier());
+ }
+
+ /**
+ * Check whether a user is a clone profile.
+ *
+ * @param user the user to check
+ * @param context the {@code Context} to retrieve system services
+ * @return whether the user is a clone profile
+ */
+ public static boolean isCloneProfile(@NonNull UserHandle user, @NonNull Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
+ return false;
+ }
+ Context userContext = getUserContext(context, user);
+ UserManager userUserManager = userContext.getSystemService(UserManager.class);
+ return userUserManager.isCloneProfile();
}
/**
@@ -92,11 +117,7 @@ public class UserUtils {
if (Process.myUserHandle().equals(user)) {
return context;
} else {
- try {
- return context.createPackageContextAsUser(context.getPackageName(), 0, user);
- } catch (PackageManager.NameNotFoundException doesNotHappen) {
- throw new IllegalStateException(doesNotHappen);
- }
+ return context.createContextAsUser(user, 0);
}
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/safetycenter/TEST_MAPPING
index e2eb5ef5d..c702ee852 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/TEST_MAPPING
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/TEST_MAPPING
@@ -24,5 +24,15 @@
{
"name": "SafetyCenterFunctionalTestCases"
}
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsSafetyCenterTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
]
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java
index eb4a84a14..df1aed7a2 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterBackgroundRefreshJobService.java
@@ -19,12 +19,11 @@ package com.android.permissioncontroller.safetycenter.service;
import static android.app.job.JobScheduler.RESULT_SUCCESS;
import static android.content.Intent.ACTION_BOOT_COMPLETED;
import static android.safetycenter.SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED;
-import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_REBOOT;
+import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER;
+import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PERIODIC;
import static com.android.permissioncontroller.Constants.SAFETY_CENTER_BACKGROUND_REFRESH_JOB_ID;
-import static java.util.Objects.requireNonNull;
-
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
@@ -39,62 +38,122 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.modules.utils.build.SdkLevel;
+
/**
- * Uses {@link android.app.job.JobScheduler} to schedule one-off calls to {@link
- * SafetyCenterManager#refreshSafetySources} after boot completed and after safety center is
- * enabled.
+ * Uses {@link android.app.job.JobScheduler} to schedule periodic calls to {@link
+ * SafetyCenterManager#refreshSafetySources} after boot completed if safety center is already
+ * enabled, or after safety center is enabled otherwise.
*
- * <p>The job waits until the device is in idle maintenance mode to minimize impact on system
- * health.
+ * <p>The job waits until the device is in idle mode to minimize impact on system health.
*/
// TODO(b/243493200): Add tests
-// TODO(b/243537828): Consider disabling this during other tests in case it makes them flakey
public final class SafetyCenterBackgroundRefreshJobService extends JobService {
private static final String TAG = "SafetyCenterBackgroundR";
+ /** Schedules a periodic background refresh. */
+ public static final class SetupSafetyCenterBackgroundRefreshReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(@NonNull Context context, @NonNull Intent intent) {
+ schedulePeriodicBackgroundRefresh(context, intent.getAction());
+ }
+ }
+
/**
- * Schedules a one-off call to {@link SafetyCenterManager#refreshSafetySources} to be run when
- * the device is idle.
+ * Schedules a periodic call to {@link SafetyCenterManager#refreshSafetySources} to be run when
+ * the device is idle, after either {@link android.content.Intent#ACTION_BOOT_COMPLETED} or
+ * {@link android.safetycenter.SafetyCenterManager#ACTION_SAFETY_CENTER_ENABLED_CHANGED}.
+ *
+ * <p>The {@link SafetyCenterManager#isSafetyCenterEnabled} check ensures that jobs are never
+ * scheduled if SafetyCenter is disabled, we check again in {@link
+ * SafetyCenterBackgroundRefreshJobService#onStartJob} in case SafetyCenter becomes disabled
+ * later.
+ *
+ * <p>{@link SafetyCenterJobServiceFlags#areBackgroundRefreshesEnabled} is only checked in
+ * {@link SafetyCenterBackgroundRefreshJobService#onStartJob} as we do not receive a new
+ * broadcast if this flag gets enabled.
*/
- public static void scheduleOneOffBackgroundRefresh(
+ private static void schedulePeriodicBackgroundRefresh(
@NonNull Context context, @Nullable String actionString) {
- if (!(ACTION_BOOT_COMPLETED.equals(actionString)
- || ACTION_SAFETY_CENTER_ENABLED_CHANGED.equals(actionString))) {
+ if (!isActionStringValid(actionString)) {
+ Log.v(TAG, "Ignoring a " + actionString + " broadcast.");
return;
}
SafetyCenterManager safetyCenterManager =
- requireNonNull(context.getSystemService(SafetyCenterManager.class));
+ context.getSystemService(SafetyCenterManager.class);
+ if (safetyCenterManager == null) {
+ Log.w(TAG, "SafetyCenterManager is null, cannot schedule background refresh.");
+ return;
+ }
+
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler == null) {
+ Log.w(TAG, "JobScheduler is null, cannot schedule background refresh.");
+ return;
+ }
+
if (!safetyCenterManager.isSafetyCenterEnabled()) {
+ Log.v(
+ TAG,
+ "Received a "
+ + actionString
+ + " broadcast, but safety center is currently disabled. Cancelling any"
+ + " existing job.");
+ jobScheduler.cancel(SAFETY_CENTER_BACKGROUND_REFRESH_JOB_ID);
return;
}
- Log.v(TAG, "Scheduling a one-off background refresh.");
- JobScheduler jobScheduler = requireNonNull(context.getSystemService(JobScheduler.class));
- JobInfo.Builder builder =
- (new JobInfo.Builder(
+ JobInfo jobInfo =
+ new JobInfo.Builder(
SAFETY_CENTER_BACKGROUND_REFRESH_JOB_ID,
new ComponentName(
- context, SafetyCenterBackgroundRefreshJobService.class)))
- .setRequiresDeviceIdle(true);
- int scheduleResult = jobScheduler.schedule(builder.build());
+ context, SafetyCenterBackgroundRefreshJobService.class))
+ .setRequiresDeviceIdle(true)
+ .setRequiresCharging(
+ SafetyCenterJobServiceFlags.getBackgroundRefreshRequiresCharging())
+ .setPeriodic(
+ SafetyCenterJobServiceFlags.getPeriodicBackgroundRefreshInterval()
+ .toMillis())
+ .build();
+
+ Log.v(
+ TAG,
+ "Scheduling a periodic background refresh with "
+ + ", interval="
+ + jobInfo.getIntervalMillis()
+ + "requires charging="
+ + jobInfo.isRequireCharging());
+
+ int scheduleResult = jobScheduler.schedule(jobInfo);
if (scheduleResult != RESULT_SUCCESS) {
- Log.e(TAG, "Could not schedule background refresh, scheduleResult=" + scheduleResult);
+ Log.e(
+ TAG,
+ "Could not schedule the background refresh job, scheduleResult="
+ + scheduleResult);
}
}
@Override
public boolean onStartJob(@NonNull JobParameters params) {
// background thread not required, PC APK makes all API calls in main thread
- Log.v(TAG, "Background refresh job has started.");
- SafetyCenterManager safetyCenterManager =
- requireNonNull(this.getSystemService(SafetyCenterManager.class));
- if (safetyCenterManager.isSafetyCenterEnabled()) {
- // TODO(b/243523521): Use the correct refresh reason depending on which intent the
- // receiver receives
- safetyCenterManager.refreshSafetySources(REFRESH_REASON_DEVICE_REBOOT);
+ if (!SafetyCenterJobServiceFlags.areBackgroundRefreshesEnabled()) {
+ Log.v(TAG, "Background refreshes are not enabled, skipping job.");
+ return false; // job is no longer running
+ }
+ SafetyCenterManager safetyCenterManager = this.getSystemService(SafetyCenterManager.class);
+ if (safetyCenterManager == null) {
+ Log.w(TAG, "Safety center manager is null, skipping job.");
+ return false; // job is no longer running
}
+ if (!safetyCenterManager.isSafetyCenterEnabled()) {
+ Log.v(TAG, "Safety center is not enabled, skipping job.");
+ return false; // job is no longer running
+ }
+
+ Log.v(TAG, "Background refresh job has started.");
+ safetyCenterManager.refreshSafetySources(getRefreshReason());
return false; // job is no longer running
}
@@ -103,13 +162,15 @@ public final class SafetyCenterBackgroundRefreshJobService extends JobService {
return false; // never want job to be rescheduled
}
- /**
- * Schedules a background refresh on boot completed and when safety center is enabled.
- */
- public static final class SetupSafetyCenterBackgroundRefreshReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(@NonNull Context context, @NonNull Intent intent) {
- scheduleOneOffBackgroundRefresh(context, intent.getAction());
+ private static boolean isActionStringValid(@Nullable String actionString) {
+ return ACTION_BOOT_COMPLETED.equals(actionString)
+ || ACTION_SAFETY_CENTER_ENABLED_CHANGED.equals(actionString);
+ }
+
+ private static int getRefreshReason() {
+ if (SdkLevel.isAtLeastU()) {
+ return REFRESH_REASON_PERIODIC;
}
+ return REFRESH_REASON_OTHER;
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterJobServiceFlags.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterJobServiceFlags.java
new file mode 100644
index 000000000..bdca4d77d
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/service/SafetyCenterJobServiceFlags.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.safetycenter.service;
+
+import android.app.job.JobService;
+import android.provider.DeviceConfig;
+
+import java.time.Duration;
+
+/** A class so that the Safety Center {@link JobService} can access {@link DeviceConfig} flags. */
+public class SafetyCenterJobServiceFlags {
+ private static final Duration DEFAULT_PERIODIC_BACKGROUND_REFRESH_INTERVAL = Duration.ofDays(1);
+ private static final String PROPERTY_BACKGROUND_REFRESH_IS_ENABLED =
+ "safety_center_background_refresh_is_enabled";
+ private static final String PROPERTY_BACKGROUND_REFRESH_REQUIRES_CHARGING =
+ "safety_center_background_requires_charging";
+ private static final String PROPERTY_PERIODIC_BACKGROUND_REFRESH_INTERVAL_MILLIS =
+ "safety_center_periodic_background_interval_millis";
+
+ /** Returns whether background refreshes should be enabled. */
+ static boolean areBackgroundRefreshesEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_BACKGROUND_REFRESH_IS_ENABLED, true);
+ }
+
+ /**
+ * Returns the interval that should be used when scheduling periodic background refresh jobs.
+ */
+ static Duration getPeriodicBackgroundRefreshInterval() {
+ return Duration.ofMillis(
+ DeviceConfig.getLong(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_PERIODIC_BACKGROUND_REFRESH_INTERVAL_MILLIS,
+ DEFAULT_PERIODIC_BACKGROUND_REFRESH_INTERVAL.toMillis()));
+ }
+
+ /**
+ * Returns whether we should constrain background refresh jobs to only run when the device is
+ * charging.
+ */
+ static boolean getBackgroundRefreshRequiresCharging() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_PRIVACY,
+ PROPERTY_BACKGROUND_REFRESH_REQUIRES_CHARGING,
+ true);
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
index c5b52bd5a..5621a2600 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
@@ -47,6 +47,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
+import com.android.modules.utils.build.SdkLevel;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel;
import com.android.safetycenter.internaldata.SafetyCenterIds;
@@ -103,9 +104,19 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
((TextView) holder.findViewById(R.id.issue_card_title)).setText(mIssue.getTitle());
((TextView) holder.findViewById(R.id.issue_card_summary)).setText(mIssue.getSummary());
+ CharSequence attributionTitle = SdkLevel.isAtLeastU() ? mIssue.getAttributionTitle() : null;
+ TextView attributionTitleTextView =
+ (TextView) holder.findViewById(R.id.issue_card_attribution_title);
+ if (TextUtils.isEmpty(attributionTitle)) {
+ attributionTitleTextView.setVisibility(View.GONE);
+ } else {
+ attributionTitleTextView.setText(attributionTitle);
+ attributionTitleTextView.setVisibility(View.VISIBLE);
+ }
CharSequence subtitle = mIssue.getSubtitle();
TextView subtitleTextView = (TextView) holder.findViewById(R.id.issue_card_subtitle);
CharSequence contentDescription;
+ // TODO(b/257972736): Add a11y support for attribution title.
if (TextUtils.isEmpty(subtitle)) {
subtitleTextView.setVisibility(View.GONE);
contentDescription =
@@ -252,8 +263,7 @@ public class IssueCardPreference extends Preference implements ComparablePrefere
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
SafetyCenterViewModel safetyCenterViewModel =
- ((SafetyCenterDashboardFragment) requireParentFragment())
- .getSafetyCenterViewModel();
+ ((SafetyCenterFragment) requireParentFragment()).getSafetyCenterViewModel();
SafetyCenterIssue issue =
requireNonNull(
requireArguments().getParcelable(ISSUE_KEY, SafetyCenterIssue.class));
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PendingIntentSender.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PendingIntentSender.kt
index f32394302..b1151ac57 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PendingIntentSender.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PendingIntentSender.kt
@@ -18,6 +18,10 @@ package com.android.permissioncontroller.safetycenter.ui
import android.app.ActivityOptions
import android.app.PendingIntent
+import android.os.Build.VERSION_CODES.TIRAMISU
+import androidx.annotation.RequiresApi
+import androidx.fragment.app.FragmentActivity
+import com.android.safetycenter.internaldata.SafetyCenterIds
/** An object which sends pendingIntents, in a proper task, if needed. */
object PendingIntentSender {
@@ -36,4 +40,22 @@ object PendingIntentSender {
pi.send()
}
}
+
+ /**
+ * Gets the current task ID for sending pending intents in a fragment
+ *
+ * @param entryId identifies an entry on the Safety Center page
+ * @param sameTaskSourceIds list of safety source IDs to show in the same task as Safety Center
+ * @param activity represents the parent activity of the fragment
+ */
+ @JvmStatic
+ @RequiresApi(TIRAMISU)
+ fun getTaskIdForEntry(
+ entryId: String,
+ sameTaskSourceIds: List<String>,
+ activity: FragmentActivity
+ ): Int? {
+ val sourceId: String = SafetyCenterIds.entryIdFromString(entryId).getSafetySourceId()
+ return if (sameTaskSourceIds.contains(sourceId)) activity.getTaskId() else null
+ }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
index c9024cd30..bbac6494b 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
@@ -15,8 +15,10 @@
*/
package com.android.permissioncontroller.safetycenter.ui;
+import static android.content.Intent.ACTION_SAFETY_CENTER;
import static android.content.Intent.FLAG_ACTIVITY_FORWARD_RESULT;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCES_GROUP_ID;
import static com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_SIGNAL_NOTIFICATION_INTERACTION;
import static com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_SIGNAL_NOTIFICATION_INTERACTION__ACTION__NOTIFICATION_CLICKED;
@@ -65,7 +67,13 @@ public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity {
}
Fragment frag;
- if (getIntent().getAction().equals(PRIVACY_CONTROLS_ACTION)) {
+ if (SafetyCenterUiFlags.getShowSubpages()
+ && getIntent().getAction().equals(ACTION_SAFETY_CENTER)
+ && getIntent().hasExtra(EXTRA_SAFETY_SOURCES_GROUP_ID)) {
+ frag =
+ SafetyCenterSubpageFragment.newInstance(
+ getIntent().getStringExtra(EXTRA_SAFETY_SOURCES_GROUP_ID));
+ } else if (getIntent().getAction().equals(PRIVACY_CONTROLS_ACTION)) {
setTitle(R.string.privacy_controls_title);
frag = PrivacyControlsFragment.newInstance();
} else {
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
index 92001b5fb..edf71aab9 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
@@ -23,7 +23,6 @@ import static com.android.permissioncontroller.safetycenter.SafetyCenterConstant
import static java.util.Objects.requireNonNull;
-import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
@@ -34,42 +33,31 @@ import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyCenterEntry;
import android.safetycenter.SafetyCenterEntryGroup;
import android.safetycenter.SafetyCenterEntryOrGroup;
-import android.safetycenter.SafetyCenterErrorDetails;
import android.safetycenter.SafetyCenterIssue;
import android.safetycenter.SafetyCenterStaticEntry;
import android.safetycenter.SafetyCenterStaticEntryGroup;
-import android.safetycenter.SafetyCenterStatus;
import android.util.Log;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
-import androidx.annotation.VisibleForTesting;
-import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceGroup;
-import androidx.preference.PreferenceScreen;
-import androidx.recyclerview.widget.RecyclerView;
import com.android.permissioncontroller.Constants;
import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.safetycenter.ui.model.LiveSafetyCenterViewModelFactory;
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData;
-import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel;
-import com.android.safetycenter.internaldata.SafetyCenterIds;
+import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData;
import com.android.safetycenter.resources.SafetyCenterResourcesContext;
-import java.util.Arrays;
+import kotlin.Unit;
+
import java.util.List;
import java.util.Map;
-import kotlin.Unit;
-
/** Dashboard fragment for the Safety Center. */
@RequiresApi(TIRAMISU)
-public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompat {
+public final class SafetyCenterDashboardFragment extends SafetyCenterFragment {
private static final String TAG = SafetyCenterDashboardFragment.class.getSimpleName();
@@ -78,38 +66,15 @@ public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompa
private static final String ENTRIES_GROUP_KEY = "entries_group";
private static final String STATIC_ENTRIES_GROUP_KEY = "static_entries_group";
- @Nullable private final ViewModelProvider.Factory mSafetyCenterViewModelFactoryOverride;
-
private SafetyStatusPreference mSafetyStatusPreference;
- private CollapsableIssuesCardHelper mCollapsableIssuesCardHelper;
private final CollapsableGroupCardHelper mCollapsableGroupCardHelper =
new CollapsableGroupCardHelper();
private PreferenceGroup mIssuesGroup;
private PreferenceGroup mEntriesGroup;
private PreferenceGroup mStaticEntriesGroup;
- private SafetyCenterViewModel mViewModel;
- private List<String> mSameTaskIssueIds;
private boolean mIsQuickSettingsFragment;
- public SafetyCenterDashboardFragment() {
- this(null);
- }
-
- /**
- * Allows providing an override view model factory for testing this fragment. The view model
- * factory will not be retained between recreations of the fragment.
- */
- @VisibleForTesting
- public SafetyCenterDashboardFragment(
- @Nullable ViewModelProvider.Factory safetyCenterViewModelFactoryOverride) {
- mSafetyCenterViewModelFactoryOverride = safetyCenterViewModelFactoryOverride;
- }
-
- private ViewModelProvider.Factory getSafetyCenterViewModelFactory() {
- return mSafetyCenterViewModelFactoryOverride != null
- ? mSafetyCenterViewModelFactoryOverride
- : new LiveSafetyCenterViewModelFactory(requireActivity().getApplication());
- }
+ public SafetyCenterDashboardFragment() {}
/**
* Create instance of SafetyCenterDashboardFragment with the arguments set
@@ -129,57 +94,19 @@ public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompa
}
@Override
- protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
- /* By default, the PreferenceGroupAdapter does setHasStableIds(true).
- * Since each Preference is internally allocated with an auto-incremented ID,
- * it does not allow us to gracefully update only changed preferences based on
- * SafetyPreferenceComparisonCallback.
- * In order to allow the list to track the changes we need to ignore the Preference IDs. */
- RecyclerView.Adapter adapter = super.onCreateAdapter(preferenceScreen);
- adapter.setHasStableIds(false);
- return adapter;
- }
-
- @Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ super.onCreatePreferences(savedInstanceState, rootKey);
setPreferencesFromResource(R.xml.safety_center_dashboard, rootKey);
- mSameTaskIssueIds =
- Arrays.asList(
- new SafetyCenterResourcesContext(getContext())
- .getStringByName("config_same_task_safety_source_ids")
- .split(","));
if (getArguments() != null) {
mIsQuickSettingsFragment =
getArguments().getBoolean(QUICK_SETTINGS_SAFETY_CENTER_FRAGMENT, false);
}
-
- mViewModel =
- new ViewModelProvider(requireActivity(), getSafetyCenterViewModelFactory())
- .get(SafetyCenterViewModel.class);
-
- mCollapsableIssuesCardHelper =
- new CollapsableIssuesCardHelper(mViewModel, mSameTaskIssueIds);
- ParsedSafetyCenterIntent parsedSafetyCenterIntent =
- ParsedSafetyCenterIntent.toSafetyCenterIntent(requireActivity().getIntent());
- mCollapsableIssuesCardHelper.setFocusedIssueKey(
- parsedSafetyCenterIntent.getSafetyCenterIssueKey());
-
- // Set quick settings state first and allow restored state to override if necessary
- mCollapsableIssuesCardHelper.setQuickSettingsState(
- mIsQuickSettingsFragment, parsedSafetyCenterIntent.getShouldExpandIssuesGroup());
- mCollapsableIssuesCardHelper.restoreState(savedInstanceState);
-
mCollapsableGroupCardHelper.restoreState(savedInstanceState);
mSafetyStatusPreference =
requireNonNull(getPreferenceScreen().findPreference(SAFETY_STATUS_KEY));
- // TODO: Use real strings here, or set more sensible defaults in the layout
- mSafetyStatusPreference.setSafetyStatus(
- new SafetyCenterStatus.Builder("Looks good", "")
- .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN)
- .build());
- mSafetyStatusPreference.setViewModel(mViewModel);
+ mSafetyStatusPreference.setViewModel(getSafetyCenterViewModel());
mIssuesGroup = getPreferenceScreen().findPreference(ISSUES_GROUP_KEY);
mEntriesGroup = getPreferenceScreen().findPreference(ENTRIES_GROUP_KEY);
@@ -191,11 +118,7 @@ public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompa
mStaticEntriesGroup = null;
}
- mViewModel.getSafetyCenterUiLiveData().observe(this, this::renderSafetyCenterData);
- mViewModel.getErrorLiveData().observe(this, this::displayErrorDetails);
-
- getPreferenceManager()
- .setPreferenceComparisonCallback(new SafetyPreferenceComparisonCallback());
+ getSafetyCenterViewModel().getStatusUiLiveData().observe(this, this::updateStatus);
}
// Set the default divider line between preferences to be transparent
@@ -214,17 +137,17 @@ public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompa
super.onStart();
configureInteractionLogger();
- mViewModel.getInteractionLogger().record(Action.SAFETY_CENTER_VIEWED);
+ getSafetyCenterViewModel().getInteractionLogger().record(Action.SAFETY_CENTER_VIEWED);
}
@Override
public void onResume() {
super.onResume();
- mViewModel.pageOpen();
+ getSafetyCenterViewModel().pageOpen();
}
private void configureInteractionLogger() {
- InteractionLogger logger = mViewModel.getInteractionLogger();
+ InteractionLogger logger = getSafetyCenterViewModel().getInteractionLogger();
logger.setSessionId(
requireArguments()
@@ -239,24 +162,26 @@ public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompa
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
- mCollapsableIssuesCardHelper.saveState(outState);
mCollapsableGroupCardHelper.saveState(outState);
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- Activity activity = getActivity();
- if (activity != null && activity.isChangingConfigurations()) {
- mViewModel.changingConfigurations();
+ private void updateStatus(StatusUiData statusUiData) {
+ if (mIsQuickSettingsFragment) {
+ SafetyCenterResourcesContext safetyCenterResourcesContext =
+ new SafetyCenterResourcesContext(requireContext());
+ boolean hasPendingActions =
+ safetyCenterResourcesContext
+ .getStringByName("overall_severity_level_ok_review_summary")
+ .equals(statusUiData.getOriginalSummary().toString());
+
+ statusUiData = statusUiData.copyForPendingActions(hasPendingActions);
}
- }
- SafetyCenterViewModel getSafetyCenterViewModel() {
- return mViewModel;
+ mSafetyStatusPreference.setData(statusUiData);
}
- private void renderSafetyCenterData(@Nullable SafetyCenterUiData uiData) {
+ @Override
+ public void renderSafetyCenterData(@Nullable SafetyCenterUiData uiData) {
if (uiData == null) return;
SafetyCenterData data = uiData.getSafetyCenterData();
@@ -267,58 +192,28 @@ public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompa
return;
}
- mSafetyStatusPreference.setSafetyData(data);
-
// TODO(b/208212820): Only update entries that have changed since last
// update, rather than deleting and re-adding all.
updateIssues(context, data.getIssues(), uiData.getResolvedIssues());
+
if (!mIsQuickSettingsFragment) {
updateSafetyEntries(context, data.getEntriesOrGroups());
updateStaticSafetyEntries(context, data.getStaticEntryGroups());
- } else {
- SafetyCenterResourcesContext safetyCenterResourcesContext =
- new SafetyCenterResourcesContext(context);
- boolean hasSettingsToReview =
- safetyCenterResourcesContext
- .getStringByName("overall_severity_level_ok_review_summary")
- .equals(data.getStatus().getSummary().toString());
- setPendingActionState(hasSettingsToReview);
}
}
- /** Determine if there are pending actions and set pending actions state */
- private void setPendingActionState(boolean hasSettingsToReview) {
- if (hasSettingsToReview) {
- mSafetyStatusPreference.setHasPendingActions(
- true,
- l -> {
- mViewModel.navigateToSafetyCenter(
- this, NavigationSource.QUICK_SETTINGS_TILE);
- mViewModel.getInteractionLogger().record(Action.REVIEW_SETTINGS_CLICKED);
- });
- } else {
- mSafetyStatusPreference.setHasPendingActions(false, null);
- }
- }
-
- private void displayErrorDetails(@Nullable SafetyCenterErrorDetails errorDetails) {
- Context context = getContext();
- if (errorDetails == null || context == null) return;
- Toast.makeText(context, errorDetails.getErrorMessage(), Toast.LENGTH_LONG).show();
- mViewModel.clearError();
- }
-
private void updateIssues(
Context context, List<SafetyCenterIssue> issues, Map<String, String> resolvedIssues) {
mIssuesGroup.removeAll();
- mCollapsableIssuesCardHelper.addIssues(
- context,
- mViewModel,
- getChildFragmentManager(),
- mIssuesGroup,
- issues,
- resolvedIssues,
- getActivity().getTaskId());
+ getCollapsableIssuesCardHelper()
+ .addIssues(
+ context,
+ getSafetyCenterViewModel(),
+ getChildFragmentManager(),
+ mIssuesGroup,
+ issues,
+ resolvedIssues,
+ getActivity().getTaskId());
}
// TODO(b/208212820): Add groups and move to separate controller
@@ -334,7 +229,9 @@ public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompa
boolean isFirstElement = i == 0;
boolean isLastElement = i == size - 1;
- if (entry != null) {
+ if (SafetyCenterUiFlags.getShowSubpages() && group != null) {
+ mEntriesGroup.addPreference(new SafetyHomepageEntryPreference(context, group));
+ } else if (entry != null) {
addTopLevelEntry(context, entry, isFirstElement, isLastElement);
} else if (group != null) {
addGroupEntries(context, group, isFirstElement, isLastElement);
@@ -350,10 +247,11 @@ public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompa
mEntriesGroup.addPreference(
new SafetyEntryPreference(
context,
- getTaskIdForEntry(entry.getId()),
+ PendingIntentSender.getTaskIdForEntry(
+ entry.getId(), getSameTaskSourceIds(), requireActivity()),
entry,
PositionInCardList.calculate(isFirstElement, isLastElement),
- mViewModel));
+ getSafetyCenterViewModel()));
}
private void addGroupEntries(
@@ -368,8 +266,10 @@ public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompa
mCollapsableGroupCardHelper::isGroupExpanded,
isFirstCard,
isLastCard,
- this::getTaskIdForEntry,
- mViewModel,
+ (entryId) ->
+ PendingIntentSender.getTaskIdForEntry(
+ entryId, getSameTaskSourceIds(), requireActivity()),
+ getSafetyCenterViewModel(),
(groupId) -> {
mCollapsableGroupCardHelper.onGroupExpanded(groupId);
return Unit.INSTANCE;
@@ -392,13 +292,11 @@ public final class SafetyCenterDashboardFragment extends PreferenceFragmentCompa
for (SafetyCenterStaticEntry entry : group.getStaticEntries()) {
category.addPreference(
new StaticSafetyEntryPreference(
- context, requireActivity().getTaskId(), entry, mViewModel));
+ context,
+ requireActivity().getTaskId(),
+ entry,
+ getSafetyCenterViewModel()));
}
}
}
-
- private @Nullable Integer getTaskIdForEntry(String entryId) {
- String issueId = SafetyCenterIds.entryIdFromString(entryId).getSafetySourceId();
- return mSameTaskIssueIds.contains(issueId) ? requireActivity().getTaskId() : null;
- }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
new file mode 100644
index 000000000..78fc4342a
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterFragment.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.safetycenter.ui
+
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Bundle
+import android.safetycenter.SafetyCenterErrorDetails
+import android.widget.Toast
+import androidx.annotation.RequiresApi
+import androidx.lifecycle.ViewModelProvider
+import androidx.preference.PreferenceFragmentCompat
+import androidx.preference.PreferenceScreen
+import androidx.recyclerview.widget.RecyclerView
+import com.android.permissioncontroller.safetycenter.SafetyCenterConstants.QUICK_SETTINGS_SAFETY_CENTER_FRAGMENT
+import com.android.permissioncontroller.safetycenter.ui.ParsedSafetyCenterIntent.Companion.toSafetyCenterIntent
+import com.android.permissioncontroller.safetycenter.ui.model.LiveSafetyCenterViewModelFactory
+import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
+import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel
+import com.android.safetycenter.resources.SafetyCenterResourcesContext
+
+/** A base fragment that represents a page in Safety Center. */
+@RequiresApi(TIRAMISU)
+abstract class SafetyCenterFragment : PreferenceFragmentCompat() {
+
+ lateinit var safetyCenterViewModel: SafetyCenterViewModel
+ lateinit var sameTaskSourceIds: List<String>
+ lateinit var collapsableIssuesCardHelper: CollapsableIssuesCardHelper
+
+ override fun onCreateAdapter(
+ preferenceScreen: PreferenceScreen?
+ ): RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ /* By default, the PreferenceGroupAdapter does setHasStableIds(true). Since each Preference
+ * is internally allocated with an auto-incremented ID, it does not allow us to gracefully
+ * update only changed preferences based on SafetyPreferenceComparisonCallback. In order to
+ * allow the list to track the changes, we need to ignore the Preference IDs. */
+ val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder> =
+ super.onCreateAdapter(preferenceScreen)
+ adapter.setHasStableIds(false)
+ return adapter
+ }
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ sameTaskSourceIds =
+ SafetyCenterResourcesContext(requireContext())
+ .getStringByName("config_same_task_safety_source_ids")
+ .split(",")
+
+ safetyCenterViewModel =
+ ViewModelProvider(
+ requireActivity(),
+ LiveSafetyCenterViewModelFactory(requireActivity().getApplication())
+ )
+ .get(SafetyCenterViewModel::class.java)
+ safetyCenterViewModel.safetyCenterUiLiveData.observe(this) { uiData: SafetyCenterUiData? ->
+ renderSafetyCenterData(uiData)
+ }
+ safetyCenterViewModel.errorLiveData.observe(this) { errorDetails: SafetyCenterErrorDetails?
+ ->
+ displayErrorDetails(errorDetails)
+ }
+
+ val safetyCenterIntent: ParsedSafetyCenterIntent =
+ requireActivity().getIntent().toSafetyCenterIntent()
+ val isQsFragment =
+ getArguments()?.getBoolean(QUICK_SETTINGS_SAFETY_CENTER_FRAGMENT, false) ?: false
+ collapsableIssuesCardHelper =
+ CollapsableIssuesCardHelper(safetyCenterViewModel, sameTaskSourceIds)
+ collapsableIssuesCardHelper.apply {
+ setFocusedIssueKey(safetyCenterIntent.safetyCenterIssueKey)
+ // Set quick settings state first and allow restored state to override if necessary
+ setQuickSettingsState(isQsFragment, safetyCenterIntent.shouldExpandIssuesGroup)
+ restoreState(savedInstanceState)
+ }
+
+ getPreferenceManager().setPreferenceComparisonCallback(SafetyPreferenceComparisonCallback())
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ collapsableIssuesCardHelper.saveState(outState)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ if (activity?.isChangingConfigurations == true) {
+ safetyCenterViewModel.changingConfigurations()
+ }
+ }
+
+ abstract fun renderSafetyCenterData(uiData: SafetyCenterUiData?)
+
+ private fun displayErrorDetails(errorDetails: SafetyCenterErrorDetails?) {
+ if (errorDetails == null) return
+ Toast.makeText(requireContext(), errorDetails.errorMessage, Toast.LENGTH_LONG).show()
+ safetyCenterViewModel.clearError()
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsFragment.java
index 265845aec..d6015ffae 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterQsFragment.java
@@ -174,22 +174,24 @@ public class SafetyCenterQsFragment extends Fragment {
securitySettings.setOnClickListener(
(v) ->
mSafetyCenterViewModel.navigateToSafetyCenter(
- this, NavigationSource.QUICK_SETTINGS_TILE));
- TextView securitySettingsText =
- securitySettings.findViewById(R.id.toggle_sensor_name);
+ mContext, NavigationSource.QUICK_SETTINGS_TILE));
+ TextView securitySettingsText = securitySettings.findViewById(R.id.toggle_sensor_name);
securitySettingsText.setText(R.string.settings);
securitySettingsText.setSelected(true);
securitySettings.findViewById(R.id.toggle_sensor_status).setVisibility(View.GONE);
- ImageView securitySettingsIcon =
- securitySettings.findViewById(R.id.toggle_sensor_icon);
- securitySettingsIcon.setImageDrawable(Utils.applyTint(mContext,
- mContext.getDrawable(R.drawable.ic_safety_center_shield),
- android.R.attr.textColorPrimaryInverse));
+ ImageView securitySettingsIcon = securitySettings.findViewById(R.id.toggle_sensor_icon);
+ securitySettingsIcon.setImageDrawable(
+ Utils.applyTint(
+ mContext,
+ mContext.getDrawable(R.drawable.ic_safety_center_shield),
+ android.R.attr.textColorPrimaryInverse));
securitySettings.findViewById(R.id.arrow_icon).setVisibility(View.VISIBLE);
((ImageView) securitySettings.findViewById(R.id.arrow_icon))
- .setImageDrawable(Utils.applyTint(mContext,
- mContext.getDrawable(R.drawable.ic_chevron_right),
- android.R.attr.textColorSecondaryInverse));
+ .setImageDrawable(
+ Utils.applyTint(
+ mContext,
+ mContext.getDrawable(R.drawable.ic_chevron_right),
+ android.R.attr.textColorSecondaryInverse));
ViewCompat.replaceAccessibilityAction(
securitySettings,
AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLICK,
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
new file mode 100644
index 000000000..911fc223a
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterSubpageFragment.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.safetycenter.ui
+
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.os.Bundle
+import android.safetycenter.SafetyCenterEntryGroup
+import android.safetycenter.SafetyCenterEntryOrGroup
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.preference.PreferenceGroup
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData
+
+/** A fragment that represents a generic subpage in Safety Center. */
+@RequiresApi(UPSIDE_DOWN_CAKE)
+class SafetyCenterSubpageFragment : SafetyCenterFragment() {
+
+ private lateinit var sourceGroupId: String
+ private var subpageIssueGroup: PreferenceGroup? = null
+ private var subpageEntryGroup: PreferenceGroup? = null
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ super.onCreatePreferences(savedInstanceState, rootKey)
+ setPreferencesFromResource(R.xml.safety_center_subpage, rootKey)
+ sourceGroupId = requireArguments().getString(SOURCE_GROUP_ID_KEY)!!
+ subpageIssueGroup = getPreferenceScreen().findPreference(ISSUE_GROUP_KEY)
+ subpageEntryGroup = getPreferenceScreen().findPreference(ENTRY_GROUP_KEY)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ safetyCenterViewModel.pageOpen(sourceGroupId)
+ }
+
+ override fun renderSafetyCenterData(uiData: SafetyCenterUiData?) {
+ Log.d(TAG, "renderSafetyCenterEntryGroup called with $uiData")
+ val entryGroup = getMatchingGroup(uiData)
+ if (entryGroup == null) {
+ Log.w(TAG, "$sourceGroupId doesn't match any of the existing SafetySourcesGroup IDs")
+ requireActivity().getSupportFragmentManager().popBackStack()
+ return
+ }
+
+ requireActivity().setTitle(entryGroup.title)
+ updateSafetyCenterIssues(uiData)
+ updateSafetyCenterEntries(entryGroup)
+ }
+
+ private fun getMatchingGroup(uiData: SafetyCenterUiData?): SafetyCenterEntryGroup? {
+ val entryOrGroups: List<SafetyCenterEntryOrGroup>? =
+ uiData?.safetyCenterData?.entriesOrGroups
+ val entryGroups = entryOrGroups?.mapNotNull { it.entryGroup }
+ return entryGroups?.find { it.id == sourceGroupId }
+ }
+
+ private fun updateSafetyCenterIssues(uiData: SafetyCenterUiData?) {
+ subpageIssueGroup?.removeAll()
+ val subpageIssues = uiData?.safetyCenterData?.issues?.filter { it.groupId == sourceGroupId }
+ if (subpageIssues.isNullOrEmpty() || subpageIssueGroup == null) {
+ Log.w(TAG, "$sourceGroupId doesn't have any matching SafetyCenterIssues")
+ // TODO(b/253171481): Display a placeholder image when there are no issues
+ return
+ }
+
+ collapsableIssuesCardHelper.addIssues(
+ requireContext(),
+ safetyCenterViewModel,
+ getChildFragmentManager(),
+ subpageIssueGroup!!,
+ subpageIssues,
+ uiData.resolvedIssues,
+ requireActivity().getTaskId()
+ )
+ }
+
+ private fun updateSafetyCenterEntries(entryGroup: SafetyCenterEntryGroup) {
+ Log.d(TAG, "updateSafetyCenterEntries called with $entryGroup")
+ subpageEntryGroup?.removeAll()
+ for (entry in entryGroup.entries) {
+ subpageEntryGroup?.addPreference(
+ SafetySubpageEntryPreference(
+ requireContext(),
+ PendingIntentSender.getTaskIdForEntry(
+ entry.id,
+ sameTaskSourceIds,
+ requireActivity()
+ ),
+ entry
+ )
+ )
+ }
+ }
+
+ companion object {
+ private val TAG: String = SafetyCenterSubpageFragment::class.java.simpleName
+ private const val ISSUE_GROUP_KEY: String = "subpage_issue_group"
+ private const val ENTRY_GROUP_KEY: String = "subpage_entry_group"
+ private const val SOURCE_GROUP_ID_KEY: String = "source_group_id"
+
+ /** Creates an instance of SafetyCenterSubpageFragment with the arguments set */
+ @JvmStatic
+ fun newInstance(groupId: String): SafetyCenterSubpageFragment {
+ val args = Bundle()
+ args.putString(SOURCE_GROUP_ID_KEY, groupId)
+
+ val subpageFragment = SafetyCenterSubpageFragment()
+ subpageFragment.setArguments(args)
+ return subpageFragment
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterUiFlags.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterUiFlags.kt
new file mode 100644
index 000000000..053a77787
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterUiFlags.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.safetycenter.ui
+
+import android.provider.DeviceConfig
+import androidx.core.os.BuildCompat
+
+/** A class to access the Safety Center UI related {@link DeviceConfig} flags. */
+object SafetyCenterUiFlags {
+ private const val PROPERTY_SHOW_SUBPAGES = "safety_center_show_subpages"
+
+ /**
+ * Returns whether to show subpages in the Safety Center UI for Android-U instead of the
+ * expand-and-collapse list implementation.
+ */
+ @JvmStatic
+ fun getShowSubpages(): Boolean {
+ return BuildCompat.isAtLeastU() &&
+ DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_SHOW_SUBPAGES, false)
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupPreference.kt
index 78837dd07..75e8800c1 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyGroupPreference.kt
@@ -27,9 +27,7 @@ import com.android.permissioncontroller.R
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel
import com.android.permissioncontroller.safetycenter.ui.view.SafetyEntryGroupView
-/**
- * A preference that displays a visual representation of a {@link SafetyCenterEntryGroup}.
- */
+/** A preference that displays a visual representation of a {@link SafetyCenterEntryGroup}. */
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
class SafetyGroupPreference(
context: Context,
@@ -43,8 +41,6 @@ class SafetyGroupPreference(
private val onCollapsedListener: (String) -> Unit
) : Preference(context), ComparablePreference {
- val groupId = group.id
-
init {
layoutResource = R.layout.preference_group
}
@@ -53,24 +49,22 @@ class SafetyGroupPreference(
super.onBindViewHolder(holder)
(holder?.itemView as? SafetyEntryGroupView)?.showGroup(
- group,
- isExpanded,
- isFirstCard,
- isLastCard,
- getTaskIdForEntry,
- viewModel,
- onExpandedListener,
- onCollapsedListener
- )
+ group,
+ isExpanded,
+ isFirstCard,
+ isLastCard,
+ getTaskIdForEntry,
+ viewModel,
+ onExpandedListener,
+ onCollapsedListener)
}
override fun isSameItem(other: Preference): Boolean =
- other is SafetyGroupPreference &&
- TextUtils.equals(group.id, other.group.id)
+ other is SafetyGroupPreference && TextUtils.equals(group.id, other.group.id)
override fun hasSameContents(other: Preference): Boolean =
- other is SafetyGroupPreference &&
- group == other.group &&
- isFirstCard == other.isFirstCard &&
- isLastCard == other.isLastCard
-} \ No newline at end of file
+ other is SafetyGroupPreference &&
+ group == other.group &&
+ isFirstCard == other.isFirstCard &&
+ isLastCard == other.isLastCard
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyHomepageEntryPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyHomepageEntryPreference.kt
new file mode 100644
index 000000000..583f3f381
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyHomepageEntryPreference.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.safetycenter.ui
+
+import android.content.Context
+import android.content.Intent
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.safetycenter.SafetyCenterEntryGroup
+import android.safetycenter.SafetyCenterManager
+import android.text.TextUtils
+import androidx.annotation.RequiresApi
+import androidx.preference.Preference
+import java.util.Objects
+
+/**
+ * A preference that displays a visual representation of a {@link SafetyCenterEntryGroup} on the
+ * Safety Center homepage.
+ */
+@RequiresApi(UPSIDE_DOWN_CAKE)
+internal class SafetyHomepageEntryPreference(
+ context: Context,
+ private val entryGroup: SafetyCenterEntryGroup
+) : Preference(context), ComparablePreference {
+
+ init {
+ setTitle(entryGroup.title)
+ setSummary(entryGroup.summary)
+ setIcon(
+ SeverityIconPicker.selectIconResId(
+ entryGroup.severityLevel, entryGroup.severityUnspecifiedIconType))
+
+ // TODO(b/260822348): Check if there is a better way to open the subpage fragment
+ val intent = Intent(Intent.ACTION_SAFETY_CENTER)
+ intent.putExtra(SafetyCenterManager.EXTRA_SAFETY_SOURCES_GROUP_ID, entryGroup.id)
+ setIntent(intent)
+ }
+
+ override fun isSameItem(preference: Preference): Boolean =
+ preference is SafetyHomepageEntryPreference && entryGroup.id == preference.entryGroup.id
+
+ override fun hasSameContents(preference: Preference): Boolean =
+ preference is SafetyHomepageEntryPreference &&
+ Objects.equals(entryGroup.id, preference.entryGroup.id) &&
+ TextUtils.equals(entryGroup.title, preference.entryGroup.title) &&
+ TextUtils.equals(entryGroup.summary, preference.entryGroup.summary) &&
+ entryGroup.severityLevel == preference.entryGroup.severityLevel &&
+ entryGroup.severityUnspecifiedIconType ==
+ preference.entryGroup.severityUnspecifiedIconType
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
index f081017b4..ee2e7c73d 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java
@@ -17,9 +17,6 @@
package com.android.permissioncontroller.safetycenter.ui;
import static android.os.Build.VERSION_CODES.TIRAMISU;
-import static android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING;
-import static android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK;
-import static android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION;
import static android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
import android.content.Context;
@@ -28,11 +25,9 @@ import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
-import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyCenterStatus;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
@@ -46,6 +41,7 @@ import androidx.preference.PreferenceViewHolder;
import com.android.permissioncontroller.R;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel;
+import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData;
import com.google.android.material.button.MaterialButton;
@@ -57,10 +53,8 @@ import kotlin.Pair;
/** Preference which displays a visual representation of {@link SafetyCenterStatus}. */
@RequiresApi(TIRAMISU)
public class SafetyStatusPreference extends Preference implements ComparablePreference {
- private static final String TAG = "SafetyStatusPreference";
- @Nullable private SafetyCenterStatus mStatus;
- @Nullable private View.OnClickListener mReviewSettingsOnClickListener;
+ @Nullable private StatusUiData mStatus;
@Nullable private SafetyCenterViewModel mViewModel;
@NonNull
@@ -74,8 +68,6 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
new TextFadeAnimator(List.of(R.id.status_title, R.id.status_summary));
private boolean mFirstBind = true;
- private boolean mHasPendingActions;
- private boolean mHasIssues;
public SafetyStatusPreference(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -103,8 +95,16 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
MaterialButton pendingActionsRescanButton =
(MaterialButton) holder.findViewById(R.id.pending_actions_rescan_button);
View reviewSettingsButton = holder.findViewById(R.id.review_settings_button);
- if (mHasPendingActions) {
- reviewSettingsButton.setOnClickListener(mReviewSettingsOnClickListener);
+ if (mStatus.hasPendingActions()) {
+ reviewSettingsButton.setOnClickListener(
+ l -> {
+ requireViewModel()
+ .navigateToSafetyCenter(
+ context, NavigationSource.QUICK_SETTINGS_TILE);
+ requireViewModel()
+ .getInteractionLogger()
+ .record(Action.REVIEW_SETTINGS_CLICKED);
+ });
reviewSettingsButton.setVisibility(View.VISIBLE);
} else {
reviewSettingsButton.setVisibility(View.GONE);
@@ -112,15 +112,8 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
rescanButton = updateRescanButtonUi(rescanButton, pendingActionsRescanButton);
setRescanButtonState(rescanButton);
- int contentDescriptionResId =
- R.string.safety_status_preference_title_and_summary_content_description;
holder.findViewById(R.id.status_title_and_summary)
- .setContentDescription(
- getContext()
- .getString(
- contentDescriptionResId,
- mStatus.getTitle(),
- mStatus.getSummary()));
+ .setContentDescription(mStatus.getContentDescription(context));
rescanButton.setOnClickListener(
unused -> {
@@ -143,7 +136,8 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
View safetyProtectionSectionView = holder.findViewById(R.id.safety_protection_section_view);
if (KotlinUtils.INSTANCE.shouldShowSafetyProtectionResources(context)) {
// Hide the Safety Protection branding if there are any issue cards
- safetyProtectionSectionView.setVisibility(mHasIssues ? View.GONE : View.VISIBLE);
+ safetyProtectionSectionView.setVisibility(
+ mStatus.hasIssues() ? View.GONE : View.VISIBLE);
}
if (safetyProtectionSectionView.getVisibility() == View.GONE) {
holder.itemView.setPaddingRelative(
@@ -165,7 +159,7 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
private void updateStatusText(TextView title, TextView summary) {
if (mFirstBind) {
title.setText(mStatus.getTitle());
- summary.setText(getSummaryText());
+ summary.setText(mStatus.getSummary(getContext()));
}
runTextAnimationIfNeeded(title, summary);
}
@@ -173,7 +167,7 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
private void updateStatusIcon(ImageView statusImage, View rescanButton) {
int severityLevel = mStatus.getSeverityLevel();
- boolean isRefreshing = isRefreshInProgress();
+ boolean isRefreshing = mStatus.isRefreshInProgress();
boolean shouldStartScanAnimation = isRefreshing && !mIsScanAnimationRunning;
boolean shouldEndScanAnimation = !isRefreshing && mIsScanAnimationRunning;
boolean shouldChangeIcon = mSettledSeverityLevel != severityLevel;
@@ -202,13 +196,14 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
return;
}
String titleText = mStatus.getTitle().toString();
- String summaryText = getSummaryText().toString();
+ String summaryText = mStatus.getSummary(getContext()).toString();
boolean titleEquals = titleView.getText().toString().equals(titleText);
boolean summaryEquals = summaryView.getText().toString().equals(summaryText);
- Runnable onFinish = () -> {
- mIsTextChangeAnimationRunning = false;
- runTextAnimationIfNeeded(titleView, summaryView);
- };
+ Runnable onFinish =
+ () -> {
+ mIsTextChangeAnimationRunning = false;
+ runTextAnimationIfNeeded(titleView, summaryView);
+ };
mIsTextChangeAnimationRunning = !titleEquals || !summaryEquals;
if (!titleEquals && !summaryEquals) {
Pair<TextView, String> titleChange = new Pair<>(titleView, titleText);
@@ -221,20 +216,6 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
}
}
- private CharSequence getSummaryText() {
- if (mHasPendingActions) {
- return getContext().getString(R.string.safety_center_qs_status_summary);
- } else {
- return mStatus.getSummary().toString();
- }
- }
-
- private boolean isRefreshInProgress() {
- int refreshStatus = mStatus.getRefreshStatus();
- return refreshStatus == SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS
- || refreshStatus == SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS;
- }
-
private void startScanningAnimation(ImageView statusImage) {
mIsScanAnimationRunning = true;
statusImage.setImageResource(
@@ -264,7 +245,7 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
new Animatable2.AnimationCallback() {
@Override
public void onAnimationEnd(Drawable drawable) {
- if (mIsScanAnimationRunning && isRefreshInProgress()) {
+ if (mIsScanAnimationRunning && mStatus.isRefreshInProgress()) {
scanningAnim.start();
} else {
scanningAnim.clearAnimationCallbacks();
@@ -303,8 +284,7 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
@Override
public void onAnimationEnd(Drawable drawable) {
super.onAnimationEnd(drawable);
- finishScanAnimation(
- statusImage, rescanButton);
+ finishScanAnimation(statusImage, rescanButton);
}
});
animatedDrawable.start();
@@ -350,7 +330,7 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
}
mSettledSeverityLevel = mStatus.getSeverityLevel();
- statusImage.setImageResource(toStatusImageResId(mSettledSeverityLevel));
+ statusImage.setImageResource(mStatus.getStatusImageResId());
}
private void handleQueuedAction(ImageView statusImage) {
@@ -369,7 +349,7 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
*/
private MaterialButton updateRescanButtonUi(
MaterialButton rescanButton, MaterialButton pendingActionsRescanButton) {
- if (mHasPendingActions) {
+ if (mStatus.hasPendingActions()) {
rescanButton.setVisibility(View.GONE);
pendingActionsRescanButton.setVisibility(View.VISIBLE);
return pendingActionsRescanButton;
@@ -379,14 +359,8 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
return rescanButton;
}
- void setSafetyStatus(SafetyCenterStatus status) {
- mStatus = status;
- safeNotifyChanged();
- }
-
- void setSafetyData(SafetyCenterData data) {
- mHasIssues = data.getIssues().size() > 0;
- mStatus = data.getStatus();
+ void setData(StatusUiData statusUiData) {
+ mStatus = statusUiData;
safeNotifyChanged();
}
@@ -398,50 +372,15 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
return Objects.requireNonNull(mViewModel);
}
- /**
- * System has pending actions when the user security and privacy signals are deemed to be safe,
- * but the user has previously dismissed some warnings that may need their review
- */
- void setHasPendingActions(boolean hasPendingActions, View.OnClickListener listener) {
- mHasPendingActions = hasPendingActions;
- mReviewSettingsOnClickListener = listener;
- safeNotifyChanged();
- }
-
private void setRescanButtonState(View rescanButton) {
- rescanButton.setVisibility(shouldShowRescanButton() ? View.VISIBLE : View.GONE);
- rescanButton.setEnabled(!isRefreshInProgress());
- }
-
- private boolean shouldShowRescanButton() {
- int severityLevel = mStatus.getSeverityLevel();
- return !mHasIssues
- && !mHasPendingActions // hides the second button in QS to keep the UI clean
- && (severityLevel == OVERALL_SEVERITY_LEVEL_OK
- || severityLevel == OVERALL_SEVERITY_LEVEL_UNKNOWN);
+ rescanButton.setVisibility(mStatus.shouldShowRescanButton() ? View.VISIBLE : View.GONE);
+ rescanButton.setEnabled(!mStatus.isRefreshInProgress());
}
// Calling notifyChanged while recyclerview is scrolling or computing layout will result in an
// IllegalStateException. Post to handler to wait for UI to settle.
private void safeNotifyChanged() {
- new Handler(Looper.getMainLooper()).post(() -> notifyChanged());
- }
-
- private static int toStatusImageResId(int overallSeverityLevel) {
- switch (overallSeverityLevel) {
- case OVERALL_SEVERITY_LEVEL_UNKNOWN:
- case OVERALL_SEVERITY_LEVEL_OK:
- return R.drawable.safety_status_info;
- case OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
- return R.drawable.safety_status_recommendation;
- case OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
- return R.drawable.safety_status_warn;
- default:
- Log.w(
- TAG,
- String.format("Unexpected OverallSeverityLevel: %s", overallSeverityLevel));
- return R.drawable.safety_status_info;
- }
+ new Handler(Looper.getMainLooper()).post(this::notifyChanged);
}
@Override
@@ -456,6 +395,6 @@ public class SafetyStatusPreference extends Preference implements ComparablePref
return false;
}
SafetyStatusPreference other = (SafetyStatusPreference) preference;
- return Objects.equals(mStatus, other.mStatus) && mHasIssues == other.mHasIssues;
+ return Objects.equals(mStatus, other.mStatus);
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetySubpageEntryPreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetySubpageEntryPreference.kt
new file mode 100644
index 000000000..3a7b46262
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetySubpageEntryPreference.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.safetycenter.ui
+
+import android.content.Context
+import android.os.Build
+import android.safetycenter.SafetyCenterEntry
+import android.safetycenter.SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR
+import android.text.TextUtils
+import android.util.Log
+import android.widget.ImageView
+import androidx.annotation.RequiresApi
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.permissioncontroller.R
+import com.android.settingslib.widget.TwoTargetPreference
+
+/**
+ * A preference that displays a visual representation of a {@link SafetyCenterEntry} on the Safety
+ * Center subpage.
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+class SafetySubpageEntryPreference(
+ context: Context,
+ private val launchTaskId: Int?,
+ private val entry: SafetyCenterEntry
+) : TwoTargetPreference(context), ComparablePreference {
+
+ init {
+ setupIconActionButton()
+ setupClickListener()
+ setTitle(entry.title)
+ setSummary(entry.summary)
+ setSelectable(true)
+ }
+
+ private fun setupIconActionButton() {
+ if (entry.iconAction != null) {
+ setIconSize(ICON_SIZE_MEDIUM)
+ setWidgetLayoutResource(
+ if (entry.iconAction!!.type == ICON_ACTION_TYPE_GEAR) {
+ R.layout.preference_entry_icon_action_gear_widget
+ } else {
+ R.layout.preference_entry_icon_action_info_widget
+ })
+ }
+ }
+
+ private fun setupClickListener() {
+ val pendingIntent = entry.pendingIntent
+ if (pendingIntent != null) {
+ setOnPreferenceClickListener {
+ try {
+ PendingIntentSender.send(pendingIntent, launchTaskId)
+ true
+ } catch (ex: Exception) {
+ Log.e(TAG, "Failed to execute pending intent for $entry", ex)
+ false
+ }
+ }
+ } else {
+ Log.w(TAG, "Pending intent is null for $entry")
+ }
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+ val iconAction = entry.iconAction
+ if (iconAction == null) {
+ Log.w(TAG, "Icon action is null for $entry")
+ } else {
+ val iconActionButton = holder.findViewById(R.id.icon_action_button) as? ImageView?
+ iconActionButton?.setOnClickListener {
+ try {
+ PendingIntentSender.send(iconAction.pendingIntent, launchTaskId)
+ } catch (ex: Exception) {
+ Log.e(TAG, "Failed to execute icon action intent for $entry", ex)
+ }
+ }
+ }
+ }
+
+ override fun shouldHideSecondTarget(): Boolean = entry.iconAction == null
+
+ override fun isSameItem(preference: Preference): Boolean =
+ preference is SafetySubpageEntryPreference &&
+ TextUtils.equals(entry.id, preference.entry.id)
+
+ override fun hasSameContents(preference: Preference): Boolean =
+ preference is SafetySubpageEntryPreference && entry == preference.entry
+
+ companion object {
+ private val TAG: String = SafetySubpageEntryPreference::class.java.simpleName
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
index c0d4e8f41..0bf475907 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
@@ -17,9 +17,11 @@
package com.android.permissioncontroller.safetycenter.ui.model
import android.app.Application
+import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_SAFETY_CENTER
import android.os.Build
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.SafetyCenterData
import android.safetycenter.SafetyCenterErrorDetails
import android.safetycenter.SafetyCenterIssue
@@ -30,9 +32,9 @@ import android.util.Log
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getMainExecutor
-import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.android.permissioncontroller.safetycenter.ui.InteractionLogger
@@ -44,6 +46,8 @@ import com.android.safetycenter.internaldata.SafetyCenterIds
class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
private val TAG: String = LiveSafetyCenterViewModel::class.java.simpleName
+ override val statusUiLiveData: LiveData<StatusUiData>
+ get() = Transformations.map(safetyCenterUiLiveData) { StatusUiData(it.safetyCenterData) }
override val safetyCenterUiLiveData: LiveData<SafetyCenterUiData> by this::_safetyCenterLiveData
override val errorLiveData: LiveData<SafetyCenterErrorDetails> by this::_errorLiveData
@@ -75,7 +79,8 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
.toSet()
} else {
setOf()
- })
+ }
+ )
}
private var changingConfigurations = false
@@ -97,7 +102,8 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
SafetyCenterIds.issueIdFromString(issue.id)
.toBuilder()
.setTaskId(launchTaskId)
- .build())
+ .build()
+ )
} else {
issue.id
}
@@ -110,36 +116,70 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
override fun rescan() {
safetyCenterManager.refreshSafetySources(
- SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK)
+ SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK
+ )
}
override fun clearError() {
_errorLiveData.value = null
}
- override fun navigateToSafetyCenter(fragment: Fragment, navigationSource: NavigationSource?) {
+ override fun navigateToSafetyCenter(context: Context, navigationSource: NavigationSource?) {
val intent = Intent(ACTION_SAFETY_CENTER)
if (navigationSource != null) {
navigationSource.addToIntent(intent)
}
- fragment.startActivity(intent)
+ context.startActivity(intent)
}
override fun pageOpen() {
- if (changingConfigurations) {
- // Don't refresh when changing configurations, but reset for the next pageOpen call
- changingConfigurations = false
- } else {
+ executeIfNotChangingConfigurations {
safetyCenterManager.refreshSafetySources(SafetyCenterManager.REFRESH_REASON_PAGE_OPEN)
}
}
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ override fun pageOpen(sourceGroupId: String) {
+ executeIfNotChangingConfigurations {
+ val safetySourceIds = getSafetySourceIdsToRefresh(sourceGroupId)
+ if (safetySourceIds == null) {
+ Log.w(TAG, "$sourceGroupId has no matching source IDs, so refreshing all sources")
+ safetyCenterManager.refreshSafetySources(
+ SafetyCenterManager.REFRESH_REASON_PAGE_OPEN
+ )
+ } else {
+ safetyCenterManager.refreshSafetySources(
+ SafetyCenterManager.REFRESH_REASON_PAGE_OPEN,
+ safetySourceIds
+ )
+ }
+ }
+ }
+
override fun changingConfigurations() {
changingConfigurations = true
}
+ private fun executeIfNotChangingConfigurations(block: () -> Unit) {
+ if (changingConfigurations) {
+ // Don't refresh when changing configurations, but reset for the next pageOpen call
+ changingConfigurations = false
+ return
+ }
+
+ block()
+ }
+
+ private fun getSafetySourceIdsToRefresh(sourceGroupId: String): List<String>? {
+ val safetySourcesGroup =
+ safetyCenterManager.safetyCenterConfig?.safetySourcesGroups?.find {
+ it.id == sourceGroupId
+ }
+ return safetySourcesGroup?.safetySources?.map { it.id }
+ }
+
private inner class SafetyCenterLiveData :
MutableLiveData<SafetyCenterUiData>(),
SafetyCenterManager.OnSafetyCenterDataChangedListener {
@@ -153,7 +193,9 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
override fun onActive() {
safetyCenterManager.addOnSafetyCenterDataChangedListener(
- getMainExecutor(app.applicationContext), this)
+ getMainExecutor(app.applicationContext),
+ this
+ )
super.onActive()
}
@@ -187,7 +229,8 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
Log.d(
TAG,
"Received SafetyCenterData while issue resolution animations" +
- " occurring. Will update UI with new data soon.")
+ " occurring. Will update UI with new data soon."
+ )
return
}
@@ -219,9 +262,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
private fun determineResolvedIssues(nextIssueIds: Set<IssueId>): Map<IssueId, ActionId> {
// Any previously in-flight issue that does not appear in the incoming SafetyCenterData
// is considered resolved.
- return issuesPendingResolution.filterNot { issue ->
- nextIssueIds.contains(issue.key)
- }
+ return issuesPendingResolution.filterNot { issue -> nextIssueIds.contains(issue.key) }
}
private fun shouldEndScan(nextData: SafetyCenterData): Boolean =
@@ -260,9 +301,7 @@ class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
}
}
-/**
- * Returns inflight issues pending resolution
- */
+/** Returns inflight issues pending resolution */
private fun SafetyCenterData.getInFlightIssues(): Map<IssueId, ActionId> =
issues
.map { issue ->
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterViewModel.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterViewModel.kt
index 291079ce9..28e7e4927 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterViewModel.kt
@@ -17,12 +17,13 @@
package com.android.permissioncontroller.safetycenter.ui.model
import android.app.Application
+import android.content.Context
import android.os.Build
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.SafetyCenterData
import android.safetycenter.SafetyCenterErrorDetails
import android.safetycenter.SafetyCenterIssue
import androidx.annotation.RequiresApi
-import androidx.fragment.app.Fragment
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import com.android.permissioncontroller.safetycenter.ui.InteractionLogger
@@ -31,6 +32,7 @@ import com.android.permissioncontroller.safetycenter.ui.NavigationSource
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
abstract class SafetyCenterViewModel(protected val app: Application) : AndroidViewModel(app) {
+ abstract val statusUiLiveData: LiveData<StatusUiData>
abstract val safetyCenterUiLiveData: LiveData<SafetyCenterUiData>
abstract val errorLiveData: LiveData<SafetyCenterErrorDetails>
abstract val interactionLogger: InteractionLogger
@@ -56,12 +58,22 @@ abstract class SafetyCenterViewModel(protected val app: Application) : AndroidVi
abstract fun clearError()
abstract fun navigateToSafetyCenter(
- fragment: Fragment,
+ context: Context,
navigationSource: NavigationSource? = null
)
abstract fun pageOpen()
+ /**
+ * Refreshes a specific subset of safety sources on page-open.
+ *
+ * This is an overload of the [pageOpen] method and is used to request data from safety sources
+ * that are part of a subpage in the Safety Center UI.
+ *
+ * @param sourceGroupId represents ID of the corresponding safety sources group
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE) abstract fun pageOpen(sourceGroupId: String)
+
abstract fun changingConfigurations()
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt
new file mode 100644
index 000000000..beeda213c
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/StatusUiData.kt
@@ -0,0 +1,80 @@
+package com.android.permissioncontroller.safetycenter.ui.model
+
+import android.content.Context
+import android.safetycenter.SafetyCenterData
+import android.safetycenter.SafetyCenterStatus
+import android.util.Log
+import com.android.permissioncontroller.R
+
+/** UI model representation of a Status Card. */
+data class StatusUiData(
+ private val status: SafetyCenterStatus,
+ @get:JvmName("hasIssues") val hasIssues: Boolean = false,
+ @get:JvmName("hasPendingActions") val hasPendingActions: Boolean = false
+) {
+
+ constructor(
+ safetyCenterData: SafetyCenterData
+ ) : this(safetyCenterData.status, hasIssues = safetyCenterData.issues.size > 0)
+
+ // For convenience use in Java.
+ fun copyForPendingActions(hasPendingActions: Boolean) =
+ copy(hasPendingActions = hasPendingActions)
+
+ private companion object {
+ val TAG: String = StatusUiData::class.java.simpleName
+ }
+
+ val title: CharSequence by status::title
+ val originalSummary: CharSequence by status::summary
+ val severityLevel: Int by status::severityLevel
+
+ val statusImageResId: Int
+ get() =
+ when (severityLevel) {
+ SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN,
+ SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK -> R.drawable.safety_status_info
+ SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION ->
+ R.drawable.safety_status_recommendation
+ SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING ->
+ R.drawable.safety_status_warn
+ else -> {
+ Log.w(TAG, "Unexpected OverallSeverityLevel: $severityLevel")
+ R.drawable.safety_status_info
+ }
+ }
+
+ fun getSummary(context: Context): CharSequence {
+ return if (hasPendingActions) {
+ // Use a different string for the special quick-settings-only hasPendingActions state.
+ context.getString(R.string.safety_center_qs_status_summary)
+ } else {
+ originalSummary
+ }
+ }
+
+ fun getContentDescription(context: Context): CharSequence {
+ return context.getString(
+ R.string.safety_status_preference_title_and_summary_content_description,
+ title,
+ getSummary(context))
+ }
+
+ val isRefreshInProgress: Boolean
+ get() =
+ when (status.refreshStatus) {
+ SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS,
+ SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS -> true
+ else -> false
+ }
+
+ fun shouldShowRescanButton(): Boolean {
+ return !hasIssues &&
+ !hasPendingActions &&
+ when (severityLevel) {
+ SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK,
+ SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN -> true
+ else -> false
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetylabel/AppsSafetyLabelHistory.kt b/PermissionController/src/com/android/permissioncontroller/safetylabel/AppsSafetyLabelHistory.kt
new file mode 100644
index 000000000..9e1897e99
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetylabel/AppsSafetyLabelHistory.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.safetylabel
+
+import java.time.Instant
+
+/** Data class representing safety label history of installed apps. */
+data class AppsSafetyLabelHistory(val appSafetyLabelHistories: List<AppSafetyLabelHistory>) {
+
+ /** Data class representing the safety label history of an app. */
+ data class AppSafetyLabelHistory(
+ /** Information about the app. */
+ val appInfo: AppInfo,
+ /**
+ * A list of [SafetyLabel]s that this app has had in the past, ordered by
+ * [SafetyLabel.receivedAt].
+ *
+ * The last [SafetyLabel] in this list can be considered the last known [SafetyLabel] of the
+ * app.
+ */
+ val safetyLabelHistory: List<SafetyLabel>
+ ) {
+ init {
+ require(safetyLabelHistory.sortedBy { it.receivedAt } == safetyLabelHistory)
+ require(safetyLabelHistory.all { it.appInfo == appInfo })
+ }
+
+ /**
+ * Returns an [AppSafetyLabelHistory] with the original history as well the provided safety
+ * label.
+ */
+ fun withSafetyLabel(safetyLabel: SafetyLabel) =
+ AppSafetyLabelHistory(
+ appInfo,
+ safetyLabelHistory
+ .toMutableList()
+ .apply { add(safetyLabel) }
+ .sortedBy { it.receivedAt })
+ }
+
+ /** Data class representing the information about an app. */
+ data class AppInfo(
+ val packageName: String,
+ )
+
+ /** Data class representing an app's safety label. */
+ data class SafetyLabel(
+ /** Information about the app. */
+ val appInfo: AppInfo,
+ /** Earliest time at which the safety label was known to be accurate. */
+ val receivedAt: Instant,
+ /** Information about data use policies for an app. */
+ val dataLabel: DataLabel
+ )
+
+ /** Data class representing an app's data use policies. */
+ data class DataLabel(
+ /** Map of category to [DataCategory] */
+ // TODO(b/263153040): Use Category constants from Safety Label library.
+ val dataShared: Map<String, DataCategory>
+ )
+
+ /** Data class representing an app's data use for a particular category of data. */
+ data class DataCategory(
+ /** Whether any data in this category has been used for Advertising. */
+ val containsAdvertisingPurpose: Boolean
+ )
+
+ /** Data class representing a change of an app's safety label over time. */
+ data class AppSafetyLabelDiff(
+ val safetyLabelBefore: SafetyLabel,
+ val safetyLabelAfter: SafetyLabel
+ ) {
+ init {
+ require(safetyLabelBefore.appInfo == safetyLabelAfter.appInfo)
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetylabel/AppsSafetyLabelHistoryPersistence.kt b/PermissionController/src/com/android/permissioncontroller/safetylabel/AppsSafetyLabelHistoryPersistence.kt
new file mode 100644
index 000000000..cb8bef3d6
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetylabel/AppsSafetyLabelHistoryPersistence.kt
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.safetylabel
+
+import android.content.Context
+import android.os.Build
+import android.util.AtomicFile
+import android.util.Log
+import android.util.Xml
+import androidx.annotation.RequiresApi
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.AppInfo
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.AppSafetyLabelDiff
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.AppSafetyLabelHistory
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.DataCategory
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.DataLabel
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.SafetyLabel
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.nio.charset.StandardCharsets
+import java.time.Instant
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+import org.xmlpull.v1.XmlSerializer
+
+/** Persists safety label history to disk and allows reading from and writing to this storage */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+object AppsSafetyLabelHistoryPersistence {
+ private const val TAG_DATA_SHARED_MAP = "shared"
+ private const val TAG_DATA_SHARED_ENTRY = "entry"
+ private const val TAG_APP_INFO = "app-info"
+ private const val TAG_DATA_LABEL = "data-lbl"
+ private const val TAG_SAFETY_LABEL = "sfty-lbl"
+ private const val TAG_APP_SAFETY_LABEL_HISTORY = "app-hstry"
+ private const val TAG_APPS_SAFETY_LABEL_HISTORY = "apps-hstry"
+ private const val ATTRIBUTE_PACKAGE_NAME = "pkg-name"
+ private const val ATTRIBUTE_RECEIVED_AT = "rcvd"
+ private const val ATTRIBUTE_CATEGORY = "cat"
+ private const val ATTRIBUTE_CONTAINS_ADS = "ads"
+ /** The name of the file used to persist Safety Label history. */
+ private const val APPS_SAFETY_LABEL_HISTORY_PERSISTENCE_FILE_NAME =
+ "apps_safety_label_history_persistence.xml"
+ private val LOG_TAG = "AppsSafetyLabelHistoryPersistence".take(23)
+ private val readWriteLock = Any()
+
+ /**
+ * Reads the provided file storing safety label history and returns the parsed
+ * [AppsSafetyLabelHistory].
+ */
+ fun read(file: File): AppsSafetyLabelHistory? {
+ val parser = Xml.newPullParser()
+ try {
+ AtomicFile(file).openRead().let { inputStream ->
+ parser.setInput(inputStream, StandardCharsets.UTF_8.name())
+ return parser.parseHistoryFile()
+ }
+ } catch (e: FileNotFoundException) {
+ Log.e(LOG_TAG, "File not found: $file")
+ } catch (e: IOException) {
+ Log.e(
+ LOG_TAG, "Failed to read file: $file, encountered exception ${e.localizedMessage}")
+ } catch (e: XmlPullParserException) {
+ Log.e(
+ LOG_TAG, "Failed to parse file: $file, encountered exception ${e.localizedMessage}")
+ }
+
+ return null
+ }
+
+ /**
+ * Writes a new safety label to the provided file, if the provided safety label has changed from
+ * the last recorded.
+ */
+ fun recordSafetyLabel(safetyLabel: SafetyLabel, file: File) {
+ synchronized(readWriteLock) {
+ val currentAppsSafetyLabelHistory = read(file) ?: AppsSafetyLabelHistory(listOf())
+ val appInfo = safetyLabel.appInfo
+ val currentHistories = currentAppsSafetyLabelHistory.appSafetyLabelHistories
+
+ val updatedAppsSafetyLabelHistory: AppsSafetyLabelHistory =
+ if (currentHistories.all { it.appInfo != appInfo }) {
+ AppsSafetyLabelHistory(
+ currentHistories.toMutableList().apply {
+ add(AppSafetyLabelHistory(appInfo, listOf(safetyLabel)))
+ })
+ } else {
+ AppsSafetyLabelHistory(
+ currentHistories.map {
+ if (it.appInfo != appInfo) it
+ else it.addSafetyLabelIfChanged(safetyLabel)
+ })
+ }
+
+ write(file, updatedAppsSafetyLabelHistory)
+ }
+ }
+
+ /** Serializes and writes the provided [AppsSafetyLabelHistory] to the provided file. */
+ fun write(file: File, appsSafetyLabelHistory: AppsSafetyLabelHistory) {
+ val atomicFile = AtomicFile(file)
+ var outputStream: FileOutputStream? = null
+
+ try {
+ outputStream = atomicFile.startWrite()
+ // TODO(b/263153094): Use BinaryXmlSerializer.
+ val serializer = Xml.newSerializer()
+ serializer.setOutput(outputStream, StandardCharsets.UTF_8.name())
+ serializer.startDocument(null, true)
+ serializer.serializeAllAppSafetyLabelHistory(appsSafetyLabelHistory)
+ serializer.endDocument()
+ atomicFile.finishWrite(outputStream)
+ } catch (e: Exception) {
+ Log.i(
+ LOG_TAG, "Failed to write to $file. Previous version of file will be restored.", e)
+ atomicFile.failWrite(outputStream)
+ } finally {
+ try {
+ outputStream?.close()
+ } catch (e: Exception) {
+ Log.e(LOG_TAG, "Failed to close $file.", e)
+ }
+ }
+ }
+
+ /** Reads the provided history file and returns all safety label changes since [startTime]. */
+ fun getAppSafetyLabelDiffs(startTime: Instant, file: File): List<AppSafetyLabelDiff> {
+ val currentAppsSafetyLabelHistory = read(file) ?: AppsSafetyLabelHistory(listOf())
+
+ return currentAppsSafetyLabelHistory.appSafetyLabelHistories.mapNotNull {
+ val before = it.getSafetyLabelAt(startTime)
+ val after = it.getLatestSafetyLabel()
+ if (before == null ||
+ after == null ||
+ before == after ||
+ before.receivedAt.isAfter(after.receivedAt))
+ null
+ else AppSafetyLabelDiff(before, after)
+ }
+ }
+
+ /** Clears the file. */
+ fun clear(file: File) {
+ AtomicFile(file).delete()
+ }
+
+ /** Returns the file persisting safety label history for installed apps. */
+ fun getSafetyLabelHistoryFile(context: Context): File =
+ File(context.filesDir, APPS_SAFETY_LABEL_HISTORY_PERSISTENCE_FILE_NAME)
+
+ private fun XmlPullParser.parseHistoryFile(): AppsSafetyLabelHistory {
+ if (eventType != XmlPullParser.START_DOCUMENT) {
+ throw IllegalArgumentException()
+ }
+ nextTag()
+
+ val appsSafetyLabelHistory = parseAppsSafetyLabelHistory()
+
+ while (eventType == XmlPullParser.TEXT && isWhitespace) {
+ next()
+ }
+ if (eventType != XmlPullParser.END_DOCUMENT) {
+ throw IllegalArgumentException("Unexpected extra element")
+ }
+
+ return appsSafetyLabelHistory
+ }
+
+ private fun XmlPullParser.parseAppsSafetyLabelHistory(): AppsSafetyLabelHistory {
+ // TODO(b/263153093): Add versioning.
+ checkTagStart(TAG_APPS_SAFETY_LABEL_HISTORY)
+ nextTag()
+
+ val appSafetyLabelHistories = mutableListOf<AppSafetyLabelHistory>()
+ while (eventType == XmlPullParser.START_TAG && name == TAG_APP_SAFETY_LABEL_HISTORY) {
+ appSafetyLabelHistories.add(parseAppSafetyLabelHistory())
+ }
+
+ checkTagEnd(TAG_APPS_SAFETY_LABEL_HISTORY)
+ next()
+
+ return AppsSafetyLabelHistory(appSafetyLabelHistories)
+ }
+
+ private fun XmlPullParser.parseAppSafetyLabelHistory(): AppSafetyLabelHistory {
+ checkTagStart(TAG_APP_SAFETY_LABEL_HISTORY)
+ nextTag()
+
+ val appInfo = parseAppInfo()
+
+ val safetyLabels = mutableListOf<SafetyLabel>()
+ while (eventType == XmlPullParser.START_TAG && name == TAG_SAFETY_LABEL) {
+ safetyLabels.add(parseSafetyLabel(appInfo))
+ }
+
+ checkTagEnd(TAG_APP_SAFETY_LABEL_HISTORY)
+ nextTag()
+
+ return AppSafetyLabelHistory(appInfo, safetyLabels)
+ }
+
+ private fun XmlPullParser.parseSafetyLabel(appInfo: AppInfo): SafetyLabel {
+ checkTagStart(TAG_SAFETY_LABEL)
+
+ var receivedAt: Instant? = null
+ for (i in 0 until attributeCount) {
+ when (getAttributeName(i)) {
+ ATTRIBUTE_RECEIVED_AT -> receivedAt = parseInstant(getAttributeValue(i))
+ else ->
+ throw IllegalArgumentException(
+ "Unexpected attribute ${getAttributeName(i)} in tag $TAG_SAFETY_LABEL")
+ }
+ }
+ if (receivedAt == null) {
+ throw IllegalArgumentException("Missing $ATTRIBUTE_RECEIVED_AT in $TAG_SAFETY_LABEL")
+ }
+ nextTag()
+
+ val dataLabel = parseDataLabel()
+
+ checkTagEnd(TAG_SAFETY_LABEL)
+ nextTag()
+
+ return SafetyLabel(appInfo, receivedAt, dataLabel)
+ }
+
+ private fun XmlPullParser.parseDataLabel(): DataLabel {
+ checkTagStart(TAG_DATA_LABEL)
+ nextTag()
+
+ val dataSharing = parseDataShared()
+
+ checkTagEnd(TAG_DATA_LABEL)
+ nextTag()
+
+ return DataLabel(dataSharing)
+ }
+
+ private fun XmlPullParser.parseDataShared(): Map<String, DataCategory> {
+ checkTagStart(TAG_DATA_SHARED_MAP)
+ nextTag()
+
+ val sharedCategories = mutableListOf<Pair<String, DataCategory>>()
+ while (eventType == XmlPullParser.START_TAG && name == TAG_DATA_SHARED_ENTRY) {
+ sharedCategories.add(parseDataSharedEntry())
+ }
+
+ checkTagEnd(TAG_DATA_SHARED_MAP)
+ nextTag()
+
+ return sharedCategories.associate { it.first to it.second }
+ }
+
+ private fun XmlPullParser.parseDataSharedEntry(): Pair<String, DataCategory> {
+ checkTagStart(TAG_DATA_SHARED_ENTRY)
+ var category: String? = null
+ var hasAds: Boolean? = null
+ for (i in 0 until attributeCount) {
+ when (getAttributeName(i)) {
+ ATTRIBUTE_CATEGORY -> category = getAttributeValue(i)
+ ATTRIBUTE_CONTAINS_ADS -> hasAds = getAttributeValue(i).toBoolean()
+ else ->
+ throw IllegalArgumentException(
+ "Unexpected attribute ${getAttributeName(i)} in tag $TAG_DATA_SHARED_ENTRY")
+ }
+ }
+ if (category == null) {
+ throw IllegalArgumentException("Missing $ATTRIBUTE_CATEGORY in $TAG_DATA_SHARED_ENTRY")
+ }
+ if (hasAds == null) {
+ throw IllegalArgumentException(
+ "Missing $ATTRIBUTE_CONTAINS_ADS in $TAG_DATA_SHARED_ENTRY")
+ }
+ nextTag()
+
+ checkTagEnd(TAG_DATA_SHARED_ENTRY)
+ nextTag()
+
+ return category to DataCategory(hasAds)
+ }
+
+ private fun XmlPullParser.parseAppInfo(): AppInfo {
+ checkTagStart(TAG_APP_INFO)
+ var packageName: String? = null
+ for (i in 0 until attributeCount) {
+ when (getAttributeName(i)) {
+ ATTRIBUTE_PACKAGE_NAME -> packageName = getAttributeValue(i)
+ else ->
+ throw IllegalArgumentException(
+ "Unexpected attribute ${getAttributeName(i)} in tag $TAG_APP_INFO")
+ }
+ }
+ if (packageName == null) {
+ throw IllegalArgumentException("Missing $ATTRIBUTE_PACKAGE_NAME in $TAG_APP_INFO")
+ }
+
+ nextTag()
+ checkTagEnd(TAG_APP_INFO)
+ nextTag()
+ return AppInfo(packageName)
+ }
+
+ private fun XmlPullParser.checkTagStart(tag: String) {
+ check(eventType == XmlPullParser.START_TAG && tag == name)
+ }
+
+ private fun XmlPullParser.checkTagEnd(tag: String) {
+ check(eventType == XmlPullParser.END_TAG && tag == name)
+ }
+
+ private fun parseInstant(value: String): Instant {
+ return try {
+ Instant.ofEpochMilli(value.toLong())
+ } catch (e: Exception) {
+ throw IllegalArgumentException("Could not parse $value as Instant")
+ }
+ }
+
+ private fun XmlSerializer.serializeAllAppSafetyLabelHistory(
+ appsSafetyLabelHistory: AppsSafetyLabelHistory
+ ) {
+ startTag(null, TAG_APPS_SAFETY_LABEL_HISTORY)
+ appsSafetyLabelHistory.appSafetyLabelHistories.forEach {
+ serializeAppSafetyLabelHistory(it)
+ }
+ endTag(null, TAG_APPS_SAFETY_LABEL_HISTORY)
+ }
+
+ private fun XmlSerializer.serializeAppSafetyLabelHistory(
+ appSafetyLabelHistory: AppSafetyLabelHistory
+ ) {
+ startTag(null, TAG_APP_SAFETY_LABEL_HISTORY)
+ serializeAppInfo(appSafetyLabelHistory.appInfo)
+ appSafetyLabelHistory.safetyLabelHistory.forEach { serializeSafetyLabel(it) }
+ endTag(null, TAG_APP_SAFETY_LABEL_HISTORY)
+ }
+
+ private fun XmlSerializer.serializeAppInfo(appInfo: AppInfo) {
+ startTag(null, TAG_APP_INFO)
+ attribute(null, ATTRIBUTE_PACKAGE_NAME, appInfo.packageName)
+ endTag(null, TAG_APP_INFO)
+ }
+
+ private fun XmlSerializer.serializeSafetyLabel(safetyLabel: SafetyLabel) {
+ startTag(null, TAG_SAFETY_LABEL)
+ attribute(null, ATTRIBUTE_RECEIVED_AT, safetyLabel.receivedAt.toEpochMilli().toString())
+ serializeDataLabel(safetyLabel.dataLabel)
+ endTag(null, TAG_SAFETY_LABEL)
+ }
+
+ private fun XmlSerializer.serializeDataLabel(dataLabel: DataLabel) {
+ startTag(null, TAG_DATA_LABEL)
+ serializeDataSharedMap(dataLabel.dataShared)
+ endTag(null, TAG_DATA_LABEL)
+ }
+
+ private fun XmlSerializer.serializeDataSharedMap(dataShared: Map<String, DataCategory>) {
+ startTag(null, TAG_DATA_SHARED_MAP)
+ dataShared.entries.forEach { serializeDataSharedEntry(it) }
+ endTag(null, TAG_DATA_SHARED_MAP)
+ }
+
+ private fun XmlSerializer.serializeDataSharedEntry(
+ dataSharedEntry: Map.Entry<String, DataCategory>
+ ) {
+ startTag(null, TAG_DATA_SHARED_ENTRY)
+ attribute(null, ATTRIBUTE_CATEGORY, dataSharedEntry.key)
+ attribute(
+ null,
+ ATTRIBUTE_CONTAINS_ADS,
+ dataSharedEntry.value.containsAdvertisingPurpose.toString())
+ endTag(null, TAG_DATA_SHARED_ENTRY)
+ }
+
+ private fun AppSafetyLabelHistory.addSafetyLabelIfChanged(
+ safetyLabel: SafetyLabel
+ ): AppSafetyLabelHistory {
+ val latestSafetyLabel = safetyLabelHistory.lastOrNull()
+ return if (latestSafetyLabel?.dataLabel == safetyLabel.dataLabel) this
+ else this.withSafetyLabel(safetyLabel)
+ }
+
+ private fun AppSafetyLabelHistory.getLatestSafetyLabel() = safetyLabelHistory.lastOrNull()
+
+ /**
+ * Return the safety label known to be the current safety label for the app at the provided
+ * time, if available in the history.
+ */
+ private fun AppSafetyLabelHistory.getSafetyLabelAt(startTime: Instant) =
+ safetyLabelHistory.lastOrNull {
+ // the last received safety label before or at startTime
+ it.receivedAt.isBefore(startTime) || it.receivedAt == startTime
+ }
+ ?: // the first safety label received after startTime, as a fallback
+ safetyLabelHistory.firstOrNull { it.receivedAt.isAfter(startTime) }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetylabel/SafetyLabelChangedBroadcastReceiver.kt b/PermissionController/src/com/android/permissioncontroller/safetylabel/SafetyLabelChangedBroadcastReceiver.kt
new file mode 100644
index 000000000..b09011687
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetylabel/SafetyLabelChangedBroadcastReceiver.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.safetylabel
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import androidx.core.util.Preconditions
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+
+/**
+ * Listens for package additions, replacements, and removals.
+ */
+class SafetyLabelChangedBroadcastReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ if (!KotlinUtils.isSafetyLabelChangeNotificationsEnabled()) {
+ return
+ }
+
+ val data = Preconditions.checkNotNull(intent.data)
+ val action = Preconditions.checkNotNull(intent.action)
+ if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ if (action != Intent.ACTION_PACKAGE_ADDED) {
+ return
+ }
+ // TODO(b/261660881): Update Safety Label store when packages are added or updated.
+ if (DEBUG) {
+ Log.v(TAG, "Package ${data.schemeSpecificPart} replaced. Intent " +
+ "Action: ${action}\n")
+ }
+ } else if (action == Intent.ACTION_PACKAGE_ADDED) {
+ // TODO(b/261660881): Update Safety Label store when packages are added or updated.
+ if (DEBUG) {
+ Log.v(TAG, "Package ${data.schemeSpecificPart} added.")
+ }
+ } else if (action == Intent.ACTION_PACKAGE_REMOVED) {
+ // TODO(b/261661752): Update Safety Label store when packages are removed.
+ if (DEBUG) {
+ Log.v(TAG, "Package ${data.schemeSpecificPart} removed.")
+ }
+ }
+ }
+ companion object {
+ private const val TAG = "SafetyLabelChangedBroadcastReceiver"
+ private const val DEBUG = false
+ }
+} \ No newline at end of file
diff --git a/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING
new file mode 100644
index 000000000..d5734e4d1
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetylabel/TEST_MAPPING
@@ -0,0 +1,27 @@
+{
+ "presubmit": [
+ {
+ "name": "PermissionControllerMockingTests",
+ "options": [
+ {
+ "include-filter": "com.android.permissioncontroller.tests.mocking.safetylabel.AppsSafetyLabelHistoryPersistenceTest"
+ }
+ ]
+ }
+ ],
+ "presubmit-large": [
+ {
+ "name": "CtsPermission3TestCases",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsPermission3TestCases[com.google.android.permission.apex]"
+ }
+ ]
+} \ No newline at end of file
diff --git a/PermissionController/tests/inprocess/Android.bp b/PermissionController/tests/inprocess/Android.bp
index 873e0704a..0cc355daa 100644
--- a/PermissionController/tests/inprocess/Android.bp
+++ b/PermissionController/tests/inprocess/Android.bp
@@ -44,11 +44,16 @@ android_test {
"androidx.test.rules",
"androidx.test.ext.truth",
"androidx.test.ext.junit",
- "androidx.test.uiautomator",
+ "androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt",
"permission-test-util-lib",
],
+ data: [
+ ":AppThatUsesCameraPermission",
+ ],
+ per_testcase_directory: true,
+
certificate: "platform",
instrumentation_for: "PermissionController",
diff --git a/PermissionController/tests/mocking/Android.bp b/PermissionController/tests/mocking/Android.bp
index 55e75401c..8cdcb1e99 100644
--- a/PermissionController/tests/mocking/Android.bp
+++ b/PermissionController/tests/mocking/Android.bp
@@ -108,6 +108,9 @@ android_test {
"modules-utils-build_system",
"safety-center-resources-lib",
"safety-center-internal-data",
+ "safety-label",
+ "role-controller",
+ "lottie",
"androidx.test.rules",
"androidx.test.ext.truth",
@@ -116,6 +119,11 @@ android_test {
"mockito-target-extended-minus-junit4",
],
+ proto: {
+ type: "lite",
+ include_dirs: ["packages/modules/Permission/PermissionController/src/com/android/permissioncontroller"],
+ },
+
jni_libs: [
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationControllerTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationControllerTest.kt
index 8853c5ec9..03ec86d02 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationControllerTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/hibernation/HibernationControllerTest.kt
@@ -28,9 +28,10 @@ import androidx.test.filters.SdkSuppress
import androidx.test.platform.app.InstrumentationRegistry
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.permissioncontroller.Constants
-import com.android.permissioncontroller.hibernation.v31.HibernationController
import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.hibernation.v31.HibernationController
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
+import java.io.File
import org.junit.After
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -43,11 +44,10 @@ import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
-import java.io.File
-import org.mockito.Mockito.`when` as whenever
/**
* Unit tests for [HibernationController].
@@ -159,6 +159,8 @@ class HibernationControllerTest {
false /* isInstantApp */,
true /* enabled */,
0 /* appFlags */,
- 0 /* firstInstallTime */)
+ 0 /* firstInstallTime */,
+ false /* areAttributionsUserVisible */,
+ emptyMap() /* attributionTagsToLabels */)
}
}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/ReviewPermissionsViewModelTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/ReviewPermissionsViewModelTest.kt
index df6e92154..0f4216066 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/ReviewPermissionsViewModelTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/ui/model/ReviewPermissionsViewModelTest.kt
@@ -26,11 +26,11 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.PermissionTarget.PERMISSION_BACKGROUND
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.PermissionTarget.PERMISSION_BOTH
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.PermissionTarget.PERMISSION_FOREGROUND
-import com.android.permissioncontroller.permission.ui.model.v33.ReviewPermissionsViewModel.SummaryMessage
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel.PermissionTarget.PERMISSION_BACKGROUND
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel.PermissionTarget.PERMISSION_BOTH
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel.PermissionTarget.PERMISSION_FOREGROUND
+import com.android.permissioncontroller.permission.ui.model.ReviewPermissionsViewModel.SummaryMessage
import com.android.permissioncontroller.permission.utils.Utils
import com.android.settingslib.RestrictedLockUtils
import org.junit.After
@@ -42,10 +42,10 @@ import org.mockito.Mock
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
-import org.mockito.Mockito.`when` as whenever
/**
* Unit tests for [ReviewPermissionsViewModel]
@@ -247,4 +247,4 @@ class ReviewPermissionsViewModelTest {
requestedPermissionsFlags = listOf<Int>().toIntArray()
}
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt
index df4c4e80f..fd21d950d 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/GrantRevokeTests.kt
@@ -40,6 +40,7 @@ import android.content.pm.PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY
import android.os.Build
import android.os.UserHandle
import android.permission.PermissionManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup
import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo
import com.android.permissioncontroller.permission.model.livedatatypes.LightPermGroupInfo
@@ -58,12 +59,10 @@ import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.nullable
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.mockito.Mockito.`when`
private const val PERMISSION_CONTROLLER_CHANGED_FLAG_MASK = FLAG_PERMISSION_USER_SET or
FLAG_PERMISSION_USER_FIXED or
@@ -155,7 +154,7 @@ class GrantRevokeTests {
Build.VERSION_CODES.LOLLIPOP
} else {
Build.VERSION_CODES.R
- }, isInstantApp, isInstantApp, 0, 0L)
+ }, isInstantApp, isInstantApp, 0, 0L, false, emptyMap())
}
/**
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/AppDataSharingUpdatesPrivacySourceTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/AppDataSharingUpdatesPrivacySourceTest.kt
new file mode 100644
index 000000000..9aeac3935
--- /dev/null
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/AppDataSharingUpdatesPrivacySourceTest.kt
@@ -0,0 +1,226 @@
+/*
+ * 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.tests.mocking.privacysources
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.Intent
+import android.content.Intent.ACTION_BOOT_COMPLETED
+import android.content.Intent.ACTION_REVIEW_APP_DATA_SHARING_UPDATES
+import android.os.Build
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_PRIVACY
+import android.safetycenter.SafetyCenterManager
+import android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES
+import android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID
+import android.safetycenter.SafetyEvent
+import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED
+import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED
+import android.safetycenter.SafetySourceData
+import android.safetycenter.SafetySourceStatus
+import android.safetylabel.SafetyLabelConstants.PERMISSION_RATIONALE_ENABLED
+import android.safetylabel.SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.utils.Utils
+import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.EVENT_DEVICE_REBOOTED
+import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.EVENT_REFRESH_REQUESTED
+import com.android.permissioncontroller.privacysources.v34.AppDataSharingUpdatesPrivacySource
+import com.android.permissioncontroller.privacysources.v34.AppDataSharingUpdatesPrivacySource.Companion.APP_DATA_SHARING_UPDATES_SOURCE_ID
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+import org.mockito.MockitoSession
+import org.mockito.quality.Strictness
+
+/** Tests for [AppDataSharingUpdatesPrivacySource]. */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class AppDataSharingUpdatesPrivacySourceTest {
+
+ private lateinit var mockitoSession: MockitoSession
+ private lateinit var appDataSharingUpdatesPrivacySource: AppDataSharingUpdatesPrivacySource
+ @Mock lateinit var mockSafetyCenterManager: SafetyCenterManager
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .mockStatic(DeviceConfig::class.java)
+ .mockStatic(PermissionControllerApplication::class.java)
+ .mockStatic(Utils::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ whenever(
+ Utils.getSystemServiceSafe(
+ any(ContextWrapper::class.java), eq(SafetyCenterManager::class.java)))
+ .thenReturn(mockSafetyCenterManager)
+
+ appDataSharingUpdatesPrivacySource = AppDataSharingUpdatesPrivacySource()
+
+ setPermissionRationaleEnabled(true)
+ setSafetyLabelChangeNotificationsEnabled(true)
+ }
+
+ @After
+ fun cleanup() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun safetyCenterEnabledChanged_enabled_doesNothing() {
+ appDataSharingUpdatesPrivacySource.safetyCenterEnabledChanged(context, true)
+
+ verifyZeroInteractions(mockSafetyCenterManager)
+ }
+
+ @Test
+ fun safetyCenterEnabledChanged_disabled_doesNothing() {
+ appDataSharingUpdatesPrivacySource.safetyCenterEnabledChanged(context, false)
+
+ verifyZeroInteractions(mockSafetyCenterManager)
+ }
+
+ @Test
+ fun rescanAndPushSafetyCenterData_bothFeaturesEnabled_setsDataWithStatus() {
+ val refreshIntent =
+ Intent(ACTION_REFRESH_SAFETY_SOURCES)
+ .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_ID)
+
+ appDataSharingUpdatesPrivacySource.rescanAndPushSafetyCenterData(
+ context, refreshIntent, EVENT_REFRESH_REQUESTED)
+
+ val expectedSafetySourceData: SafetySourceData =
+ SafetySourceData.Builder()
+ .setStatus(
+ SafetySourceStatus.Builder(
+ DATA_SHARING_UPDATES_TITLE,
+ DATA_SHARING_UPDATES_SUMMARY,
+ SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED)
+ .setPendingIntent(
+ PendingIntent.getActivity(
+ context,
+ /* requestCode= */ 0,
+ Intent(ACTION_REVIEW_APP_DATA_SHARING_UPDATES),
+ FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE))
+ .build())
+ .build()
+ val expectedSafetyEvent =
+ SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId(REFRESH_ID)
+ .build()
+ verify(mockSafetyCenterManager)
+ .setSafetySourceData(
+ APP_DATA_SHARING_UPDATES_SOURCE_ID, expectedSafetySourceData, expectedSafetyEvent)
+ }
+
+ @Test
+ fun rescanAndPushSafetyCenterData_safetyLabelChangeNotificationsDisabled_setsNullData() {
+ setSafetyLabelChangeNotificationsEnabled(false)
+ val bootCompleteIntent = Intent(ACTION_BOOT_COMPLETED)
+
+ appDataSharingUpdatesPrivacySource.rescanAndPushSafetyCenterData(
+ context, bootCompleteIntent, EVENT_DEVICE_REBOOTED)
+
+ val expectedSafetyEvent = SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_REBOOTED).build()
+ verify(mockSafetyCenterManager)
+ .setSafetySourceData(APP_DATA_SHARING_UPDATES_SOURCE_ID, null, expectedSafetyEvent)
+ }
+
+ @Test
+ fun rescanAndPushSafetyCenterData_permissionRationaleDisabled_setsNullData() {
+ setPermissionRationaleEnabled(false)
+ val refreshIntent =
+ Intent(ACTION_REFRESH_SAFETY_SOURCES)
+ .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_ID)
+
+ appDataSharingUpdatesPrivacySource.rescanAndPushSafetyCenterData(
+ context, refreshIntent, EVENT_REFRESH_REQUESTED)
+
+ val expectedSafetyEvent =
+ SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId(REFRESH_ID)
+ .build()
+ verify(mockSafetyCenterManager)
+ .setSafetySourceData(APP_DATA_SHARING_UPDATES_SOURCE_ID, null, expectedSafetyEvent)
+ }
+
+ @Test
+ fun rescanAndPushSafetyCenterData_bothFeaturesDisabled_setsNullData() {
+ setPermissionRationaleEnabled(false)
+ setSafetyLabelChangeNotificationsEnabled(false)
+ val refreshIntent =
+ Intent(ACTION_REFRESH_SAFETY_SOURCES)
+ .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_ID)
+
+ appDataSharingUpdatesPrivacySource.rescanAndPushSafetyCenterData(
+ context, refreshIntent, EVENT_REFRESH_REQUESTED)
+
+ val expectedSafetyEvent =
+ SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
+ .setRefreshBroadcastId(REFRESH_ID)
+ .build()
+ verify(mockSafetyCenterManager)
+ .setSafetySourceData(APP_DATA_SHARING_UPDATES_SOURCE_ID, null, expectedSafetyEvent)
+ }
+
+ /** Companion object for [AppDataSharingUpdatesPrivacySourceTest]. */
+ companion object {
+ // Real context, used in order to avoid mocking resources.
+ var context: Context = ApplicationProvider.getApplicationContext()
+ const val DATA_SHARING_UPDATES_TITLE: String = "Data sharing updates"
+ const val DATA_SHARING_UPDATES_SUMMARY: String =
+ "Review apps that changed the way they share location data."
+ const val REFRESH_ID: String = "refresh_id"
+
+ /**
+ * Sets the value for the Safety Label Change Notifications feature [DeviceConfig] property.
+ */
+ private fun setSafetyLabelChangeNotificationsEnabled(enabled: Boolean) {
+ whenever(
+ DeviceConfig.getBoolean(
+ eq(NAMESPACE_PRIVACY),
+ eq(SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED),
+ anyBoolean()))
+ .thenReturn(enabled)
+ }
+
+ /** Sets the value for the Permission Rationale feature [DeviceConfig] property. */
+ private fun setPermissionRationaleEnabled(enabled: Boolean) {
+ whenever(
+ DeviceConfig.getBoolean(
+ eq(NAMESPACE_PRIVACY), eq(PERMISSION_RATIONALE_ENABLED), anyBoolean()))
+ .thenReturn(enabled)
+ }
+ }
+}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/SafetyCenterReceiverTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/SafetyCenterReceiverTest.kt
index 72c8716ec..b4b1abd96 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/SafetyCenterReceiverTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/SafetyCenterReceiverTest.kt
@@ -38,14 +38,17 @@ import com.android.permissioncontroller.privacysources.PrivacySource
import com.android.permissioncontroller.privacysources.SafetyCenterReceiver
import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.EVENT_DEVICE_REBOOTED
import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.EVENT_REFRESH_REQUESTED
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runBlockingTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers
@@ -75,7 +78,7 @@ class SafetyCenterReceiverTest {
val application = Mockito.mock(PermissionControllerApplication::class.java)
}
- private val testCoroutineDispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
+ private val testCoroutineDispatcher = TestCoroutineDispatcher()
@Mock
lateinit var mockSafetyCenterManager: SafetyCenterManager
@@ -226,4 +229,4 @@ class SafetyCenterReceiverTest {
verifyZeroInteractions(mockPrivacySource)
verifyZeroInteractions(mockPrivacySource2)
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt
index b8bc2adf9..0989903d0 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/role/model/RoleParserTest.kt
@@ -18,16 +18,26 @@ package com.android.permissioncontroller.tests.mocking.role.model
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
-import com.android.permissioncontroller.role.model.RoleParser
+import com.android.permissioncontroller.role.model.RoleParserInitializer
+import com.android.role.controller.model.RoleParser
+import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RoleParserTest {
+ companion object {
+ @BeforeClass
+ @JvmStatic
+ fun setupBeforeClass() {
+ RoleParserInitializer.initialize()
+ }
+ }
+
private val targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext()
@Test
fun testParseRolesWithValidation() {
RoleParser(targetContext, true).parse()
}
-} \ No newline at end of file
+}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/OWNERS b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/OWNERS
new file mode 100644
index 000000000..5d8b8161b
--- /dev/null
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1026964
+
+include /SafetyCenter/OWNERS
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/StatusUiDataTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/StatusUiDataTest.kt
new file mode 100644
index 000000000..96fc5ee74
--- /dev/null
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetycenter/ui/model/StatusUiDataTest.kt
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.tests.mocking.safetycenter.ui.model
+
+import android.content.Context
+import android.os.Build
+import android.safetycenter.SafetyCenterData
+import android.safetycenter.SafetyCenterIssue
+import android.safetycenter.SafetyCenterStatus
+import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING
+import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK
+import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION
+import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN
+import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS
+import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS
+import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_NONE
+import androidx.test.filters.SdkSuppress
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SdkSuppress(maxSdkVersion = Build.VERSION_CODES.TIRAMISU)
+class StatusUiDataTest {
+
+ @Mock private lateinit var mockContext: Context
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun copyForPendingActions_setsCorrectPendingActionsValue() {
+ val copiedWithPendingActions =
+ StatusUiData(STATUS, hasPendingActions = false).copyForPendingActions(true)
+ val copiedWithoutPendingActions =
+ StatusUiData(STATUS, hasPendingActions = true).copyForPendingActions(false)
+
+ assertThat(copiedWithPendingActions.hasPendingActions).isTrue()
+ assertThat(copiedWithoutPendingActions.hasPendingActions).isFalse()
+ }
+
+ @Test
+ fun getTitle_returnsTitle() {
+ assertThat(StatusUiData(STATUS).title).isEqualTo(STATUS.title)
+ assertThat(StatusUiData(ANOTHER_STATUS).title).isEqualTo(ANOTHER_STATUS.title)
+ assertThat(StatusUiData(SafetyCenterData(STATUS, listOf(), listOf(), listOf())).title)
+ .isEqualTo(STATUS.title)
+ }
+
+ @Test
+ fun getOriginalSummary_returnsOriginalSummary() {
+ assertThat(StatusUiData(STATUS).originalSummary).isEqualTo(STATUS.summary)
+ assertThat(StatusUiData(ANOTHER_STATUS).originalSummary).isEqualTo(ANOTHER_STATUS.summary)
+ assertThat(
+ StatusUiData(SafetyCenterData(STATUS, listOf(), listOf(), listOf()))
+ .originalSummary)
+ .isEqualTo(STATUS.summary)
+ }
+
+ @Test
+ fun getSeverityLevel_returnsSeverityLevel() {
+ assertThat(StatusUiData(STATUS).severityLevel).isEqualTo(STATUS.severityLevel)
+ assertThat(StatusUiData(ANOTHER_STATUS).severityLevel)
+ .isEqualTo(ANOTHER_STATUS.severityLevel)
+ assertThat(
+ StatusUiData(SafetyCenterData(STATUS, listOf(), listOf(), listOf())).severityLevel)
+ .isEqualTo(STATUS.severityLevel)
+ }
+
+ @Test
+ fun getSummary_withoutPendingActions_returnsOriginalSummary() {
+ val dataWithoutPendingActions = StatusUiData(STATUS, hasPendingActions = false)
+
+ val actualSummary = dataWithoutPendingActions.getSummary(mockContext)
+
+ assertThat(actualSummary).isEqualTo(dataWithoutPendingActions.originalSummary)
+ }
+
+ @Test
+ fun getSummary_withPendingActions_returnsQsSummary() {
+ val expectedSummary = "a quick settings summary"
+ whenever(mockContext.getString(R.string.safety_center_qs_status_summary))
+ .thenReturn(expectedSummary)
+
+ val actualSummary = StatusUiData(STATUS, hasPendingActions = true).getSummary(mockContext)
+
+ assertThat(actualSummary).isEqualTo(expectedSummary)
+ }
+
+ @Test
+ fun getContentDescription_returnsContentDescription() {
+ val expectedContentDescription = "a content description"
+ whenever(
+ mockContext.getString(
+ R.string.safety_status_preference_title_and_summary_content_description,
+ STATUS.title,
+ STATUS.summary))
+ .thenReturn(expectedContentDescription)
+
+ val actualContentDescription = StatusUiData(STATUS).getContentDescription(mockContext)
+
+ assertThat(actualContentDescription).isEqualTo(expectedContentDescription)
+ }
+
+ @Test
+ fun fromSafetyCenterData_withIssues_hasIssuesIsTrue() {
+ assertThat(StatusUiData(DATA_WITH_ISSUES).hasIssues).isTrue()
+ }
+
+ @Test
+ fun fromSafetyCenterData_withoutIssues_hasIssuesIsFalse() {
+ assertThat(StatusUiData(DATA_WITHOUT_ISSUES).hasIssues).isFalse()
+ }
+
+ @Test
+ fun hasPendingActions_defaultsFalse() {
+ assertThat(StatusUiData(STATUS).hasPendingActions).isFalse()
+ assertThat(StatusUiData(DATA_WITH_ISSUES).hasPendingActions).isFalse()
+ }
+
+ @Test
+ fun getStatusImageResId_severityOk() {
+ assertThat(uiDataForSeverity(OVERALL_SEVERITY_LEVEL_OK).statusImageResId)
+ .isEqualTo(R.drawable.safety_status_info)
+ }
+
+ @Test
+ fun getStatusImageResId_severityUnknown() {
+ assertThat(uiDataForSeverity(OVERALL_SEVERITY_LEVEL_UNKNOWN).statusImageResId)
+ .isEqualTo(R.drawable.safety_status_info)
+ }
+ @Test
+ fun getStatusImageResId_severityRecommendation() {
+ assertThat(uiDataForSeverity(OVERALL_SEVERITY_LEVEL_RECOMMENDATION).statusImageResId)
+ .isEqualTo(R.drawable.safety_status_recommendation)
+ }
+ @Test
+ fun getStatusImageResId_severityWarning() {
+ assertThat(uiDataForSeverity(OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING).statusImageResId)
+ .isEqualTo(R.drawable.safety_status_warn)
+ }
+
+ @Test
+ fun isRefreshInProgress_dataFetch_isTrue() {
+ assertThat(
+ uiDataForRefreshStatus(REFRESH_STATUS_DATA_FETCH_IN_PROGRESS).isRefreshInProgress)
+ .isTrue()
+ }
+
+ @Test
+ fun isRefreshInProgress_fullRescan_isTrue() {
+ assertThat(
+ uiDataForRefreshStatus(REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS).isRefreshInProgress)
+ .isTrue()
+ }
+
+ @Test
+ fun isRefreshInProgress_none_isFalse() {
+ assertThat(uiDataForRefreshStatus(REFRESH_STATUS_NONE).isRefreshInProgress).isFalse()
+ }
+
+ @Test
+ fun shouldShowRescanButton_severityOk_noIssues_noPendingActions_isTrue() {
+ assertThat(
+ StatusUiData(
+ statusForSeverity(OVERALL_SEVERITY_LEVEL_OK),
+ hasIssues = false,
+ hasPendingActions = false)
+ .shouldShowRescanButton())
+ .isTrue()
+ }
+
+ @Test
+ fun shouldShowRescanButton_severityUnknown_noIssues_noPendingActions_isTrue() {
+ assertThat(
+ StatusUiData(
+ statusForSeverity(OVERALL_SEVERITY_LEVEL_UNKNOWN),
+ hasIssues = false,
+ hasPendingActions = false)
+ .shouldShowRescanButton())
+ .isTrue()
+ }
+
+ @Test
+ fun shouldShowRescanButton_hasIssues_isFalse() {
+ assertThat(
+ StatusUiData(
+ statusForSeverity(OVERALL_SEVERITY_LEVEL_OK),
+ hasIssues = true,
+ hasPendingActions = false)
+ .shouldShowRescanButton())
+ .isFalse()
+ }
+
+ @Test
+ fun shouldShowRescanButton_hasPendingActions_isFalse() {
+ assertThat(
+ StatusUiData(
+ statusForSeverity(OVERALL_SEVERITY_LEVEL_OK),
+ hasIssues = false,
+ hasPendingActions = true)
+ .shouldShowRescanButton())
+ .isFalse()
+ }
+
+ @Test
+ fun shouldShowRescanButton_severityNotOkOrUnknown_isFalse() {
+ for (severity in
+ listOf(
+ OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING, OVERALL_SEVERITY_LEVEL_RECOMMENDATION)) {
+ assertThat(
+ StatusUiData(
+ statusForSeverity(severity),
+ hasIssues = false,
+ hasPendingActions = false)
+ .shouldShowRescanButton())
+ .isFalse()
+ }
+ }
+
+ private companion object {
+ val STATUS =
+ SafetyCenterStatus.Builder("a title", "a summary")
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_OK)
+ .setRefreshStatus(REFRESH_STATUS_NONE)
+ .build()
+
+ val ANOTHER_STATUS =
+ SafetyCenterStatus.Builder("another title", "another summary")
+ .setSeverityLevel(OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
+ .setRefreshStatus(REFRESH_STATUS_DATA_FETCH_IN_PROGRESS)
+ .build()
+
+ val ISSUE = SafetyCenterIssue.Builder("iSsUe_Id", "issue title", "issue summary").build()
+
+ val DATA_WITH_ISSUES = SafetyCenterData(STATUS, listOf(ISSUE), listOf(), listOf())
+ val DATA_WITHOUT_ISSUES = SafetyCenterData(STATUS, listOf(), listOf(), listOf())
+
+ fun statusForSeverity(severityLevel: Int) =
+ SafetyCenterStatus.Builder(STATUS).setSeverityLevel(severityLevel).build()
+
+ fun uiDataForSeverity(severityLevel: Int) = StatusUiData(statusForSeverity(severityLevel))
+
+ fun uiDataForRefreshStatus(refreshStatus: Int) =
+ StatusUiData(SafetyCenterStatus.Builder(STATUS).setRefreshStatus(refreshStatus).build())
+ }
+}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/AppsSafetyLabelHistoryPersistenceTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/AppsSafetyLabelHistoryPersistenceTest.kt
new file mode 100644
index 000000000..ec53173be
--- /dev/null
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/AppsSafetyLabelHistoryPersistenceTest.kt
@@ -0,0 +1,310 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.tests.mocking.safetylabel
+
+import android.content.Context
+import android.os.Build
+import android.provider.DeviceConfig
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SdkSuppress
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.AppInfo
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.AppSafetyLabelDiff
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.AppSafetyLabelHistory
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.DataCategory
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.DataLabel
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistory.SafetyLabel
+import com.android.permissioncontroller.safetylabel.AppsSafetyLabelHistoryPersistence
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.time.ZonedDateTime
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.MockitoAnnotations
+import org.mockito.MockitoSession
+import org.mockito.quality.Strictness
+
+/** Tests for [AppsSafetyLabelHistoryPersistence]. */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class AppsSafetyLabelHistoryPersistenceTest {
+ private lateinit var context: Context
+ private lateinit var dataFile: File
+ private lateinit var mockitoSession: MockitoSession
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context = ApplicationProvider.getApplicationContext()
+ dataFile = context.getFileStreamPath(TEST_FILE_NAME)
+ mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .mockStatic(DeviceConfig::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ }
+
+ @After
+ fun cleanup() {
+ context.deleteFile(TEST_FILE_NAME)
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun read_afterDeleted_returnsNull() {
+ val appsSafetyLabelHistory =
+ AppsSafetyLabelHistory(
+ listOf(),
+ )
+ AppsSafetyLabelHistoryPersistence.write(dataFile, appsSafetyLabelHistory)
+ AppsSafetyLabelHistoryPersistence.clear(dataFile)
+
+ assertThat(AppsSafetyLabelHistoryPersistence.read(dataFile)).isEqualTo(null)
+ }
+
+ @Test
+ fun read_afterWrite_noHistory_returnsIdenticalAppsSafetyLabelHistory() {
+ val appsSafetyLabelHistory =
+ AppsSafetyLabelHistory(
+ listOf(),
+ )
+ AppsSafetyLabelHistoryPersistence.write(dataFile, appsSafetyLabelHistory)
+
+ assertThat(AppsSafetyLabelHistoryPersistence.read(dataFile))
+ .isEqualTo(appsSafetyLabelHistory)
+ }
+
+ @Test
+ fun read_afterWrite_noSharing_returnsIdenticalAppsSafetyLabelHistory() {
+ val appsSafetyLabelHistory =
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(AppInfo(PACKAGE_NAME_2), listOf(SAFETY_LABEL_PKG_2_V2))))
+ AppsSafetyLabelHistoryPersistence.write(dataFile, appsSafetyLabelHistory)
+
+ assertThat(AppsSafetyLabelHistoryPersistence.read(dataFile))
+ .isEqualTo(appsSafetyLabelHistory)
+ }
+
+ @Test
+ fun read_afterWrite_returnsIdenticalAppsSafetyLabelHistory() {
+ val appsSafetyLabelHistory =
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(AppInfo(PACKAGE_NAME_1), listOf(SAFETY_LABEL_PKG_1_V1)),
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_2),
+ listOf(SAFETY_LABEL_PKG_2_V1, SAFETY_LABEL_PKG_2_V2))))
+ AppsSafetyLabelHistoryPersistence.write(dataFile, appsSafetyLabelHistory)
+
+ assertThat(AppsSafetyLabelHistoryPersistence.read(dataFile))
+ .isEqualTo(appsSafetyLabelHistory)
+ }
+
+ @Test
+ fun recordSafetyLabel_noAppsHistory_addsAppsHistory() {
+ AppsSafetyLabelHistoryPersistence.clear(dataFile)
+
+ AppsSafetyLabelHistoryPersistence.recordSafetyLabel(SAFETY_LABEL_PKG_1_V1, dataFile)
+
+ assertThat(AppsSafetyLabelHistoryPersistence.read(dataFile))
+ .isEqualTo(
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_1), listOf(SAFETY_LABEL_PKG_1_V1)))))
+ }
+
+ @Test
+ fun recordSafetyLabel_noAppHistory_addsAppHistory() {
+ val appsSafetyLabelHistory =
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(AppInfo(PACKAGE_NAME_1), listOf(SAFETY_LABEL_PKG_1_V1))))
+ AppsSafetyLabelHistoryPersistence.write(dataFile, appsSafetyLabelHistory)
+
+ AppsSafetyLabelHistoryPersistence.recordSafetyLabel(SAFETY_LABEL_PKG_2_V1, dataFile)
+
+ assertThat(AppsSafetyLabelHistoryPersistence.read(dataFile))
+ .isEqualTo(
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_1), listOf(SAFETY_LABEL_PKG_1_V1)),
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_2), listOf(SAFETY_LABEL_PKG_2_V1)))))
+ }
+
+ @Test
+ fun recordSafetyLabel_existingAppHistory_addsToHistory() {
+ val appsSafetyLabelHistory =
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(AppInfo(PACKAGE_NAME_1), listOf(SAFETY_LABEL_PKG_1_V1)),
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_2),
+ listOf(SAFETY_LABEL_PKG_2_V1, SAFETY_LABEL_PKG_2_V2))))
+ AppsSafetyLabelHistoryPersistence.write(dataFile, appsSafetyLabelHistory)
+
+ AppsSafetyLabelHistoryPersistence.recordSafetyLabel(SAFETY_LABEL_PKG_2_V3, dataFile)
+
+ assertThat(AppsSafetyLabelHistoryPersistence.read(dataFile))
+ .isEqualTo(
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_1), listOf(SAFETY_LABEL_PKG_1_V1)),
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_2),
+ listOf(
+ SAFETY_LABEL_PKG_2_V1,
+ SAFETY_LABEL_PKG_2_V2,
+ SAFETY_LABEL_PKG_2_V3)))))
+ }
+
+ @Test
+ fun recordSafetyLabel_noChangeToLastLabel_doesNothing() {
+ val appsSafetyLabelHistory =
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(AppInfo(PACKAGE_NAME_1), listOf(SAFETY_LABEL_PKG_1_V2)),
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_2),
+ listOf(SAFETY_LABEL_PKG_2_V1, SAFETY_LABEL_PKG_2_V2))))
+ AppsSafetyLabelHistoryPersistence.write(dataFile, appsSafetyLabelHistory)
+
+ AppsSafetyLabelHistoryPersistence.recordSafetyLabel(SAFETY_LABEL_PKG_1_V3, dataFile)
+
+ assertThat(AppsSafetyLabelHistoryPersistence.read(dataFile))
+ .isEqualTo(appsSafetyLabelHistory)
+ }
+
+ @Test
+ fun getAppSafetyLabelDiffs_whenNoHistory_returnsEmpty() {
+ AppsSafetyLabelHistoryPersistence.clear(dataFile)
+
+ val safetyLabelDiffs: List<AppSafetyLabelDiff> =
+ AppsSafetyLabelHistoryPersistence.getAppSafetyLabelDiffs(DATE_2022_12_10, dataFile)
+
+ assertThat(safetyLabelDiffs).isEqualTo(listOf<AppSafetyLabelDiff>())
+ }
+
+ @Test
+ fun getAppSafetyLabelDiffs_whenNoSafetyLabelChangesSinceStartTime_returnsEmpty() {
+ val appsSafetyLabelHistory =
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(AppInfo(PACKAGE_NAME_1), listOf(SAFETY_LABEL_PKG_1_V1))))
+ AppsSafetyLabelHistoryPersistence.write(dataFile, appsSafetyLabelHistory)
+
+ val safetyLabelDiffs: List<AppSafetyLabelDiff> =
+ AppsSafetyLabelHistoryPersistence.getAppSafetyLabelDiffs(DATE_2022_10_14, dataFile)
+
+ assertThat(safetyLabelDiffs).isEqualTo(listOf<AppSafetyLabelDiff>())
+ }
+
+ @Test
+ fun getAppSafetyLabelDiffs_whenNoSafetyLabelsSinceStartTime_returnsEmpty() {
+ val appsSafetyLabelHistory =
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_1),
+ listOf(SAFETY_LABEL_PKG_1_V1, SAFETY_LABEL_PKG_1_V2))))
+ AppsSafetyLabelHistoryPersistence.write(dataFile, appsSafetyLabelHistory)
+
+ val safetyLabelDiffs: List<AppSafetyLabelDiff> =
+ AppsSafetyLabelHistoryPersistence.getAppSafetyLabelDiffs(DATE_2022_12_10, dataFile)
+
+ assertThat(safetyLabelDiffs).isEqualTo(listOf<AppSafetyLabelDiff>())
+ }
+
+ @Test
+ fun getAppSafetyLabelDiffs_returnsAvailableDiffs() {
+ val appsSafetyLabelHistory =
+ AppsSafetyLabelHistory(
+ listOf(
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_1),
+ listOf(SAFETY_LABEL_PKG_1_V1, SAFETY_LABEL_PKG_1_V2)),
+ AppSafetyLabelHistory(
+ AppInfo(PACKAGE_NAME_2),
+ listOf(
+ SAFETY_LABEL_PKG_2_V1, SAFETY_LABEL_PKG_2_V2, SAFETY_LABEL_PKG_2_V3))))
+ AppsSafetyLabelHistoryPersistence.write(dataFile, appsSafetyLabelHistory)
+
+ val safetyLabelDiffs =
+ AppsSafetyLabelHistoryPersistence.getAppSafetyLabelDiffs(DATE_2022_10_12, dataFile)
+
+ assertThat(safetyLabelDiffs)
+ .isEqualTo(
+ listOf(
+ AppSafetyLabelDiff(SAFETY_LABEL_PKG_1_V1, SAFETY_LABEL_PKG_1_V2),
+ AppSafetyLabelDiff(SAFETY_LABEL_PKG_2_V1, SAFETY_LABEL_PKG_2_V3)))
+ }
+
+ companion object {
+ private const val TEST_FILE_NAME = "test_safety_label_history_file"
+ private const val PACKAGE_NAME_1 = "package_name_1"
+ private const val PACKAGE_NAME_2 = "package_name_2"
+ private const val LOCATION_CATEGORY = "location"
+ private const val FINANCIAL_CATEGORY = "financial"
+ private val DATE_2022_09_01 = ZonedDateTime.parse("2022-09-01T00:00:00.000Z").toInstant()
+ private val DATE_2022_10_10 = ZonedDateTime.parse("2022-10-10T00:00:00.000Z").toInstant()
+ private val DATE_2022_10_12 = ZonedDateTime.parse("2022-10-12T00:00:00.000Z").toInstant()
+ private val DATE_2022_10_14 = ZonedDateTime.parse("2022-10-14T00:00:00.000Z").toInstant()
+ private val DATE_2022_12_10 = ZonedDateTime.parse("2022-12-10T00:00:00.000Z").toInstant()
+ private val DATE_2022_12_30 = ZonedDateTime.parse("2022-12-30T00:00:00.000Z").toInstant()
+
+ private val SAFETY_LABEL_PKG_1_V1 =
+ SafetyLabel(
+ AppInfo(PACKAGE_NAME_1),
+ DATE_2022_09_01,
+ DataLabel(mapOf(LOCATION_CATEGORY to DataCategory(true))))
+
+ private val SAFETY_LABEL_PKG_1_V2 =
+ SafetyLabel(
+ AppInfo(PACKAGE_NAME_1),
+ DATE_2022_10_14,
+ DataLabel(mapOf(LOCATION_CATEGORY to DataCategory(false))))
+
+ private val SAFETY_LABEL_PKG_1_V3 =
+ SafetyLabel(
+ AppInfo(PACKAGE_NAME_1),
+ DATE_2022_12_10,
+ DataLabel(mapOf(LOCATION_CATEGORY to DataCategory(false))))
+
+ private val SAFETY_LABEL_PKG_2_V1 =
+ SafetyLabel(
+ AppInfo(PACKAGE_NAME_2),
+ DATE_2022_10_10,
+ DataLabel(
+ mapOf(
+ LOCATION_CATEGORY to DataCategory(true),
+ FINANCIAL_CATEGORY to DataCategory(false))))
+
+ private val SAFETY_LABEL_PKG_2_V2 =
+ SafetyLabel(AppInfo(PACKAGE_NAME_2), DATE_2022_12_10, DataLabel(mapOf()))
+
+ private val SAFETY_LABEL_PKG_2_V3 =
+ SafetyLabel(
+ AppInfo(PACKAGE_NAME_2),
+ DATE_2022_12_30,
+ DataLabel(mapOf(FINANCIAL_CATEGORY to DataCategory(true))))
+ }
+}
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/SafetyLabelChangesJobServiceTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/SafetyLabelChangesJobServiceTest.kt
new file mode 100644
index 000000000..9297c68f2
--- /dev/null
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/SafetyLabelChangesJobServiceTest.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.tests.mocking.safetylabel
+
+import android.app.Notification
+import android.app.NotificationManager
+import android.app.job.JobInfo
+import android.app.job.JobParameters
+import android.app.job.JobScheduler
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.provider.DeviceConfig
+import android.safetylabel.SafetyLabelConstants
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SdkSuppress
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.permissioncontroller.Constants
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.service.v34.SafetyLabelChangesJobService
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+import org.mockito.MockitoSession
+import org.mockito.Spy
+import org.mockito.quality.Strictness
+
+/** Tests for [SafetyLabelChangesJobService]. */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class SafetyLabelChangesJobServiceTest {
+ @Spy private val service = SafetyLabelChangesJobService()
+ private val receiver = SafetyLabelChangesJobService.Receiver()
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private lateinit var mockitoSession: MockitoSession
+
+ @Mock private lateinit var application: PermissionControllerApplication
+
+ @Mock private lateinit var mockJobScheduler: JobScheduler
+
+ @Mock private lateinit var mockNotificationManager: NotificationManager
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .mockStatic(DeviceConfig::class.java)
+ .mockStatic(PermissionControllerApplication::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+
+ // Mock flags
+ setSafetyLabelChangeNotificationsEnabled(true)
+ setPermissionRationaleEnabled(true)
+
+ // Mock application context
+ whenever(PermissionControllerApplication.get()).thenReturn(application)
+ whenever(application.resources).thenReturn(context.resources)
+ whenever(application.applicationInfo).thenReturn(context.applicationInfo)
+
+ // Mock services
+ whenever(application.getSystemService(eq(NotificationManager::class.java)))
+ .thenReturn(mockNotificationManager)
+ whenever(application.getSystemService(eq(JobScheduler::class.java)))
+ .thenReturn(mockJobScheduler)
+ doNothing().`when`(service).jobFinished(any(), anyBoolean())
+ }
+
+ @After
+ fun cleanup() {
+ mockitoSession.finishMocking()
+ }
+
+ @Test
+ fun flagsDisabled_onReceiveValidIntentAction_jobNotScheduled() {
+ setSafetyLabelChangeNotificationsEnabled(false)
+
+ receiver.onReceive(application, Intent(Intent.ACTION_BOOT_COMPLETED))
+
+ verifyZeroInteractions(mockJobScheduler)
+ }
+
+ @Test
+ fun flagsDisabled_onMainJobStart_notificationNotShown() {
+ setSafetyLabelChangeNotificationsEnabled(false)
+
+ val jobId = mockJobParamsForJobId(Constants.SAFETY_LABEL_CHANGES_JOB_ID)
+ val jobStillRunning = service.onStartJob(jobId)
+
+ assertThat(jobStillRunning).isEqualTo(false)
+
+ verifyZeroInteractions(mockNotificationManager)
+ }
+
+ @Test
+ fun onReceiveInvalidIntentAction_jobNotScheduled() {
+ receiver.onReceive(application, Intent(Intent.ACTION_DEFAULT))
+
+ verifyZeroInteractions(mockJobScheduler)
+ }
+
+ @Test
+ fun onReceiveValidIntentAction_periodicJobScheduled() {
+ receiver.onReceive(application, Intent(Intent.ACTION_BOOT_COMPLETED))
+
+ val captor = ArgumentCaptor.forClass(JobInfo::class.java)
+ verify(mockJobScheduler).schedule(captor.capture())
+ assertThat(captor.value.id).isEqualTo(Constants.PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID)
+ }
+
+ @Test
+ fun onStartPeriodicJob_scheduleJob() {
+ val jobParams = mockJobParamsForJobId(Constants.PERIODIC_SAFETY_LABEL_CHANGES_JOB_ID)
+ val jobStillRunning = service.onStartJob(jobParams)
+
+ assertThat(jobStillRunning).isEqualTo(false)
+
+ val captor = ArgumentCaptor.forClass(JobInfo::class.java)
+ verify(mockJobScheduler, timeout(5000)).schedule(captor.capture())
+ assertThat(captor.value.id).isEqualTo(Constants.SAFETY_LABEL_CHANGES_JOB_ID)
+ }
+
+ @Test
+ fun onStartMainJob_notificationShown() {
+ val jobParams = mockJobParamsForJobId(Constants.SAFETY_LABEL_CHANGES_JOB_ID)
+ val jobStillRunning = service.onStartJob(jobParams)
+
+ assertThat(jobStillRunning).isEqualTo(true)
+ waitForJobFinished()
+
+ val captor = ArgumentCaptor.forClass(Notification::class.java)
+ verify(mockNotificationManager)
+ .notify(eq(Constants.SAFETY_LABEL_CHANGES_NOTIFICATION_ID), captor.capture())
+ // TODO(b/261662686): Assert notification title and content
+ }
+
+ private fun waitForJobFinished() {
+ verify(service, timeout(5000)).jobFinished(any(), anyBoolean())
+ }
+
+ private fun mockJobParamsForJobId(jobId: Int): JobParameters {
+ val jobParameters = mock(JobParameters::class.java)
+ whenever(jobParameters.jobId).thenReturn(jobId)
+ return jobParameters
+ }
+
+ private fun setSafetyLabelChangeNotificationsEnabled(flagValue: Boolean) {
+ whenever(
+ DeviceConfig.getBoolean(
+ eq(DeviceConfig.NAMESPACE_PRIVACY),
+ eq(SafetyLabelConstants.SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED),
+ anyBoolean()))
+ .thenReturn(flagValue)
+ }
+
+ private fun setPermissionRationaleEnabled(flagValue: Boolean) {
+ whenever(
+ DeviceConfig.getBoolean(
+ eq(DeviceConfig.NAMESPACE_PRIVACY),
+ eq(SafetyLabelConstants.PERMISSION_RATIONALE_ENABLED),
+ anyBoolean()))
+ .thenReturn(flagValue)
+ }
+} \ No newline at end of file
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/TEST_MAPPING b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/TEST_MAPPING
new file mode 100644
index 000000000..e1ddb5a4a
--- /dev/null
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/safetylabel/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "PermissionControllerMockingTests",
+ "options": [
+ {
+ "include-filter": "com.android.permissioncontroller.tests.mocking.safetylabel.AppsSafetyLabelHistoryPersistenceTest"
+ }
+ ]
+ }
+ ]
+}
diff --git a/PermissionController/tests/outofprocess/Android.bp b/PermissionController/tests/outofprocess/Android.bp
index 806c8ecd7..e646def28 100644
--- a/PermissionController/tests/outofprocess/Android.bp
+++ b/PermissionController/tests/outofprocess/Android.bp
@@ -51,6 +51,7 @@ android_test {
proto: {
type: "lite",
+ include_dirs: ["packages/modules/Permission/PermissionController/src/com/android/permissioncontroller"],
},
test_suites: [
diff --git a/PermissionController/tests/permissionui/Android.bp b/PermissionController/tests/permissionui/Android.bp
index 6d5c91043..704307c1f 100644
--- a/PermissionController/tests/permissionui/Android.bp
+++ b/PermissionController/tests/permissionui/Android.bp
@@ -45,25 +45,24 @@ android_test {
"androidx.test.rules",
"androidx.test.ext.truth",
"androidx.test.ext.junit",
- "androidx.test.uiautomator",
+ "androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt",
"permission-test-util-lib",
],
test_suites: [
"device-tests",
+ "general-tests",
"mts-permission",
],
- required: [
- "CtsAppThatRequestsLocationPermission29",
- ],
-
data: [
+ ":CtsAppThatRequestsLocationPermission29",
":PermissionUiUseStoragePermissionApp",
":PermissionUiUseCameraPermissionApp",
":PermissionUiDefineAdditionalPermissionApp",
":PermissionUiUseAdditionalPermissionApp",
":PermissionUiUseTwoAdditionalPermissionsApp",
- ]
+ ],
+ per_testcase_directory: true,
}
diff --git a/PermissionController/tests/permissionui/AndroidTest.xml b/PermissionController/tests/permissionui/AndroidTest.xml
index 7c759ed29..6e0bf1a38 100644
--- a/PermissionController/tests/permissionui/AndroidTest.xml
+++ b/PermissionController/tests/permissionui/AndroidTest.xml
@@ -21,6 +21,7 @@
<option name="test-suite-tag" value="apct-instrumentation" />
<option name="test-tag" value="PermissionUiTestCases" />
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.permission.apex" />
<!-- Install test -->
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/SafetyCenter/Config/Android.bp b/SafetyCenter/Config/Android.bp
index 6615dd1c4..78480bf55 100644
--- a/SafetyCenter/Config/Android.bp
+++ b/SafetyCenter/Config/Android.bp
@@ -36,6 +36,7 @@ java_library {
"androidx.annotation_annotation",
"framework-annotations-lib",
"framework-permission-s",
+ "modules-utils-build",
],
apex_available: [
"com.android.permission",
diff --git a/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java b/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java
index 8081ae472..f7faf9e27 100644
--- a/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java
+++ b/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java
@@ -37,6 +37,8 @@ import android.safetycenter.config.SafetySourcesGroup;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
@@ -58,6 +60,7 @@ public final class SafetyCenterConfigParser {
private static final String ATTR_SAFETY_SOURCES_GROUP_TITLE = "title";
private static final String ATTR_SAFETY_SOURCES_GROUP_SUMMARY = "summary";
private static final String ATTR_SAFETY_SOURCES_GROUP_STATELESS_ICON_TYPE = "statelessIconType";
+ private static final String ATTR_SAFETY_SOURCES_GROUP_TYPE = "type";
private static final String ATTR_SAFETY_SOURCE_ID = "id";
private static final String ATTR_SAFETY_SOURCE_PACKAGE_NAME = "packageName";
private static final String ATTR_SAFETY_SOURCE_TITLE = "title";
@@ -71,8 +74,14 @@ public final class SafetyCenterConfigParser {
private static final String ATTR_SAFETY_SOURCE_LOGGING_ALLOWED = "loggingAllowed";
private static final String ATTR_SAFETY_SOURCE_REFRESH_ON_PAGE_OPEN_ALLOWED =
"refreshOnPageOpenAllowed";
+ private static final String ATTR_SAFETY_SOURCE_NOTIFICATIONS_ALLOWED = "notificationsAllowed";
+ private static final String ATTR_SAFETY_SOURCE_DEDUPLICATION_GROUP = "deduplicationGroup";
+ private static final String ATTR_SAFETY_SOURCE_PACKAGE_CERT_HASHES = "packageCertificateHashes";
private static final String ENUM_STATELESS_ICON_TYPE_NONE = "none";
private static final String ENUM_STATELESS_ICON_TYPE_PRIVACY = "privacy";
+ private static final String ENUM_GROUP_TYPE_STATEFUL = "stateful";
+ private static final String ENUM_GROUP_TYPE_STATELESS = "stateless";
+ private static final String ENUM_GROUP_TYPE_HIDDEN = "hidden";
private static final String ENUM_PROFILE_PRIMARY = "primary_profile_only";
private static final String ENUM_PROFILE_ALL = "all_profiles";
private static final String ENUM_INITIAL_DISPLAY_STATE_ENABLED = "enabled";
@@ -183,6 +192,18 @@ public final class SafetyCenterConfigParser {
parser.getAttributeName(i),
resources));
break;
+ case ATTR_SAFETY_SOURCES_GROUP_TYPE:
+ if (SdkLevel.isAtLeastU()) {
+ builder.setType(
+ parseGroupType(
+ parser.getAttributeValue(i),
+ name,
+ parser.getAttributeName(i),
+ resources));
+ } else {
+ throw attributeUnexpected(name, parser.getAttributeName(i));
+ }
+ break;
default:
throw attributeUnexpected(name, parser.getAttributeName(i));
}
@@ -321,6 +342,46 @@ public final class SafetyCenterConfigParser {
parser.getAttributeName(i),
resources));
break;
+ case ATTR_SAFETY_SOURCE_NOTIFICATIONS_ALLOWED:
+ if (SdkLevel.isAtLeastU()) {
+ builder.setNotificationsAllowed(
+ parseBoolean(
+ parser.getAttributeValue(i),
+ name,
+ parser.getAttributeName(i),
+ resources));
+ } else {
+ throw attributeUnexpected(name, parser.getAttributeName(i));
+ }
+ break;
+ case ATTR_SAFETY_SOURCE_DEDUPLICATION_GROUP:
+ if (SdkLevel.isAtLeastU()) {
+ builder.setDeduplicationGroup(
+ parseStringResourceValue(
+ parser.getAttributeValue(i),
+ name,
+ parser.getAttributeName(i),
+ resources));
+ } else {
+ throw attributeUnexpected(name, parser.getAttributeName(i));
+ }
+ break;
+ case ATTR_SAFETY_SOURCE_PACKAGE_CERT_HASHES:
+ if (SdkLevel.isAtLeastU()) {
+ String commaSeparatedHashes =
+ parseStringResourceValue(
+ parser.getAttributeValue(i),
+ name,
+ parser.getAttributeName(i),
+ resources);
+ String[] splits = commaSeparatedHashes.split(",");
+ for (int j = 0; j < splits.length; j++) {
+ builder.addPackageCertificateHash(splits[j]);
+ }
+ } else {
+ throw attributeUnexpected(name, parser.getAttributeName(i));
+ }
+ break;
default:
throw attributeUnexpected(name, parser.getAttributeName(i));
}
@@ -501,6 +562,25 @@ public final class SafetyCenterConfigParser {
}
}
+ private static int parseGroupType(
+ @NonNull String valueString,
+ @NonNull String parent,
+ @NonNull String name,
+ @NonNull Resources resources)
+ throws ParseException {
+ String valueToParse = getValueToParse(valueString, parent, name, resources);
+ switch (valueToParse) {
+ case ENUM_GROUP_TYPE_STATEFUL:
+ return SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL;
+ case ENUM_GROUP_TYPE_STATELESS:
+ return SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS;
+ case ENUM_GROUP_TYPE_HIDDEN:
+ return SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN;
+ default:
+ throw attributeInvalid(valueToParse, parent, name);
+ }
+ }
+
private static int parseProfile(
@NonNull String valueString,
@NonNull String parent,
diff --git a/SafetyCenter/Config/tests/Android.bp b/SafetyCenter/Config/tests/Android.bp
index cca539f6c..9d9975980 100644
--- a/SafetyCenter/Config/tests/Android.bp
+++ b/SafetyCenter/Config/tests/Android.bp
@@ -25,9 +25,10 @@ android_test {
srcs: [
"java/**/*.kt",
],
- required: [
- "SafetyCenterConfigTestsOverlay",
+ data: [
+ ":SafetyCenterConfigTestsOverlay",
],
+ per_testcase_directory: true,
static_libs: [
"compatibility-device-util-axt",
"kotlinx-coroutines-android",
diff --git a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt
index daa2104a9..fe189dc7a 100644
--- a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt
+++ b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt
@@ -18,6 +18,7 @@ package com.android.safetycenter.config
import android.content.Context
import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import com.android.modules.utils.build.SdkLevel
import com.android.safetycenter.config.tests.R
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
@@ -33,7 +34,8 @@ class ParserConfigInvalidTest {
private val testName: String,
val configResourceId: Int,
val errorMessage: String,
- val causeErrorMessage: String?
+ val causeErrorMessage: String?,
+ val includeCondition: Boolean = true
) {
override fun toString() = testName
}
@@ -247,6 +249,20 @@ class ParserConfigInvalidTest {
"Element safety-sources-group invalid",
"Safety sources group empty"),
Params(
+ "ConfigSafetySourcesGroupHiddenWithOnlyDynamic",
+ R.raw.config_safety_sources_group_hidden_with_only_dynamic,
+ "Element safety-sources-group invalid",
+ "Safety sources groups of type hidden can only contain sources of type " +
+ "issue-only",
+ SdkLevel.isAtLeastU()),
+ Params(
+ "ConfigSafetySourcesGroupHiddenWithOnlyStatic",
+ R.raw.config_safety_sources_group_hidden_with_only_static,
+ "Element safety-sources-group invalid",
+ "Safety sources groups of type hidden can only contain sources of type " +
+ "issue-only",
+ SdkLevel.isAtLeastU()),
+ Params(
"ConfigSafetySourcesGroupInvalidIcon",
R.raw.config_safety_sources_group_invalid_icon,
"Attribute value \"invalid\" in safety-sources-group.statelessIconType invalid",
@@ -267,6 +283,20 @@ class ParserConfigInvalidTest {
"Element safety-sources-group invalid",
"Required attribute title missing"),
Params(
+ "ConfigSafetySourcesGroupStatefulWithOnlyIssueOnly",
+ R.raw.config_safety_sources_group_stateful_with_only_issue_only,
+ "Element safety-sources-group invalid",
+ "Safety sources groups containing only sources of type issue-only must be of " +
+ "type hidden",
+ SdkLevel.isAtLeastU()),
+ Params(
+ "ConfigSafetySourcesGroupStatelessWithOnlyIssueOnly",
+ R.raw.config_safety_sources_group_stateless_with_only_issue_only,
+ "Element safety-sources-group invalid",
+ "Safety sources groups containing only sources of type issue-only must be of " +
+ "type hidden",
+ SdkLevel.isAtLeastU()),
+ Params(
"ConfigStaticSafetySourceDuplicateKey",
R.raw.config_static_safety_source_duplicate_key,
"Element safety-sources-config invalid",
@@ -302,6 +332,12 @@ class ParserConfigInvalidTest {
"Element static-safety-source invalid",
"Required attribute title missing"),
Params(
+ "ConfigStaticSafetySourceWithDeduplicationGroups",
+ R.raw.config_static_safety_source_with_deduplication_groups,
+ "Element static-safety-source invalid",
+ "Prohibited attribute deduplicationGroup present",
+ SdkLevel.isAtLeastU()),
+ Params(
"ConfigStaticSafetySourceWithDisplay",
R.raw.config_static_safety_source_with_display,
"Element static-safety-source invalid",
@@ -312,10 +348,23 @@ class ParserConfigInvalidTest {
"Element static-safety-source invalid",
"Prohibited attribute loggingAllowed present"),
Params(
+ "ConfigStaticSafetySourceWithNotifications",
+ R.raw.config_static_safety_source_with_notifications,
+ "Element static-safety-source invalid",
+ "Prohibited attribute notificationsAllowed present",
+ SdkLevel.isAtLeastU()),
+ Params(
"ConfigStaticSafetySourceWithPackage",
R.raw.config_static_safety_source_with_package,
"Element static-safety-source invalid",
- "Prohibited attribute packageName present"),
+ "Prohibited attribute packageName present",
+ !SdkLevel.isAtLeastU()),
+ Params(
+ "ConfigStaticSafetySourceWithPackageCertficates",
+ R.raw.config_static_safety_source_with_package_certs,
+ "Element static-safety-source invalid",
+ "Prohibited attribute packageCertificateHashes present",
+ SdkLevel.isAtLeastU()),
Params(
"ConfigStaticSafetySourceWithPrimaryAndWork",
R.raw.config_static_safety_source_with_primary_and_work,
@@ -367,5 +416,6 @@ class ParserConfigInvalidTest {
"Resource name \"@com.android.safetycenter.config.tests:string/missing\" in " +
"safety-sources-group.title missing or invalid",
null))
+ .filter { it.includeCondition }
}
}
diff --git a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt
index 55ba8c985..8bf445b38 100644
--- a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt
+++ b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt
@@ -22,6 +22,7 @@ import android.safetycenter.config.SafetySource
import android.safetycenter.config.SafetySourcesGroup
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel
import com.android.safetycenter.config.tests.R
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -68,6 +69,14 @@ class ParserConfigValidTest {
.setSearchTermsResId(R.string.reference)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup("group")
+ addPackageCertificateHash("feed1")
+ addPackageCertificateHash("feed2")
+ }
+ }
.build())
.addSafetySource(
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
@@ -83,6 +92,14 @@ class ParserConfigValidTest {
.setSearchTermsResId(R.string.reference)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup("group")
+ addPackageCertificateHash("feed1")
+ addPackageCertificateHash("feed2")
+ }
+ }
.build())
.addSafetySource(
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
@@ -127,6 +144,7 @@ class ParserConfigValidTest {
.addSafetySource(
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_STATIC)
.setId("static_all_optional")
+ .apply { if (SdkLevel.isAtLeastU()) setPackageName("package") }
.setTitleResId(R.string.reference)
.setTitleForWorkResId(R.string.reference)
.setSummaryResId(R.string.reference)
@@ -152,6 +170,14 @@ class ParserConfigValidTest {
.setMaxSeverityLevel(300)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup("group")
+ addPackageCertificateHash("feed1")
+ addPackageCertificateHash("feed2")
+ }
+ }
.build())
.addSafetySource(
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY)
@@ -187,6 +213,94 @@ class ParserConfigValidTest {
.setProfile(SafetySource.PROFILE_PRIMARY)
.build())
.build())
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ addSafetySourcesGroup(
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL)
+ .setId("stateful_barebone")
+ .setTitleResId(R.string.reference)
+ .addSafetySource(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_STATIC)
+ .setId("stateful_barebone_source")
+ .setTitleResId(R.string.reference)
+ .setIntentAction("intent")
+ .setProfile(SafetySource.PROFILE_PRIMARY)
+ .build())
+ .build())
+ addSafetySourcesGroup(
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL)
+ .setId("stateful_all_optional")
+ .setTitleResId(R.string.reference)
+ .setSummaryResId(R.string.reference)
+ .setStatelessIconType(
+ SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ .addSafetySource(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_STATIC)
+ .setId("stateful_all_optional_source")
+ .setTitleResId(R.string.reference)
+ .setIntentAction("intent")
+ .setProfile(SafetySource.PROFILE_PRIMARY)
+ .build())
+ .build())
+ addSafetySourcesGroup(
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS)
+ .setId("stateless_barebone")
+ .setTitleResId(R.string.reference)
+ .addSafetySource(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_STATIC)
+ .setId("stateless_barebone_source")
+ .setTitleResId(R.string.reference)
+ .setIntentAction("intent")
+ .setProfile(SafetySource.PROFILE_PRIMARY)
+ .build())
+ .build())
+ addSafetySourcesGroup(
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS)
+ .setId("stateless_all_optional")
+ .setTitleResId(R.string.reference)
+ .setSummaryResId(R.string.reference)
+ .setStatelessIconType(
+ SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ .addSafetySource(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_STATIC)
+ .setId("stateless_all_optional_source")
+ .setTitleResId(R.string.reference)
+ .setIntentAction("intent")
+ .setProfile(SafetySource.PROFILE_PRIMARY)
+ .build())
+ .build())
+ addSafetySourcesGroup(
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ .setId("hidden_barebone")
+ .addSafetySource(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY)
+ .setId("hidden_barebone_source")
+ .setPackageName("package")
+ .setProfile(SafetySource.PROFILE_PRIMARY)
+ .build())
+ .build())
+ addSafetySourcesGroup(
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ .setId("hidden_all_optional")
+ .setTitleResId(R.string.reference)
+ .setSummaryResId(R.string.reference)
+ .setStatelessIconType(
+ SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ .addSafetySource(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY)
+ .setId("hidden_all_optional_source")
+ .setPackageName("package")
+ .setProfile(SafetySource.PROFILE_PRIMARY)
+ .build())
+ .build())
+ }
+ }
.build()
assertThat(actual).isEqualTo(expected)
}
diff --git a/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_hidden_with_only_dynamic.xml b/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_hidden_with_only_dynamic.xml
new file mode 100644
index 000000000..4be2c75ff
--- /dev/null
+++ b/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_hidden_with_only_dynamic.xml
@@ -0,0 +1,15 @@
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ type="hidden"
+ id="id">
+ <dynamic-safety-source
+ id="id"
+ packageName="package"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_hidden_with_only_static.xml b/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_hidden_with_only_static.xml
new file mode 100644
index 000000000..65f9f0ac9
--- /dev/null
+++ b/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_hidden_with_only_static.xml
@@ -0,0 +1,14 @@
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ type="hidden"
+ id="id">
+ <static-safety-source
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_stateful_with_only_issue_only.xml b/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_stateful_with_only_issue_only.xml
new file mode 100644
index 000000000..f801ca544
--- /dev/null
+++ b/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_stateful_with_only_issue_only.xml
@@ -0,0 +1,13 @@
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ type="stateful"
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference">
+ <issue-only-safety-source
+ id="id"
+ packageName="package"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_stateless_with_only_issue_only.xml b/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_stateless_with_only_issue_only.xml
new file mode 100644
index 000000000..9c05e59dd
--- /dev/null
+++ b/SafetyCenter/Config/tests/res/raw-v34/config_safety_sources_group_stateless_with_only_issue_only.xml
@@ -0,0 +1,13 @@
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ type="stateless"
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference">
+ <issue-only-safety-source
+ id="id"
+ packageName="package"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_deduplication_groups.xml b/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_deduplication_groups.xml
new file mode 100644
index 000000000..b75969ae1
--- /dev/null
+++ b/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_deduplication_groups.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference">
+ <static-safety-source
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"
+ deduplicationGroup="group"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config> \ No newline at end of file
diff --git a/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_notifications.xml b/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_notifications.xml
new file mode 100644
index 000000000..920f5c44b
--- /dev/null
+++ b/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_notifications.xml
@@ -0,0 +1,16 @@
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference">
+ <static-safety-source
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"
+ notificationsAllowed="true"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_package_certs.xml b/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_package_certs.xml
new file mode 100644
index 000000000..2a99d5617
--- /dev/null
+++ b/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_package_certs.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference">
+ <static-safety-source
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"
+ packageCertificateHashes="feed1,feed2"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Config/tests/res/raw-v34/config_valid.xml b/SafetyCenter/Config/tests/res/raw-v34/config_valid.xml
new file mode 100644
index 000000000..a473ea225
--- /dev/null
+++ b/SafetyCenter/Config/tests/res/raw-v34/config_valid.xml
@@ -0,0 +1,193 @@
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="dynamic"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ statelessIconType="privacy">
+ <dynamic-safety-source
+ id="dynamic_barebone"
+ packageName="package"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ <dynamic-safety-source
+ id="dynamic_all_optional"
+ packageName="package"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ titleForWork="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="all_profiles"
+ initialDisplayState="disabled"
+ maxSeverityLevel="300"
+ searchTerms="@com.android.safetycenter.config.tests:string/reference"
+ loggingAllowed="false"
+ refreshOnPageOpenAllowed="true"
+ notificationsAllowed="true"
+ deduplicationGroup="group"
+ packageCertificateHashes="feed1,feed2"/>
+ <dynamic-safety-source
+ id="@com.android.safetycenter.config.tests:string/dynamic_all_references_id"
+ packageName="@com.android.safetycenter.config.tests:string/dynamic_all_references_package_name"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ titleForWork="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="@com.android.safetycenter.config.tests:string/dynamic_all_references_intent_action"
+ profile="@com.android.safetycenter.config.tests:string/dynamic_all_references_profile"
+ initialDisplayState="@com.android.safetycenter.config.tests:string/dynamic_all_references_initial_display_state"
+ maxSeverityLevel="@com.android.safetycenter.config.tests:string/dynamic_all_references_max_severity_level"
+ searchTerms="@com.android.safetycenter.config.tests:string/reference"
+ loggingAllowed="@com.android.safetycenter.config.tests:string/dynamic_all_references_logging_allowed"
+ refreshOnPageOpenAllowed="@com.android.safetycenter.config.tests:string/dynamic_all_references_refresh_on_page_open_allowed"
+ notificationsAllowed="@com.android.safetycenter.config.tests:string/dynamic_all_references_notifications_allowed"
+ deduplicationGroup="@com.android.safetycenter.config.tests:string/dynamic_all_references_deduplication_group"
+ packageCertificateHashes="@com.android.safetycenter.config.tests:string/dynamic_all_references_package_cert_hashes"/>
+ <dynamic-safety-source
+ id="dynamic_disabled"
+ packageName="package"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ profile="primary_profile_only"
+ initialDisplayState="disabled"/>
+ <dynamic-safety-source
+ id="dynamic_hidden"
+ packageName="package"
+ profile="all_profiles"
+ initialDisplayState="hidden"/>
+ <dynamic-safety-source
+ id="dynamic_hidden_with_search"
+ packageName="package"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ titleForWork="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="all_profiles"
+ initialDisplayState="hidden"
+ searchTerms="@com.android.safetycenter.config.tests:string/reference"/>
+ </safety-sources-group>
+ <safety-sources-group
+ id="static"
+ title="@com.android.safetycenter.config.tests:string/reference">
+ <static-safety-source
+ id="static_barebone"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ <static-safety-source
+ id="static_all_optional"
+ packageName="package"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ titleForWork="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="all_profiles"
+ searchTerms="@com.android.safetycenter.config.tests:string/reference"/>
+ </safety-sources-group>
+ <safety-sources-group
+ id="issue_only">
+ <issue-only-safety-source
+ id="issue_only_barebone"
+ packageName="package"
+ profile="primary_profile_only"/>
+ <issue-only-safety-source
+ id="issue_only_all_optional"
+ packageName="package"
+ profile="all_profiles"
+ maxSeverityLevel="300"
+ loggingAllowed="false"
+ refreshOnPageOpenAllowed="true"
+ notificationsAllowed="true"
+ deduplicationGroup="group"
+ packageCertificateHashes="feed1,feed2"/>
+ <issue-only-safety-source
+ id="id_test_abcxyz_ABCXYZ_012789"
+ packageName="package"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ <safety-sources-group
+ id="mixed"
+ title="@com.android.safetycenter.config.tests:string/reference">
+ <dynamic-safety-source
+ id="mixed_dynamic_barebone"
+ packageName="package"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ <issue-only-safety-source
+ id="mixed_issue_only_barebone"
+ packageName="package"
+ profile="primary_profile_only"/>
+ <static-safety-source
+ id="mixed_static_barebone"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ <safety-sources-group
+ type="stateful"
+ id="stateful_barebone"
+ title="@com.android.safetycenter.config.tests:string/reference">
+ <static-safety-source
+ id="stateful_barebone_source"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ <safety-sources-group
+ type="stateful"
+ id="stateful_all_optional"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ statelessIconType="privacy">
+ <static-safety-source
+ id="stateful_all_optional_source"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ <safety-sources-group
+ type="stateless"
+ id="stateless_barebone"
+ title="@com.android.safetycenter.config.tests:string/reference">
+ <static-safety-source
+ id="stateless_barebone_source"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ <safety-sources-group
+ type="stateless"
+ id="stateless_all_optional"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ statelessIconType="privacy">
+ <static-safety-source
+ id="stateless_all_optional_source"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ <safety-sources-group
+ type="hidden"
+ id="hidden_barebone">
+ <issue-only-safety-source
+ id="hidden_barebone_source"
+ packageName="package"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ <safety-sources-group
+ type="hidden"
+ id="hidden_all_optional"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ statelessIconType="privacy">
+ <issue-only-safety-source
+ id="hidden_all_optional_source"
+ packageName="package"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Config/tests/res/values/strings.xml b/SafetyCenter/Config/tests/res/values/strings.xml
index 195f56c2a..a625c5225 100644
--- a/SafetyCenter/Config/tests/res/values/strings.xml
+++ b/SafetyCenter/Config/tests/res/values/strings.xml
@@ -26,4 +26,7 @@
<string name="dynamic_all_references_max_severity_level" translatable="false">300</string>
<string name="dynamic_all_references_logging_allowed" translatable="false">false</string>
<string name="dynamic_all_references_refresh_on_page_open_allowed" translatable="false">true</string>
+ <string name="dynamic_all_references_notifications_allowed" translatable="false">true</string>
+ <string name="dynamic_all_references_deduplication_group" translatable="false">group</string>
+ <string name="dynamic_all_references_package_cert_hashes" translatable="false">feed1,feed2</string>
</resources>
diff --git a/SafetyCenter/ConfigLintChecker/Android.bp b/SafetyCenter/ConfigLintChecker/Android.bp
index fb2e7ce3e..08881ccd5 100644
--- a/SafetyCenter/ConfigLintChecker/Android.bp
+++ b/SafetyCenter/ConfigLintChecker/Android.bp
@@ -33,8 +33,9 @@ java_library_host {
"layoutlib_api-prebuilt", // For com.android.resources.ResourceFolderType
"lint_api",
],
- java_resources: [":safetycenter-config-schema"],
+ java_resources: [":safetycenter-config-schemas"],
jarjar_rules: "jarjar-rules.txt",
+ kotlincflags: ["-Xjvm-default=all"],
visibility: [
"//packages/modules/Permission:__subpackages__",
"//vendor:__subpackages__",
@@ -43,6 +44,12 @@ java_library_host {
java_test_host {
name: "ConfigLintCheckerTest",
+ // TODO(b/239881504): Since this test was written, Android
+ // Lint was updated, and now includes classes that were
+ // compiled for java 15. The soong build doesn't support
+ // java 15 yet, so we can't compile against "lint". Disable
+ // the test until java 15 is supported.
+ enabled: false,
srcs: [
"tests/java/**/*.kt",
],
diff --git a/SafetyCenter/ConfigLintChecker/java/android/annotation/SuppressLint.java b/SafetyCenter/ConfigLintChecker/java/android/annotation/SuppressLint.java
new file mode 100644
index 000000000..c6d6dc757
--- /dev/null
+++ b/SafetyCenter/ConfigLintChecker/java/android/annotation/SuppressLint.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should ignore the specified warnings for the annotated element. */
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+ /**
+ * The set of warnings (identified by the lint issue id) that should be ignored by lint. It is
+ * not an error to specify an unrecognized name.
+ */
+ String[] value();
+}
diff --git a/SafetyCenter/ConfigLintChecker/java/android/os/Build.java b/SafetyCenter/ConfigLintChecker/java/android/os/Build.java
index abe5d49d3..531b6e481 100644
--- a/SafetyCenter/ConfigLintChecker/java/android/os/Build.java
+++ b/SafetyCenter/ConfigLintChecker/java/android/os/Build.java
@@ -20,9 +20,11 @@ package android.os;
public final class Build {
private Build() {}
- /** Stub class to used in the Safety Center config files. */
+ /** Stub class used in the Safety Center config code. */
public static final class VERSION_CODES {
- /** Constant used in the Safety Center config files. */
- public static final int TIRAMISU = 10000;
+ /** Constant used in the Safety Center config code. */
+ public static final int TIRAMISU = 33;
+ /** Constant used in the Safety Center config code. */
+ public static final int UPSIDE_DOWN_CAKE = 34;
}
}
diff --git a/SafetyCenter/ConfigLintChecker/java/android/os/Parcel.java b/SafetyCenter/ConfigLintChecker/java/android/os/Parcel.java
index e1300de32..81bedc860 100644
--- a/SafetyCenter/ConfigLintChecker/java/android/os/Parcel.java
+++ b/SafetyCenter/ConfigLintChecker/java/android/os/Parcel.java
@@ -29,6 +29,8 @@ public interface Parcel {
int readInt();
/** Method used in the Safety Center config data structures. */
String readString();
+ /** Method used in the Safety Center config data structures. */
+ ArrayList<String> createStringArrayList();
/** Method used in the Safety Center config data structures. */
void writeBoolean(boolean value);
@@ -37,5 +39,7 @@ public interface Parcel {
/** Method used in the Safety Center config data structures. */
void writeString(String value);
/** Method used in the Safety Center config data structures. */
+ void writeStringList(List<String> value);
+ /** Method used in the Safety Center config data structures. */
<T extends Parcelable> void writeTypedList(List<T> value);
}
diff --git a/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ConfigSchemaDetector.kt b/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ConfigSchemaDetector.kt
index ed58e5341..0b00805c2 100644
--- a/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ConfigSchemaDetector.kt
+++ b/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ConfigSchemaDetector.kt
@@ -16,6 +16,7 @@
package android.safetycenter.lint
+import android.os.Build.VERSION_CODES.TIRAMISU
import com.android.resources.ResourceFolderType
import com.android.tools.lint.detector.api.Category
import com.android.tools.lint.detector.api.Context
@@ -36,20 +37,19 @@ import org.xml.sax.SAXException
class ConfigSchemaDetector : Detector(), OtherFileScanner {
companion object {
- val ISSUE = Issue.create(
- id = "InvalidSafetyCenterConfigSchema",
- briefDescription = "The Safety Center config does not meet the schema requirements",
- explanation = """The Safety Center config must follow all constraints defined in \
+ val ISSUE =
+ Issue.create(
+ id = "InvalidSafetyCenterConfigSchema",
+ briefDescription = "The Safety Center config does not meet the schema requirements",
+ explanation =
+ """The Safety Center config must follow all constraints defined in \
safety_center_config.xsd. Either the config is invalid or the schema is not up to
date.""",
- category = Category.CORRECTNESS,
- severity = Severity.ERROR,
- implementation = Implementation(
- ConfigSchemaDetector::class.java,
- Scope.OTHER_SCOPE
- ),
- androidSpecific = true
- )
+ category = Category.CORRECTNESS,
+ severity = Severity.ERROR,
+ implementation =
+ Implementation(ConfigSchemaDetector::class.java, Scope.OTHER_SCOPE),
+ androidSpecific = true)
}
override fun appliesTo(folderType: ResourceFolderType): Boolean {
@@ -60,9 +60,31 @@ class ConfigSchemaDetector : Detector(), OtherFileScanner {
if (context.file.name != "safety_center_config.xml") {
return
}
- val xsd = StreamSource(
- ConfigSchemaDetector::class.java.getResourceAsStream("/safety_center_config.xsd")
- )
+ val fileSdk = FileSdk.getSdkQualifier(context.file)
+ // A config must comply with the schema at the highest SDK level that is lower or equal to
+ // the SDK level of the config itself.
+ var found = false
+ for (sdk in fileSdk downTo TIRAMISU) {
+ if (testSchema(sdk, context)) {
+ found = true
+ break
+ }
+ }
+ if (!found) {
+ context.report(
+ ISSUE,
+ Location.create(context.file),
+ "No schema found for SDK level: $fileSdk, was it deleted?")
+ }
+ // Test new schemas for backward compatibility.
+ for (sdk in fileSdk + 1..FileSdk.getMaxSdkVersion()) {
+ testSchema(sdk, context)
+ }
+ }
+
+ private fun testSchema(sdk: Int, context: Context): Boolean {
+ val xsdInputStream = FileSdk.getSchemaAsStream(sdk) ?: return false
+ val xsd = StreamSource(xsdInputStream)
val xml = StreamSource(context.file.inputStream())
val schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
try {
@@ -73,14 +95,13 @@ class ConfigSchemaDetector : Detector(), OtherFileScanner {
context.report(
ISSUE,
Location.create(context.file),
- e.message!!
- )
+ "SAXException exception at sdk=$sdk: \"${e.message}\"")
} catch (e: IOException) {
context.report(
ISSUE,
Location.create(context.file),
- e.message!!
- )
+ "IOException exception at sdk=$sdk: \"${e.message}\"")
}
+ return true
}
-} \ No newline at end of file
+}
diff --git a/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/FileSdk.kt b/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/FileSdk.kt
new file mode 100644
index 000000000..f93d4ecab
--- /dev/null
+++ b/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/FileSdk.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.lint
+
+import android.os.Build
+import android.os.Build.VERSION_CODES.TIRAMISU
+import com.google.common.annotations.VisibleForTesting
+import java.io.File
+import java.io.InputStream
+import java.lang.reflect.Modifier.isFinal
+import java.lang.reflect.Modifier.isPublic
+import java.lang.reflect.Modifier.isStatic
+
+/** A class that allows interacting with files that are versioned by sdk. */
+object FileSdk {
+ /**
+ * Linter constant to limit the mocked SDK levels that will be checked. We are making an
+ * important assumption here that if new parser logic is introduced that depends on a new SDK
+ * level, we expect a new schema to exist and a new version code to have been added.
+ */
+ private val MAX_VERSION: Int = maxOf(getMaxVersionCodesConstant(), getMaxSchemaVersion())
+
+ /** Test only override to further limit the mocked SDK that will be checked in a test */
+ @VisibleForTesting @Volatile @JvmStatic var maxVersionOverride: Int? = null
+
+ /**
+ * Returns the max SDK level version that should be used while linting to check for backward
+ * compatibility.
+ */
+ fun getMaxSdkVersion(): Int = maxVersionOverride ?: MAX_VERSION
+
+ /** Returns the SDK level version that a file resource belongs to. */
+ fun getSdkQualifier(file: File): Int {
+ val directParentName = file.parentFile.name
+ val lastQualifier = directParentName.substringAfterLast("-", "")
+ if (lastQualifier.isEmpty() || lastQualifier[0] != 'v') {
+ return TIRAMISU
+ }
+ return try {
+ lastQualifier.substring(1).toInt()
+ } catch (nfe: NumberFormatException) {
+ TIRAMISU
+ }
+ }
+
+ /** Returns the schema for the specific SDK level provided or null if it doesn't exist. */
+ fun getSchemaAsStream(sdk: Int): InputStream? =
+ FileSdk::class.java.getResourceAsStream("/safety_center_config${toQualifier(sdk)}.xsd")
+
+ private fun toQualifier(sdk: Int): String = if (sdk == TIRAMISU) "" else "-v$sdk"
+
+ private fun getMaxVersionCodesConstant(): Int =
+ Build.VERSION_CODES::class
+ .java
+ .declaredFields
+ .filter {
+ isPublic(it.modifiers) &&
+ isFinal(it.modifiers) &&
+ isStatic(it.modifiers) &&
+ it.type == Integer.TYPE
+ }
+ .maxOf { it.get(null) as Int }
+
+ private fun getMaxSchemaVersion(): Int =
+ // 99 is an arbitrary high value to look for the schema with the highest SDK level.
+ // Gaps are possible which is why we cannot just stop as soon as an SDK level has no schema.
+ (TIRAMISU..99).filter { getSchemaAsStream(it) != null }.maxOrNull() ?: 0
+}
diff --git a/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ParserExceptionDetector.kt b/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ParserExceptionDetector.kt
index e512b7e54..09f074818 100644
--- a/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ParserExceptionDetector.kt
+++ b/SafetyCenter/ConfigLintChecker/java/android/safetycenter/lint/ParserExceptionDetector.kt
@@ -19,6 +19,7 @@ package android.safetycenter.lint
import android.content.res.Resources
import com.android.SdkConstants.ATTR_NAME
import com.android.SdkConstants.TAG_STRING
+import com.android.modules.utils.build.SdkLevel
import com.android.resources.ResourceFolderType
import com.android.safetycenter.config.ParseException
import com.android.safetycenter.config.SafetyCenterConfigParser
@@ -103,21 +104,34 @@ class ParserExceptionDetector : Detector(), OtherFileScanner, XmlScanner {
context.file.name != "safety_center_config.xml") {
return
}
- try {
- SafetyCenterConfigParser.parseXmlResource(
- context.file.inputStream(),
- // Note: using a map of the string resources present in the APK under analysis is
- // necessary in order to get the value of string resources that are resolved and
- // validated at parse time. The drawback of this is that the linter cannot be used
- // on overlay packages that refer to resources in the target package or on packages
- // that refer to Android global resources. However, we cannot use custom a linter
- // with the default soong overlay build rule regardless.
- Resources(context.project.`package`, mNameToIndex, mIndexToValue))
- } catch (e: ParseException) {
- context.report(
- ISSUE,
- Location.create(context.file),
- "Parser exception: \"${e.message}\", cause: \"${e.cause?.message}\"")
+ val minSdk = FileSdk.getSdkQualifier(context.file)
+ val maxSdk = maxOf(minSdk, FileSdk.getMaxSdkVersion())
+ // Test the parser at the SDK level for which the config was designed.
+ // Then test parsers at higher SDK levels for backward compatibility.
+ // This is slightly inefficient if a parser at a higher SDK level has no behavioral changes
+ // compared to one at a lower SDK level, but doing an exhaustive search is safer.
+ for (sdk in minSdk..maxSdk) {
+ synchronized(SdkLevel::class.java) {
+ SdkLevel.setSdkInt(sdk)
+ try {
+ SafetyCenterConfigParser.parseXmlResource(
+ context.file.inputStream(),
+ // Note: using a map of the string resources present in the APK under
+ // analysis is necessary in order to get the value of string resources that
+ // are resolved and validated at parse time. The drawback of this is that
+ // the linter cannot be used on overlay packages that refer to resources in
+ // the target package or on packages that refer to Android global resources.
+ // However, we cannot use a custom linter with the default soong overlay
+ // build rule regardless.
+ Resources(context.project.`package`, mNameToIndex, mIndexToValue))
+ } catch (e: ParseException) {
+ context.report(
+ ISSUE,
+ Location.create(context.file),
+ "Parser exception at sdk=$sdk: \"${e.message}\", cause: " +
+ "\"${e.cause?.message}\"")
+ }
+ }
}
}
}
diff --git a/SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java b/SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java
new file mode 100644
index 000000000..cfc864c65
--- /dev/null
+++ b/SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import java.util.HashSet;
+
+/**
+ * A simple ArraySet implementation for the lint checker.
+ *
+ * <p>It's not array based, but for this simple purpose that doesn't matter.
+ *
+ * @param <E> the type of elements maintained by this set
+ */
+public final class ArraySet<E> extends HashSet<E> {}
diff --git a/SafetyCenter/ConfigLintChecker/java/com/android/modules/utils/build/SdkLevel.java b/SafetyCenter/ConfigLintChecker/java/com/android/modules/utils/build/SdkLevel.java
new file mode 100644
index 000000000..dbfaa56b1
--- /dev/null
+++ b/SafetyCenter/ConfigLintChecker/java/com/android/modules/utils/build/SdkLevel.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.modules.utils.build;
+
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+/** Stub class to compile the linter for host execution. */
+public final class SdkLevel {
+ private SdkLevel() {}
+
+ private static volatile int sSdkInt = TIRAMISU;
+
+ /**
+ * Linter only method to set the mocked SDK level for the Safety Center config code.
+ *
+ * <p>You must hold the class lock before calling this method. You should hold the class lock
+ * for the whole duration of the lint check.
+ */
+ public static void setSdkInt(int sdkInt) {
+ if (!Thread.holdsLock(SdkLevel.class)) {
+ throw new IllegalStateException("Lock not held.");
+ }
+ sSdkInt = sdkInt;
+ }
+
+ /** Method used in the Safety Center config code. */
+ public static boolean isAtLeastU() {
+ return sSdkInt >= UPSIDE_DOWN_CAKE;
+ }
+}
diff --git a/SafetyCenter/ConfigLintChecker/tests/java/android/safetycenter/lint/test/ConfigSchemaDetectorTest.kt b/SafetyCenter/ConfigLintChecker/tests/java/android/safetycenter/lint/test/ConfigSchemaDetectorTest.kt
index 0dd4afbd2..754d1a71f 100644
--- a/SafetyCenter/ConfigLintChecker/tests/java/android/safetycenter/lint/test/ConfigSchemaDetectorTest.kt
+++ b/SafetyCenter/ConfigLintChecker/tests/java/android/safetycenter/lint/test/ConfigSchemaDetectorTest.kt
@@ -16,11 +16,14 @@
package android.safetycenter.lint.test
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.lint.ConfigSchemaDetector
+import android.safetycenter.lint.FileSdk
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
+import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -34,11 +37,45 @@ class ConfigSchemaDetectorTest : LintDetectorTest() {
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+ @After
+ fun resetOverrides() {
+ FileSdk.maxVersionOverride = null
+ }
+
+ @Test
+ fun validMinimumConfig_doesNotThrow() {
+ lint()
+ .files(
+ (xml(
+ "res/raw/safety_center_config.xml",
+ """
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="group"
+ title="@package:string/reference"
+ summary="@package:string/reference">
+ <static-safety-source
+ id="source"
+ title="@package:string/reference"
+ summary="@package:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
+ """)))
+ .run()
+ .expectClean()
+ }
+
@Test
- fun validConfig_doesNotThrow() {
- lint().files((
- xml("res/raw/safety_center_config.xml",
- """
+ fun validFutureConfig_doesNotThrow() {
+ lint()
+ .files(
+ (xml(
+ "res/raw-99/safety_center_config.xml",
+ """
<safety-center-config>
<safety-sources-config>
<safety-sources-group
@@ -54,26 +91,82 @@ class ConfigSchemaDetectorTest : LintDetectorTest() {
</safety-sources-group>
</safety-sources-config>
</safety-center-config>
- """))).run().expectClean()
+ """)))
+ .run()
+ .expectClean()
}
@Test
- fun invalidConfig_throws() {
- lint().files((xml("res/raw/safety_center_config.xml", "<invalid-root/>")))
- .run().expect("res/raw/safety_center_config.xml: Error: cvc-elt.1.a: Cannot find the " +
- "declaration of element 'invalid-root'. [InvalidSafetyCenterConfigSchema]\n1 " +
- "errors, 0 warnings")
+ fun invalidConfigWithSingleSchema_throwsOneError() {
+ FileSdk.maxVersionOverride = UPSIDE_DOWN_CAKE
+ lint()
+ .files((xml("res/raw-v34/safety_center_config.xml", "<invalid-root/>")))
+ .run()
+ .expect(
+ "res/raw-v34/safety_center_config.xml: Error: SAXException exception at sdk=34: " +
+ "\"cvc-elt.1.a: Cannot find the declaration of element 'invalid-root'.\" " +
+ "[InvalidSafetyCenterConfigSchema]\n1 errors, 0 warnings")
+ }
+
+ @Test
+ fun invalidConfigWithMultipleSchemas_throwsMultipleErrors() {
+ FileSdk.maxVersionOverride = UPSIDE_DOWN_CAKE
+ lint()
+ .files((xml("res/raw/safety_center_config.xml", "<invalid-root/>")))
+ .run()
+ .expect(
+ "res/raw/safety_center_config.xml: Error: SAXException exception at sdk=33: " +
+ "\"cvc-elt.1.a: Cannot find the declaration of element 'invalid-root'.\" " +
+ "[InvalidSafetyCenterConfigSchema]\nres/raw/safety_center_config.xml: " +
+ "Error: SAXException exception at sdk=34: \"cvc-elt.1.a: Cannot find the " +
+ "declaration of element 'invalid-root'.\" " +
+ "[InvalidSafetyCenterConfigSchema]\n2 errors, 0 warnings")
+ }
+
+ @Test
+ fun validUConfigWithUFields_throwsInT() {
+ FileSdk.maxVersionOverride = UPSIDE_DOWN_CAKE
+ lint()
+ .files(
+ (xml(
+ "res/raw/safety_center_config.xml",
+ """
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="group"
+ title="@package:string/reference"
+ summary="@package:string/reference">
+ <dynamic-safety-source
+ id="source"
+ packageName="package"
+ title="@package:string/reference"
+ summary="@package:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"
+ notificationsAllowed="true"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
+ """)))
+ .run()
+ .expect(
+ "res/raw/safety_center_config.xml: Error: SAXException exception at sdk=33: " +
+ "\"cvc-complex-type.3.2.2: Attribute 'notificationsAllowed' is not allowed " +
+ "to appear in element 'dynamic-safety-source'.\" " +
+ "[InvalidSafetyCenterConfigSchema]\n1 errors, 0 warnings")
}
@Test
fun unrelatedFile_doesNotThrow() {
- lint().files((xml("res/raw/some_other_config.xml", "<some-other-root/>")))
- .run().expectClean()
+ lint()
+ .files((xml("res/raw/some_other_config.xml", "<some-other-root/>")))
+ .run()
+ .expectClean()
}
@Test
fun unrelatedFolder_doesNotThrow() {
- lint().files((xml("res/values/strings.xml", "<some-other-root/>")))
- .run().expectClean()
+ lint().files((xml("res/values/strings.xml", "<some-other-root/>"))).run().expectClean()
}
}
diff --git a/SafetyCenter/ConfigLintChecker/tests/java/android/safetycenter/lint/test/ParserExceptionDetectorTest.kt b/SafetyCenter/ConfigLintChecker/tests/java/android/safetycenter/lint/test/ParserExceptionDetectorTest.kt
index ad7d36685..a3030554e 100644
--- a/SafetyCenter/ConfigLintChecker/tests/java/android/safetycenter/lint/test/ParserExceptionDetectorTest.kt
+++ b/SafetyCenter/ConfigLintChecker/tests/java/android/safetycenter/lint/test/ParserExceptionDetectorTest.kt
@@ -16,11 +16,15 @@
package android.safetycenter.lint.test
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.safetycenter.lint.FileSdk
import android.safetycenter.lint.ParserExceptionDetector
import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
import com.android.tools.lint.checks.infrastructure.TestLintTask
import com.android.tools.lint.detector.api.Detector
import com.android.tools.lint.detector.api.Issue
+import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@@ -34,61 +38,130 @@ class ParserExceptionDetectorTest : LintDetectorTest() {
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+ @After
+ fun resetOverrides() {
+ FileSdk.maxVersionOverride = null
+ }
+
@Test
- fun validConfig_doesNotThrow() {
+ fun validMinimumConfig_doesNotThrow() {
lint()
.files(
- (xml(
- "res/raw/safety_center_config.xml",
- """
-<safety-center-config>
- <safety-sources-config>
- <safety-sources-group
- id="group"
- title="@lint.test.pkg:string/reference"
- summary="@lint.test.pkg:string/reference">
- <static-safety-source
- id="source"
- title="@lint.test.pkg:string/reference"
- summary="@lint.test.pkg:string/reference"
- intentAction="intent"
- profile="primary_profile_only"/>
- </safety-sources-group>
- </safety-sources-config>
-</safety-center-config>
- """)),
- (xml(
- "res/values/strings.xml",
- """
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="reference" translatable="false">Reference</string>
-</resources>
- """)))
+ xml("res/raw/safety_center_config.xml", VALID_TIRAMISU_CONFIG),
+ STRINGS_WITH_REFERENCE_XML)
.run()
.expectClean()
}
@Test
- fun invalidConfig_throws() {
+ fun validFutureConfig_doesNotThrow() {
lint()
- .files((xml("res/raw/safety_center_config.xml", "<invalid-root/>")))
+ .files(
+ xml("res/raw-99/safety_center_config.xml", VALID_TIRAMISU_CONFIG),
+ STRINGS_WITH_REFERENCE_XML)
+ .run()
+ .expectClean()
+ }
+
+ @Test
+ fun invalidConfigWithSingleSchema_throwsOneError() {
+ FileSdk.maxVersionOverride = UPSIDE_DOWN_CAKE
+ lint()
+ .files(xml("res/raw-v34/safety_center_config.xml", "<invalid-root/>"))
.run()
.expect(
- "res/raw/safety_center_config.xml: Error: Parser exception: " +
+ "res/raw-v34/safety_center_config.xml: Error: Parser exception at sdk=34: " +
"\"Element safety-center-config missing\", cause: \"null\" " +
"[InvalidSafetyCenterConfig]\n1 errors, 0 warnings")
}
@Test
- fun unrelatedFile_doesNotThrow() {
+ fun invalidConfigWithMultipleSchemas_throwsMultipleErrors() {
+ FileSdk.maxVersionOverride = UPSIDE_DOWN_CAKE
lint()
- .files((xml("res/raw/some_other_config.xml", "<some-other-root/>")))
+ .files(xml("res/raw/safety_center_config.xml", "<invalid-root/>"))
.run()
- .expectClean()
+ .expect(
+ "res/raw/safety_center_config.xml: Error: Parser exception at sdk=33: " +
+ "\"Element safety-center-config missing\", cause: \"null\" " +
+ "[InvalidSafetyCenterConfig]\nres/raw/safety_center_config.xml: " +
+ "Error: Parser exception at sdk=34: " +
+ "\"Element safety-center-config missing\", cause: \"null\" " +
+ "[InvalidSafetyCenterConfig]\n2 errors, 0 warnings")
+ }
+
+ @Test
+ fun validUConfigWithUFields_throwsInT() {
+ FileSdk.maxVersionOverride = UPSIDE_DOWN_CAKE
+ lint()
+ .files(
+ xml("res/raw/safety_center_config.xml", VALID_UDC_CONFIG),
+ STRINGS_WITH_REFERENCE_XML)
+ .run()
+ .expect(
+ "res/raw/safety_center_config.xml: Error: Parser exception at sdk=33: " +
+ "\"Unexpected attribute dynamic-safety-source.notificationsAllowed\", cause: " +
+ "\"null\" [InvalidSafetyCenterConfig]\n1 errors, 0 warnings")
+ }
+
+ @Test
+ fun unrelatedFile_doesNotThrow() {
+ lint().files(xml("res/raw/some_other_config.xml", "<some-other-root/>")).run().expectClean()
}
@Test
fun unrelatedFolder_doesNotThrow() {
- lint().files((xml("res/values/strings.xml", "<some-other-root/>"))).run().expectClean()
+ lint().files(xml("res/values/strings.xml", "<some-other-root/>")).run().expectClean()
+ }
+
+ private companion object {
+ val STRINGS_WITH_REFERENCE_XML: TestFile =
+ xml(
+ "res/values/strings.xml",
+ """
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="reference" translatable="false">Reference</string>
+</resources>
+ """)
+
+ const val VALID_TIRAMISU_CONFIG =
+ """
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="group"
+ title="@lint.test.pkg:string/reference"
+ summary="@lint.test.pkg:string/reference">
+ <static-safety-source
+ id="source"
+ title="@lint.test.pkg:string/reference"
+ summary="@lint.test.pkg:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
+ """
+
+ const val VALID_UDC_CONFIG =
+ """
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="group"
+ title="@lint.test.pkg:string/reference"
+ summary="@lint.test.pkg:string/reference">
+ <dynamic-safety-source
+ id="source"
+ packageName="package"
+ title="@lint.test.pkg:string/reference"
+ summary="@lint.test.pkg:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"
+ notificationsAllowed="true"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
+ """
}
}
diff --git a/SafetyCenter/Persistence/tests/java/com/android/safetycenter/persistence/PersistedSafetyCenterIssueTest.kt b/SafetyCenter/Persistence/tests/java/com/android/safetycenter/persistence/PersistedSafetyCenterIssueTest.kt
index 1ef147985..002d66557 100644
--- a/SafetyCenter/Persistence/tests/java/com/android/safetycenter/persistence/PersistedSafetyCenterIssueTest.kt
+++ b/SafetyCenter/Persistence/tests/java/com/android/safetycenter/persistence/PersistedSafetyCenterIssueTest.kt
@@ -53,7 +53,7 @@ class PersistedSafetyCenterIssueTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.of<PersistedSafetyCenterIssue>()
.addEqualityGroup(
ACTIVE_ISSUE,
PersistedSafetyCenterIssue.Builder()
diff --git a/SafetyCenter/Resources/res/raw-v34/safety_center_config.xml b/SafetyCenter/Resources/res/raw-v34/safety_center_config.xml
new file mode 100644
index 000000000..8d838e729
--- /dev/null
+++ b/SafetyCenter/Resources/res/raw-v34/safety_center_config.xml
@@ -0,0 +1,124 @@
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="AndroidLockScreenSources"
+ title="@com.android.safetycenter.resources:string/lock_screen_sources_title"
+ summary="@com.android.safetycenter.resources:string/lock_screen_sources_summary">
+ <dynamic-safety-source
+ id="AndroidLockScreen"
+ packageName="com.android.settings"
+ profile="primary_profile_only"
+ title="@com.android.safetycenter.resources:string/lock_screen_title"
+ summary="@com.android.safetycenter.resources:string/lock_screen_summary_disabled"
+ searchTerms="@com.android.safetycenter.resources:string/lock_screen_search_terms"
+ initialDisplayState="disabled"/>
+ <dynamic-safety-source
+ id="AndroidBiometrics"
+ packageName="com.android.settings"
+ profile="primary_profile_only"
+ title="@com.android.safetycenter.resources:string/biometrics_title"
+ searchTerms="@com.android.safetycenter.resources:string/biometrics_search_terms"
+ initialDisplayState="hidden"/>
+ </safety-sources-group>
+ <safety-sources-group
+ id="AndroidPrivacySources"
+ title="@com.android.safetycenter.resources:string/privacy_sources_title"
+ summary="@com.android.safetycenter.resources:string/privacy_sources_summary"
+ statelessIconType="privacy">
+ <static-safety-source
+ id="AndroidPermissionUsage"
+ profile="primary_profile_only"
+ intentAction="android.intent.action.REVIEW_PERMISSION_USAGE"
+ title="@com.android.safetycenter.resources:string/permission_usage_title"
+ summary="@com.android.safetycenter.resources:string/permission_usage_summary"
+ searchTerms="@com.android.safetycenter.resources:string/permission_usage_search_terms"/>
+ <static-safety-source
+ id="AndroidPermissionManager"
+ profile="primary_profile_only"
+ intentAction="android.intent.action.MANAGE_PERMISSIONS"
+ title="@com.android.safetycenter.resources:string/permission_manager_title"
+ summary="@com.android.safetycenter.resources:string/permission_manager_summary"
+ searchTerms="@com.android.safetycenter.resources:string/permission_manager_search_terms"/>
+ <static-safety-source
+ id="AndroidPrivacyControls"
+ profile="primary_profile_only"
+ intentAction="android.settings.PRIVACY_CONTROLS"
+ title="@com.android.safetycenter.resources:string/privacy_controls_title"
+ summary="@com.android.safetycenter.resources:string/privacy_controls_summary"
+ searchTerms="@com.android.safetycenter.resources:string/privacy_controls_search_terms"/>
+ <dynamic-safety-source
+ id="AndroidPrivacyAppDataSharingUpdates"
+ packageName="com.android.permissioncontroller"
+ profile="primary_profile_only"
+ initialDisplayState="hidden"
+ refreshOnPageOpenAllowed="true"/>
+ <static-safety-source
+ id="AndroidHealthConnect"
+ profile="primary_profile_only"
+ intentAction="android.healthconnect.action.HEALTH_HOME_SETTINGS"
+ title="@com.android.safetycenter.resources:string/health_connect_title"
+ summary="@com.android.safetycenter.resources:string/health_connect_summary"
+ searchTerms="@com.android.safetycenter.resources:string/health_connect_search_terms"/>
+ <issue-only-safety-source
+ id="AndroidAccessibility"
+ packageName="com.android.permissioncontroller"
+ profile="all_profiles"
+ refreshOnPageOpenAllowed="true"/>
+ <issue-only-safety-source
+ id="AndroidNotificationListener"
+ packageName="com.android.permissioncontroller"
+ profile="primary_profile_only"
+ refreshOnPageOpenAllowed="true"/>
+ <issue-only-safety-source
+ id="AndroidBackgroundLocation"
+ packageName="com.android.permissioncontroller"
+ profile="all_profiles"
+ refreshOnPageOpenAllowed="true"/>
+ <issue-only-safety-source
+ id="AndroidPermissionAutoRevoke"
+ packageName="com.android.permissioncontroller"
+ profile="all_profiles"
+ refreshOnPageOpenAllowed="true"/>
+ </safety-sources-group>
+ <safety-sources-group
+ id="AndroidAdvancedSources"
+ title="@com.android.safetycenter.resources:string/advanced_title">
+ <dynamic-safety-source
+ id="AndroidWorkPolicyInfo"
+ packageName="com.android.permissioncontroller"
+ profile="primary_profile_only"
+ initialDisplayState="hidden"
+ refreshOnPageOpenAllowed="true"/>
+ <static-safety-source
+ id="AndroidAdvancedSecurity"
+ profile="primary_profile_only"
+ intentAction="com.android.settings.security.SECURITY_ADVANCED_SETTINGS"
+ title="@com.android.safetycenter.resources:string/advanced_security_title"
+ summary="@com.android.safetycenter.resources:string/advanced_security_summary"
+ searchTerms="@com.android.safetycenter.resources:string/advanced_security_search_terms"/>
+ <static-safety-source
+ id="AndroidAdvancedPrivacy"
+ profile="primary_profile_only"
+ intentAction="android.settings.PRIVACY_ADVANCED_SETTINGS"
+ title="@com.android.safetycenter.resources:string/advanced_privacy_title"
+ summary="@com.android.safetycenter.resources:string/advanced_privacy_summary"
+ searchTerms="@com.android.safetycenter.resources:string/advanced_privacy_search_terms"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Resources/res/values-fa/strings.xml b/SafetyCenter/Resources/res/values-fa/strings.xml
index 41a3121ea..868f9bfdb 100644
--- a/SafetyCenter/Resources/res/values-fa/strings.xml
+++ b/SafetyCenter/Resources/res/values-fa/strings.xml
@@ -38,7 +38,7 @@
<string name="privacy_controls_search_terms" msgid="3774472175934304165">"حریم خصوصی، تنظیمات حریم خصوصی"</string>
<string name="advanced_title" msgid="8745436380690561172">"تنظیمات بیشتر"</string>
<string name="advanced_security_title" msgid="1126833338772188155">"تنظیمات امنیتی بیشتر"</string>
- <string name="advanced_security_summary" msgid="6172253327022425123">"رمزگذاری، اطلاعات اعتباری، و غیره"</string>
+ <string name="advanced_security_summary" msgid="6172253327022425123">"رمزگذاری، اطلاعات کاربری، و غیره"</string>
<string name="advanced_security_search_terms" msgid="3350609555814362075"></string>
<string name="advanced_privacy_title" msgid="1117725225706176643">"تنظیمات حریم خصوصی بیشتر"</string>
<string name="advanced_privacy_summary" msgid="2281203390575069543">"تکمیل خودکار، کنترل‌های فعالیت، و غیره"</string>
diff --git a/SafetyCenter/Resources/res/values-v34/strings.xml b/SafetyCenter/Resources/res/values-v34/strings.xml
new file mode 100644
index 000000000..5e4ffd0ef
--- /dev/null
+++ b/SafetyCenter/Resources/res/values-v34/strings.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <string name="health_connect_title" description="Health connect title">Health&#160;Connect</string>
+ <string name="health_connect_summary" description="Health connect summary, which describes what user can do when they click Health Connect">App permissions and data management</string>
+ <string name="health_connect_search_terms" description="Health connect search words">Health, Health Connect</string>
+
+</resources> \ No newline at end of file
diff --git a/SafetyCenter/Resources/shared_res/values-af/strings.xml b/SafetyCenter/Resources/shared_res/values-af/strings.xml
index 09a89fb15..d35ccadf1 100644
--- a/SafetyCenter/Resources/shared_res/values-af/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-af/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Gaan instellingslys na"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Toestel is dalk in gevaar"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Toestel is in gevaar"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Jy is dalk in gevaar"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Jy is in gevaar"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Jou data is dalk in gevaar"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Jou data is in gevaar"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Wagwoorde is gekompromitteer (oud)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Wagwoorde is gekompromitteer (nuut)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Jy is dalk in gevaar"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Jy is in gevaar"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potensiële risiko’s gevind"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risiko’s gevind"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Rekening is dalk in gevaar"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Rekening is in gevaar"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# opletberig}other{# opletberigte}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-am/strings.xml b/SafetyCenter/Resources/shared_res/values-am/strings.xml
index a52c37c04..028d60fae 100644
--- a/SafetyCenter/Resources/shared_res/values-am/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-am/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"የቅንብሮች ዝርዝርን ይፈትሹ"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"መሣሪያ አደጋ ላይ ሊሆን ይችላል"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"መሣሪያ አደጋ ላይ ነው"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"ተጋላጭ ሊሆኑ ይችላሉ"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"ተጋላጭ ነዎት"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"ውሂብዎ አደጋ ላይ ሊሆን ይችላል"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"ውሂብዎ አደጋ ላይ ነው"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"የይለፍ ቃላት ተጠልፈዋል (የድሮ)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"የይለፍ ቃላት ተጠልፈዋል (አዲስ)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"አደጋ ላይ ሊሆኑ ይችላሉ"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"አደጋ ላይ ነዎት"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"ሊከሰቱ የሚችሉ አደጋዎች ተገኝተዋል"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"አደጋዎች ተገኝተዋል"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"መለያ አደጋ ላይ ሊሆን ይችላል"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"መለያ አደጋ ላይ ነው"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# ማንቂያ}one{# ማንቂያ}other{# ማንቂያዎች}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ar/strings.xml b/SafetyCenter/Resources/shared_res/values-ar/strings.xml
index 8ff93f51f..78696025c 100644
--- a/SafetyCenter/Resources/shared_res/values-ar/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ar/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"الاطّلاع على قائمة الإعدادات"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"الجهاز قد يكون معرّضًا للخطر"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"الجهاز معرّض للخطر"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"قد تكون معرّضًا للخطر"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"أنت معرّض للخطر"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"قد تكون بياناتك معرّضة للخطر"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"بياناتك معرّضة للخطر"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"تعرضت كلمات المرور (القديمة) للخطر"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"تعرضت كلمات المرور (الجديدة) للخطر"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"قد تكون معرّضًا للخطر"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"أنت معرّض للخطر"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"هناك مخاطر محتملة"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"هناك مخاطر"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"قد يكون الحساب معرّضًا للخطر"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"الحساب معرّض للخطر"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{تنبيه واحد (#)}zero{# تنبيه}two{تنبيهان}few{# تنبيهات}many{# تنبيهًا}other{# تنبيه}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-as/strings.xml b/SafetyCenter/Resources/shared_res/values-as/strings.xml
index 7daf9c7ff..b1d006597 100644
--- a/SafetyCenter/Resources/shared_res/values-as/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-as/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"ছেটিঙৰ সূচী পৰীক্ষা কৰক"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"ডিভাইচটোৰ বিপদাশংকা থাকিব পাৰে"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"ডিভাইচটো অসুৰক্ষিত অৱস্থাত আছে"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"আপুনি আশংকাত থাকিব পাৰে"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"আপুনি আশংকাত আছে"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"আপোনাৰ ডেটাখিনিৰ বিপদাশংকা থাকিব পাৰে"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"আপোনাৰ ডেটাখিনিৰ বিপদাশংকা আছে"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"পাছৱৰ্ড হেক কৰা হৈছে (পুৰণি)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"পাছৱৰ্ড হেক কৰা হৈছে (নতুন)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"একাউণ্টত বিপদাশংকা থাকিব পাৰে"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"আপোনাৰ একাউণ্টত বিপদাশংকা আছে"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"সম্ভাব্য বিপদাশংকা পোৱা গৈছে"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"বিপদাশংকা পোৱা গৈছে"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"একাউণ্টটোত বিপদাশংকা থাকিব পাৰে"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"একাউণ্টৰ বিপদাশংকা আছে"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# টা সতৰ্কবাৰ্তা}one{# সতৰ্কবাৰ্তা}other{# সতৰ্কবাৰ্তা}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-az/strings.xml b/SafetyCenter/Resources/shared_res/values-az/strings.xml
index 20cebbaab..96510a6cb 100644
--- a/SafetyCenter/Resources/shared_res/values-az/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-az/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Ayarlar siyahısını yoxlayın"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Cihaz risk altında ola bilər"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Cihaz risk altındadır"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Risk altında ola bilərsiniz"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Risk altındasınız"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Datanız risk altında ola bilər"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Datanız risk altındadır"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Parollar oğurlanıb (köhnə)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Parollar oğurlanıb (yeni)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Risk altında ola bilərsiniz"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Risk altındasınız"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potensial risklər tapıldı"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risklər tapıldı"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Hesab risk altında ola bilər"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Hesab risk altındadır"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# siqnal}other{# siqnal}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml b/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
index ce6247204..30b65051c 100644
--- a/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Proverite listu podešavanja"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Uređaj je možda ugrožen"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Uređaj je ugrožen"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Možda ste ugroženi"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Ugroženi ste"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Podaci su možda ugroženi"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Podaci su ugroženi"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Lozinke su ugrožene (stare)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Lozinke su ugrožene (nove)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Možda ste ugroženi"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Ugroženi ste"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Pronađeni su potencijalni rizici"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Pronađeni su rizici"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Nalog je možda ugrožen"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Nalog je ugrožen"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# obaveštenje}one{# obaveštenje}few{# obaveštenja}other{# obaveštenja}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-be/strings.xml b/SafetyCenter/Resources/shared_res/values-be/strings.xml
index d57336aca..e76aefac5 100644
--- a/SafetyCenter/Resources/shared_res/values-be/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-be/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Праверыць спіс налад"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Прылада можа быць у небяспецы"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Прылада ў небяспецы"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Вы можаце быць у небяспецы"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Вы ў небяспецы"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Вашы даныя могуць быць у небяспецы"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Вашы даныя ў небяспецы"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Раскрытыя паролі (старыя)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Раскрытыя паролі (новыя)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Вы можаце быць у небяспецы"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Вы ў небяспецы"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Выяўлена патэнцыяльная небяспека"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Выяўлена небяспека"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Уліковы запіс можа быць у небяспецы"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Уліковы запіс у небяспецы"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# абвестка}one{# абвестка}few{# абвесткі}many{# абвестак}other{# абвесткі}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bg/strings.xml b/SafetyCenter/Resources/shared_res/values-bg/strings.xml
index 1f0175973..ad13d485a 100644
--- a/SafetyCenter/Resources/shared_res/values-bg/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bg/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Проверка на списъка с настройки"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"У-вото може да е изложено на риск"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Устройството е изложено на риск"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Има потенциален риск за вас"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Има риск за вас"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Данните може да са изложени на риск"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Данните ви са изложени на риск"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Компрометирани пароли (стари)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Компрометирани пароли (нови)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Има потенциален риск за вас"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Има риск за вас"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Открити са потенциални рискове"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Открити са рискове"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Профилът може да е изложен на риск"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Профилът е изложен на риск"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# сигнал}other{# сигнала}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bn/strings.xml b/SafetyCenter/Resources/shared_res/values-bn/strings.xml
index 290b2fed7..7646005ac 100644
--- a/SafetyCenter/Resources/shared_res/values-bn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bn/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"সেটিংস তালিকা চেক করুন"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"ডিভাইসের নিরাপত্তা বিপন্ন হতে পারে"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"ডিভাইসের নিরাপত্তা বিপন্ন"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"আপনার নিরাপত্তা বিপন্ন হতে পারে"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"আপনার নিরাপত্তা বিপন্ন"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"আপনার ডেটার হয়ত ক্ষতি হতে পারে"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"আপনার ডেটার ক্ষতি হতে পারে"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"পাসওয়ার্ড চুরি হয়ে গেছে (পুরনো)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"পাসওয়ার্ড চুরি হয়ে গেছে (নতুন)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"আপনার নিরাপত্তা বিপন্ন হতে পারে"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"আপনার নিরাপত্তা বিপন্ন"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"সম্ভাব্য ঝুঁকি খুঁজে পাওয়া গেছে"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"ঝুঁকির বিষয়টি খুঁজে পাওয়া গেছে"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"অ্যাকাউন্টের হয়ত ক্ষতি হতে পারে"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"অ্যাকাউন্টের ক্ষতি হতে পারে"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{#টি সতর্কতা}one{#টি সতর্কতা}other{#টি সতর্কতা}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bs/strings.xml b/SafetyCenter/Resources/shared_res/values-bs/strings.xml
index 96d792479..62c542eaa 100644
--- a/SafetyCenter/Resources/shared_res/values-bs/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bs/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Provjerite listu postavki"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Uređaj bi mogao biti izložen riziku"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Uređaj je izložen riziku"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Moguće je da ste izloženi riziku"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Izloženi ste riziku"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Vaši podaci bi mogli biti izloženi riziku"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Vaši podaci su izloženi riziku"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Lozinke su ugrožene (stare)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Lozinke su ugrožene (nove)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Možda ste izloženi riziku"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Izloženi ste riziku"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Pronađeni su potencijalni rizici"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Pronađeni su rizici"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Račun bi mogao biti izložen riziku"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Račun je izložen riziku"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# upozorenje}one{# upozorenje}few{# upozorenja}other{# upozorenja}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ca/strings.xml b/SafetyCenter/Resources/shared_res/values-ca/strings.xml
index 231a4cf1a..46f5970da 100644
--- a/SafetyCenter/Resources/shared_res/values-ca/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ca/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Comprova la llista de configuració"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"El dispositiu pot estar en perill"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"El dispositiu està en perill"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Pot ser que estiguis en perill"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Estàs en perill"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Les dades poden estar en perill"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Les dades estan en perill"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Contrasenyes en perill (antigues)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Contrasenyes en perill (noves)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Pot ser que estiguis en perill"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Estàs en perill"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"S\'han trobat possibles riscos"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"S\'han trobat riscos"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"El compte pot estar en perill"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Aquest compte està en perill"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerta}many{# alerts}other{# alertes}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-cs/strings.xml b/SafetyCenter/Resources/shared_res/values-cs/strings.xml
index a7ab9bcd4..603087524 100644
--- a/SafetyCenter/Resources/shared_res/values-cs/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-cs/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Zkontrolujte seznam nastavení"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Zařízení může být ohroženo"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Zařízení je ohroženo"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Můžete být ohroženi"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Jste v ohrožení"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Data mohou být ohrožena"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Data jsou ohrožena"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Prolomená hesla (staré)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Prolomená hesla (nové)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Můžete být ohroženi"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Jste v ohrožení"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Byla nalezena možná rizika"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Byla nalezena rizika"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Účet může být ohrožen"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Účet je ohrožen"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# upozornění}few{# upozornění}many{# upozornění}other{# upozornění}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-da/strings.xml b/SafetyCenter/Resources/shared_res/values-da/strings.xml
index 8745e6096..634d5d483 100644
--- a/SafetyCenter/Resources/shared_res/values-da/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-da/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Tjek listen over indstillinger"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Enheden kan være sårbar"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Enheden er sårbar"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Du er muligvis i fare"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Du er i fare"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Dine data kan være sårbare"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Dine data er sårbare"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Kompromitterede adgangskoder (gamle)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Kompromitterede adgangskoder (nye)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Din konto er muligvis sårbar"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Din konto er sårbar"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Der blev fundet potentielle risici"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Der blev fundet risici"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Kontoen kan være sårbar"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Kontoen er sårbar"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# underretning}one{# underretning}other{# underretninger}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-de/strings.xml b/SafetyCenter/Resources/shared_res/values-de/strings.xml
index 0b7c27277..a6872dc67 100644
--- a/SafetyCenter/Resources/shared_res/values-de/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-de/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Liste der Einstellungen prüfen"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Gerät ist eventuell gefährdet"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Gerät ist gefährdet"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Du bist möglicherweise gefährdet"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Du bist gefährdet"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Daten eventuell gefährdet"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Daten gefährdet"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"(Alte) Passwörter gefährdet"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"(Neue) Passwörter gefährdet"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Du bist möglicherweise gefährdet"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Du bist gefährdet"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Mögliche Sicherheitsrisiken gefunden"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Sicherheitsrisiken gefunden"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Konto eventuell gefährdet"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Konto gefährdet"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# Warnung}other{# Warnungen}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-el/strings.xml b/SafetyCenter/Resources/shared_res/values-el/strings.xml
index b88c8b3e8..6c98e0816 100644
--- a/SafetyCenter/Resources/shared_res/values-el/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-el/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Έλεγχος λίστας ρυθμίσεων"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Η συσκευή μπορεί να κινδυνεύει"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Η συσκευή κινδυνεύει"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Ενδέχεται να βρίσκεστε σε κίνδυνο"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Βρίσκεστε σε κίνδυνο"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Τα δεδομένα σας μπορεί να κινδυν."</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Τα δεδομένα σας κινδυνεύουν"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Παραβιάστηκαν κωδ. πρόσβ. (παλιοί)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Παραβιάστηκαν κωδ. πρόσβασης (νέοι)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Ενδέχεται να βρίσκεστε σε κίνδυνο"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Βρίσκεστε σε κίνδυνο"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Εντοπίστηκαν πιθανοί κίνδυνοι"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Εντοπίστηκαν κίνδυνοι"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Ο λογαριασμός μπορεί να κινδυνεύει"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Ο λογαριασμός βρίσκεται σε κίνδυνο"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# ειδοποίηση}other{# ειδοποιήσεις}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
index 6137c2caa..26967f2da 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Check settings list"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Device may be at risk"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Device is at risk"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"You may be at risk"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"You are at risk"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Your data may be at risk"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Your data is at risk"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Passwords compromised (old)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Passwords compromised (new)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"You may be at risk"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"You are at risk"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potential risks found"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risks found"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Account may be at risk"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Account is at risk"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alert}other{# alerts}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
index 588409f4f..c26f0d40e 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Check settings list"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Device may be at risk"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Device is at risk"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"You may be at risk"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"You are at risk"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Your data may be at risk"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Your data is at risk"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Passwords compromised (old)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Passwords compromised (new)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"You may be at risk"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"You are at risk"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potential risks found"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risks found"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Account may be at risk"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Account is at risk"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alert}other{# alerts}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
index 6137c2caa..26967f2da 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Check settings list"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Device may be at risk"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Device is at risk"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"You may be at risk"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"You are at risk"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Your data may be at risk"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Your data is at risk"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Passwords compromised (old)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Passwords compromised (new)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"You may be at risk"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"You are at risk"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potential risks found"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risks found"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Account may be at risk"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Account is at risk"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alert}other{# alerts}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
index 6137c2caa..26967f2da 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Check settings list"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Device may be at risk"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Device is at risk"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"You may be at risk"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"You are at risk"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Your data may be at risk"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Your data is at risk"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Passwords compromised (old)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Passwords compromised (new)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"You may be at risk"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"You are at risk"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potential risks found"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risks found"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Account may be at risk"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Account is at risk"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alert}other{# alerts}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
index 3058d9c54..ba21dba30 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‎‏‎‏‎‏‎‎‏‏‎‎‏‏‎Check settings list‎‏‎‎‏‎"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‎‏‏‏‏‎‎‎‏‏‏‎‏‏‏‎‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‏‎Device may be at risk‎‏‎‎‏‎"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‏‎‎‎‏‎‏‏‎‎‎‎‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‎Device is at risk‎‏‎‎‏‎"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‏‏‎‎‏‎‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎You may be at risk‎‏‎‎‏‎"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‏‎‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‎‎‎‏‎‎‏‎‎‎You are at risk‎‏‎‎‏‎"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‎‏‎‎‎‎‎‎‎‏‎‏‏‏‎‎‎‎‏‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‎‎‎‎‎‏‎‏‎‎‎‎‎‏‏‎‎Your data may be at risk‎‏‎‎‏‎"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‏‎‏‏‏‎‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‏‎‎‎‏‎‎‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‎Your data is at risk‎‏‎‎‏‎"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‎Passwords compromised (old)‎‏‎‎‏‎"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‎‎‏‎‏‎‎‎‎‎‎‏‏‎‏‏‎‏‎‎‎‎‏‎‏‎‎‏‏‎‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎Passwords compromised (new)‎‏‎‎‏‎"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎‎‏‏‎You may be at risk‎‏‎‎‏‎"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‎‎‏‏‏‏‏‎‎You are at risk‎‏‎‎‏‎"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‎‎‎‏‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‎‏‎‎‏‏‎‎‏‏‎‏‎‎‏‎Potential risks found‎‏‎‎‏‎"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‎‏‎‎‏‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‏‎‏‎‎‎Risks found‎‏‎‎‏‎"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‎‎‎‎‏‎‏‏‎‎‎‎‏‎‎‏‎‎Account may be at risk‎‏‎‎‏‎"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‏‏‎‎‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‏‎‎‏‎‎‎‏‎Account is at risk‎‏‎‎‏‎"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎# alert‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‏‎‏‎‎‏‏‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎# alerts‎‏‎‎‏‎}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml b/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
index 2e5146f38..2440fa870 100644
--- a/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Verifica la lista de configuración"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"El dispositivo podría estar en riesgo"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"El dispositivo está en riesgo"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Es posible que estés en peligro"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Estás en peligro"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Tus datos podrían estar en riesgo"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Tus datos están en riesgo"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Contraseñas comprometidas (viejas)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Contraseñas comprometidas (nuevas)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Es posible que estés en peligro"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Estás en peligro"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Se detectaron riesgos potenciales"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Se detectaron riesgos"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"La cuenta podría estar en riesgo"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"La cuenta está en riesgo"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerta}many{# alertas}other{# alertas}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-es/strings.xml b/SafetyCenter/Resources/shared_res/values-es/strings.xml
index 255f424e8..78fc55428 100644
--- a/SafetyCenter/Resources/shared_res/values-es/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-es/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Comprueba la lista de ajustes"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"El dispositivo puede estar en riesgo"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"El dispositivo está en riesgo"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Puedes estar en riesgo"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Estás en riesgo"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Tus datos pueden estar en riesgo"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Tus datos están en riesgo"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Contraseñas vulneradas (antiguas)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Contraseñas vulneradas (nuevas)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Puedes estar en riesgo"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Estás en riesgo"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Posibles riesgos detectados"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Riesgos detectados"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"La cuenta puede estar en riesgo"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"La cuenta está en riesgo"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerta}many{# alertas}other{# alertas}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-et/strings.xml b/SafetyCenter/Resources/shared_res/values-et/strings.xml
index fdf25c6e2..f8eec7fc7 100644
--- a/SafetyCenter/Resources/shared_res/values-et/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-et/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Kontrollige seadete loendit"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Seade võib olla ohus"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Seade on ohus"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Võite olla ohus"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Olete ohus"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Teie andmed võivad ohus olla"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Teie andmed on ohus"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Paroolid on ohus (vana)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Paroolid on ohus (uus)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Võite olla ohus"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Olete ohus"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Leiti võimalikud ohud"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Leiti ohud"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Konto võib olla ohus"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Konto on ohus"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# hoiatus}other{# hoiatust}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-eu/strings.xml b/SafetyCenter/Resources/shared_res/values-eu/strings.xml
index 31ee60f97..54d7f98fc 100644
--- a/SafetyCenter/Resources/shared_res/values-eu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-eu/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Egiaztatu ezarpenen zerrenda"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Baliteke gailua arriskuan egotea"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Gailua arriskuan dago"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Baliteke arriskuan egotea"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Arriskuan zaude"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Baliteke datuak arriskuan egotea"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Datuak arriskuan daude"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Pasahitz zaharrak arriskuan daude"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Pasahitz berriak arriskuan daude"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Baliteke arriskuan egotea"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Arriskuan zaude"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Balizko arriskuak aurkitu dira"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Arriskuak aurkitu dira"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Baliteke kontua arriskuan egotea"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Kontua arriskuan dago"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerta}other{# alerta}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fa/strings.xml b/SafetyCenter/Resources/shared_res/values-fa/strings.xml
index f76e1c43f..35babbd8e 100644
--- a/SafetyCenter/Resources/shared_res/values-fa/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fa/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"بررسی فهرست تنظیمات"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"دستگاه ممکن است درمعرض خطر باشد"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"دستگاه درمعرض خطر است"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"ممکن است درمعرض خطر باشید"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"درمعرض خطر هستید"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"شاید داده‌هایتان درمعرض خطر باشد"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"داده‌هایتان درمعرض خطر است"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"گذرواژه‌ها لو رفته است (قدیمی)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"گذرواژه‌ها لو رفته است (جدید)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"ممکن است درمعرض خطر باشید"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"درمعرض خطر هستید"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"خطرات احتمالی پیدا شده است"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"خطراتی پیدا شده است"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"ممکن است حساب درمعرض خطر باشد"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"حساب درمعرض خطر است"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# هشدار}one{# هشدار}other{# هشدار}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fi/strings.xml b/SafetyCenter/Resources/shared_res/values-fi/strings.xml
index 1caece7d6..cde64a34d 100644
--- a/SafetyCenter/Resources/shared_res/values-fi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fi/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Tarkista asetuslista"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Laite saattaa olla vaarantunut"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Laite on vaarantunut"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Turvallisuutesi on voinut vaarantua"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Turvallisuutesi on vaarantanut"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Data voi olla vaarantunut"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Data on vaarantunut"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"(Vanhat) salasanat vaarantuneet"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"(Uudet) salasanat vaarantuneet"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Turvallisuutesi on voinut vaarantua"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Turvallisuutesi on vaarantanut"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Mahdollisia riskejä löydetty"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Tietoturvariskejä löydetty"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Tili voi olla vaarantunut"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Tili on vaarantunut"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# hälytys}other{# hälytystä}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml b/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
index df8088492..64a690116 100644
--- a/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Vérifiez la liste des paramètres"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"L\'appareil pourrait être en danger"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"L\'appareil est en danger"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Vous pourriez être en danger"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Vous êtes en danger"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Vos données pourraient être en danger"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Vos données sont en danger"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Mots de passe compromis (ancien)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Mots de passe compromis (nouveau)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Vous pourriez être en danger"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Vous êtes en danger"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Dangers potentiels trouvés"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Dangers trouvés"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Le compte pourrait être en danger"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Le compte est en danger"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerte}one{# alerte}many{# alertes}other{# alertes}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fr/strings.xml b/SafetyCenter/Resources/shared_res/values-fr/strings.xml
index 63c15e508..e1e716fd1 100644
--- a/SafetyCenter/Resources/shared_res/values-fr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fr/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Vérifier la liste des paramètres"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Risque potentiel sur l\'appareil"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Risque sur l\'appareil"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Risque potentiel"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Risque de sécurité"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Risque potentiel sur vos données"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Risque sur vos données"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Mots de passe compromis (anciens)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Mots de passe compromis (nouveaux)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Risque potentiel"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Risque de sécurité"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Risques potentiels détectés"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risques détectés"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Risque potentiel sur le compte"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Risque sur le compte"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerte}one{# alerte}many{# alertes}other{# alertes}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-gl/strings.xml b/SafetyCenter/Resources/shared_res/values-gl/strings.xml
index e8538b7a6..fd2df84f4 100644
--- a/SafetyCenter/Resources/shared_res/values-gl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-gl/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Revisa a lista de opcións de configuración"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"O dispositivo pode estar en risco"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"O dispositivo está en risco"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Pode que esteas en risco"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Estás en risco"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Os teus datos poden estar en risco"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Os teus datos están en risco"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Contrasinais vulnerados (antigos)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Contrasinais vulnerados (novos)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Pode que a túa conta estea en risco"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"A túa conta está en risco"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Atopáronse posibles riscos"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Atopáronse riscos"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"A conta pode estar en risco"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"A conta está en risco"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerta}other{# alertas}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-gu/strings.xml b/SafetyCenter/Resources/shared_res/values-gu/strings.xml
index 60ecc2ee6..85afd571b 100644
--- a/SafetyCenter/Resources/shared_res/values-gu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-gu/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"સેટિંગની સૂચિ ચેક કરો"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"ડિવાઇસ કદાચ જોખમમાં હોઈ શકે"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"ડિવાઇસ જોખમમાં છે"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"તમારી સુરક્ષા જોખમમાં હોઈ શકે છે"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"તમારી સુરક્ષા જોખમમાં છે"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"તમારો ડેટા જોખમમાં હોઈ શકે છે"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"તમારો ડેટા જોખમમાં છે"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"પાસવર્ડ સાથે ચેડાં થયા (જૂના)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"પાસવર્ડ સાથે ચેડાં થયા (નવા)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"તમારી સુરક્ષા જોખમમાં હોઈ શકે છે"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"તમારી સુરક્ષા જોખમમાં છે"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"સંભવિત જોખમો મળ્યા"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"જોખમો મળ્યા"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"એકાઉન્ટ જોખમમાં હોઈ શકે છે"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"એકાઉન્ટ જોખમમાં છે"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# અલર્ટ}one{# અલર્ટ}other{# અલર્ટ}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hi/strings.xml b/SafetyCenter/Resources/shared_res/values-hi/strings.xml
index e264fac2c..e69dfc05b 100644
--- a/SafetyCenter/Resources/shared_res/values-hi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hi/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"सेटिंग की सूची देखें"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"डिवाइस की सुरक्षा शायद खतरे में है"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"डिवाइस की सुरक्षा खतरे में है"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"शायद आपकी सुरक्षा खतरे में है"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"आपकी सुरक्षा खतरे में है"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"डेटा की सुरक्षा खतरे में हो सकती है"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"डेटा की सुरक्षा खतरे में है"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"पासवर्ड हैक हो गया (पुराना)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"पासवर्ड हैक हो गया (नया)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"शायद आपकी सुरक्षा खतरे में है"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"आपकी सुरक्षा खतरे में है"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"संभावित खतरे का पता चला"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"खतरे का पता चला"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"खाते की सुरक्षा खतरे में हो सकती है"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"खाते की सुरक्षा खतरे में है"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# चेतावनी}one{# चेतावनी}other{# चेतावनियां}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hr/strings.xml b/SafetyCenter/Resources/shared_res/values-hr/strings.xml
index 2c89f1a39..e345b9bd0 100644
--- a/SafetyCenter/Resources/shared_res/values-hr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hr/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Provjera popisa postavki"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Uređaj je možda ugrožen"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Uređaj je ugrožen"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Vaša je sigurnost možda ugrožena"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Vaša je sigurnost ugrožena"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Podaci mogu biti ugroženi"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Podaci su ugroženi"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Zaporke su ugrožene (staro)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Zaporke su ugrožene (novo)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Vaša je sigurnost možda ugrožena"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Vaša je sigurnost ugrožena"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Pronađeni su potencijalni rizici"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Pronađeni su rizici"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Račun je možda ugrožen"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Račun je ugrožen"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# upozorenje}one{# upozorenje}few{# upozorenja}other{# upozorenja}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hu/strings.xml b/SafetyCenter/Resources/shared_res/values-hu/strings.xml
index 23de9b2fd..e3e26408c 100644
--- a/SafetyCenter/Resources/shared_res/values-hu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hu/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Ellenőrizze a beállításlistát"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Az eszközhasználat kockázatos lehet"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Az eszköz használata kockázatos"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Veszélyben lehet"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Veszélyben van"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Adatai veszélyben lehetnek"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Adatai veszélyben vannak"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Feltört jelszavak (régi)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Feltört jelszavak (új)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Veszélyben lehet"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Veszélyben van"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Lehetséges veszélyeket észleltünk"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Veszélyeket észleltünk"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Fiókja veszélyben lehet"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Fiókja veszélyben van"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# értesítés}other{# értesítés}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hy/strings.xml b/SafetyCenter/Resources/shared_res/values-hy/strings.xml
index 91d243deb..3f4f9e358 100644
--- a/SafetyCenter/Resources/shared_res/values-hy/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hy/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Ստուգեք կարգավորումների ցանկը"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Սարքը կարող է վտանգված լինել"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Սարքը վտանգված է"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Դուք կարող եք վտանգի տակ լինել"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Դուք վտանգի տակ եք"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Տվյալները կարող են վտանգված լինել"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Ձեր տվյալները վտանգված են"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Հին գաղտնաբառերը կոտրված են"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Նոր գաղտնաբառերը կոտրված են"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Դուք կարող եք վտանգված լինել"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Դուք վտանգված եք"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Հնարավոր ռիսկեր են հայտնաբերվել"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Ռիսկեր են հայտնաբերվել"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Հաշիվը կարող է վտանգված լինել"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Հաշիվը վտանգված է"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# ծանուցում}one{# ծանուցում}other{# ծանուցում}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-in/strings.xml b/SafetyCenter/Resources/shared_res/values-in/strings.xml
index f7ac72208..8c0b3a268 100644
--- a/SafetyCenter/Resources/shared_res/values-in/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-in/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Periksa daftar setelan"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Perangkat mungkin berisiko"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Perangkat berisiko"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Anda mungkin berisiko"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Anda berisiko"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Data Anda mungkin berisiko"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Data Anda berisiko"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Sandi disusupi (lama)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Sandi disusupi (baru)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Anda mungkin berisiko"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Anda berisiko"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potensi risiko ditemukan"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risiko ditemukan"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Akun mungkin berisiko"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Akun berisiko"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# peringatan}other{# peringatan}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-is/strings.xml b/SafetyCenter/Resources/shared_res/values-is/strings.xml
index fc5ef0e99..b003327b9 100644
--- a/SafetyCenter/Resources/shared_res/values-is/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-is/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Athuga lista yfir stillingar"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Tækið er hugsanlega í hættu"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Tækið er í hættu"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Þú gætir verið í hættu"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Þú ert í hættu"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Gögnin þín gætu verið í hættu"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Gögnin þín eru í hættu"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Aðgangsorð í hættu (gömul)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Aðgangsorð í hættu (ný)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Þú gætir verið í hættu"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Þú ert í hættu"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Hugsanleg hætta greindist"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Hætta greindist"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Reikningurinn er hugsanlega í hættu"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Reikningurinn er í hættu"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# viðvörun}one{# viðvörun}other{# viðvaranir}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-it/strings.xml b/SafetyCenter/Resources/shared_res/values-it/strings.xml
index 2b5ee3092..7535a163a 100644
--- a/SafetyCenter/Resources/shared_res/values-it/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-it/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Controlla l\'elenco di impostazioni"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Il dispositivo potrebbe essere a rischio"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Il dispositivo è a rischio"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Potresti essere a rischio"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Sei a rischio"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"I dati potrebbero essere a rischio"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"I dati sono a rischio"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Password compromesse (precedenti)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Password compromesse (nuove)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Potresti essere a rischio"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Sei a rischio"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potenziali rischi rilevati"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Rischi rilevati"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"L\'account potrebbe essere a rischio"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"L\'account è a rischio"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# avviso}many{# avvisi}other{# avvisi}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-iw/strings.xml b/SafetyCenter/Resources/shared_res/values-iw/strings.xml
index 26033f4df..f4ffae4d9 100644
--- a/SafetyCenter/Resources/shared_res/values-iw/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-iw/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"בדיקה של רשימת ההגדרות"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"המכשיר עלול להיות בסיכון"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"המכשיר בסיכון"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"ייתכן שקיים סיכון"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"קיים סיכון"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"ייתכן שהנתונים שלך בסיכון"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"הנתונים שלך בסיכון"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"סיסמאות בסכנת חשיפה (ישנות)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"סיסמאות בסכנת חשיפה (חדשות)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"ייתכן שקיים סיכון"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"קיים סיכון"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"נמצאו סכנות אפשריות"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"נמצאו סיכונים"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"החשבון עלול להיות בסיכון"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"החשבון בסיכון"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{התראה #}one{# התראות}two{# התראות}other{# התראות}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ja/strings.xml b/SafetyCenter/Resources/shared_res/values-ja/strings.xml
index 16dde305e..af733879f 100644
--- a/SafetyCenter/Resources/shared_res/values-ja/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ja/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"設定リストをご確認ください"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"デバイスが危険な可能性があります"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"デバイスが危険です"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"危険な可能性があります"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"危険にさらされています"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"データが危険な可能性があります"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"データが危険です"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"古いパスワードの不正使用"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"新しいパスワードの不正使用"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"危険にさらされています"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"危険にさらされています"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"潜在的なリスクが検出されました"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"リスクが検出されました"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"アカウントが危険な可能性があります"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"アカウントが危険です"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# 件のアラート}other{# 件のアラート}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ka/strings.xml b/SafetyCenter/Resources/shared_res/values-ka/strings.xml
index 82eda0161..bf1471501 100644
--- a/SafetyCenter/Resources/shared_res/values-ka/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ka/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"პარამეტრების სიის შემოწმება"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"მოწყობილობა შესაძლო საფრთხის ქვეშაა"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"მოწყობილობა საფრთხის ქვეშაა"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"თქვენ შეიძლება საფრთხე გემუქრებოდეთ"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"თქვენ საფრთხეში ხართ"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"თქვ. მონაც. შესაძლოა საფრთხე ემუქრ."</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"თქვენ მონაცემებს საფრთხე ემუქრება"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"პაროლები კომპრომეტირებულია (ძველი)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"პაროლები გატეხილია (ახალი)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"თქვენ შეიძლება საფრთხე გემუქრებოდეთ"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"თქვენ საფრთხეში ხართ"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"ნაპოვნია პოტენციური რისკები"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"აღმოჩენილი რისკები"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"ანგარიშს შესაძლოა საფრთხე ემუქრება"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"ანგარიშს საფრთხე ემუქრება"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# გაფრთხილება}other{# გაფრთხილება}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-kk/strings.xml b/SafetyCenter/Resources/shared_res/values-kk/strings.xml
index 25ac190b5..da5237f30 100644
--- a/SafetyCenter/Resources/shared_res/values-kk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-kk/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Параметрлер тізімін тексеру"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Құрылғыға қауіп төнген болуы мүмкін"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Құрылғыға қауіп төніп тұр"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Қауіп төнуі мүмкін"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Қауіп төніп тұр"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Деректерге қауіп төнген сияқты"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Деректерге қауіп төніп тұр"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Ұрланған құпия сөздер (ескі)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Ұрланған құпия сөздер (жаңа)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Қауіп төнуі мүмкін"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Қауіп төніп тұр"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Қауіп төну мүмкіндігі бар"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Қауіп төніп тұрғаны белгілі болды"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Аккаунтқа қауіп төнген сияқты"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Аккаунтқа қауіп төніп тұр"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# хабарландыру}other{# хабарландыру}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-km/strings.xml b/SafetyCenter/Resources/shared_res/values-km/strings.xml
index 30b60cf50..a36b062a4 100644
--- a/SafetyCenter/Resources/shared_res/values-km/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-km/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"ពិនិត្យបញ្ជីការកំណត់"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"ឧបករណ៍អាចប្រឈម​នឹងហានិភ័យ"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"ឧបករណ៍​កំពុងប្រឈមនឹង​ហានិភ័យ"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"អ្នក​អាច​ប្រឈម​នឹង​ហានិភ័យ"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"អ្នក​ប្រឈម​នឹង​ហានិភ័យ"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"ទិន្នន័យរបស់អ្នកអាចប្រឈមនឹងហានិភ័យ"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"ទិន្នន័យរបស់អ្នកគឺប្រឈមនឹងហានិភ័យ"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"ពាក្យសម្ងាត់រងការលុកលុយ (ចាស់)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"ពាក្យសម្ងាត់រងការលុកលុយ (ថ្មី)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"អ្នក​អាច​ប្រឈម​នឹង​ហានិភ័យ"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"អ្នក​ប្រឈម​នឹង​ហានិភ័យ"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"រកឃើញហានិភ័យដែលអាចមាន"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"រកឃើញហានិភ័យ"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"គណនីអាចប្រឈមនឹងហានិភ័យ"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"គណនីប្រឈមនឹងហានិភ័យ"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{ការជូនដំណឹង #}other{ការជូនដំណឹង #}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-kn/strings.xml b/SafetyCenter/Resources/shared_res/values-kn/strings.xml
index ac8fc7349..f8198afe9 100644
--- a/SafetyCenter/Resources/shared_res/values-kn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-kn/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"ಸೆಟ್ಟಿಂಗ್‌ಗಳ ಪಟ್ಟಿಯನ್ನು ಪರಿಶೀಲಿಸಿ"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"ಸಾಧನವು ಅಪಾಯದಲ್ಲಿರಬಹುದು"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"ಸಾಧನವು ಅಪಾಯದಲ್ಲಿದೆ"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"ನೀವು ಅಪಾಯಕ್ಕೀಡಾಗಬಹುದು"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"ನೀವು ಅಪಾಯದಲ್ಲಿದ್ದೀರಿ"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"ನಿಮ್ಮ ಡೇಟಾ ಅಪಾಯದಲ್ಲಿರಬಹುದು"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"ನಿಮ್ಮ ಡೇಟಾ ಅಪಾಯದಲ್ಲಿದೆ"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"ಅಪಾಯಕ್ಕೀಡಾದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು (ಹಳೆಯ)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"ಅಪಾಯಕ್ಕೀಡಾದ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು (ಹೊಸತು)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"ನೀವು ಅಪಾಯಕ್ಕೀಡಾಗಿರಬಹುದು"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"ನೀವು ಅಪಾಯದಲ್ಲಿದ್ದೀರಿ"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"ಸಂಭವನೀಯ ಅಪಾಯಗಳು ಕಂಡುಬಂದಿವೆ"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"ಅಪಾಯಗಳು ಕಂಡುಬಂದಿವೆ"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"ಖಾತೆಯು ಅಪಾಯಕ್ಕೀಡಾಗಬಹುದು"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"ಖಾತೆ ಅಪಾಯದಲ್ಲಿದೆ"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# ಅಲರ್ಟ್}one{# ಅಲರ್ಟ್‌ಗಳು}other{# ಅಲರ್ಟ್‌ಗಳು}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ko/strings.xml b/SafetyCenter/Resources/shared_res/values-ko/strings.xml
index d49f59620..1674c1707 100644
--- a/SafetyCenter/Resources/shared_res/values-ko/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ko/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"설정 목록 확인"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"기기에 잠재적 위험 발생"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"기기 위험 발생"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"위험에 노출되었을 수 있음"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"위험에 노출됨"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"데이터에 보안 위험이 있을 수 있음"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"데이터에 보안 위험이 있음"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"기존 비밀번호 손상됨"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"신규 비밀번호 손상됨"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"위험에 노출되었을 수 있음"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"위험에 노출됨"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"잠재적 위험 발견됨"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"위험 발견됨"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"계정에 보안 위험이 있을 수 있음"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"계정에 보안 위험이 있음"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{알림 #개}other{알림 #개}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ky/strings.xml b/SafetyCenter/Resources/shared_res/values-ky/strings.xml
index 6b66f8086..a29fe358b 100644
--- a/SafetyCenter/Resources/shared_res/values-ky/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ky/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Параметрлердин тизмесин текшерүү"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Түзмөк коркунучта болушу мүмкүн"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Түзмөк коркунучта"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Коопсуздук коркунучта болушу мүмкүн"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Коопсуздук коркунучта"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Маалыматтын коопсуздугу коркунучта"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Маалыматтын коопсуздугу коркунучта"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Сырсөздөр уурдалды (эски)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Сырсөз уурдалды (жаңы)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Коопсуздук коркунучта"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Коопсуздук коркунучта"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Мүмкүн болгон коркунучтар табылды"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Коркунучтар табылды"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Аккаунт коркунучта болушу мүмкүн"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Аккаунттун коопсуздугу коркунучта"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# эскертүү}other{# эскертүү}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lo/strings.xml b/SafetyCenter/Resources/shared_res/values-lo/strings.xml
index 3c2d7a452..e0f6f0f83 100644
--- a/SafetyCenter/Resources/shared_res/values-lo/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lo/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"ກວດລາຍການການຕັ້ງຄ່າ"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"ອຸປະກອນອາດມີຄວາມສ່ຽງ"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"ອຸປະກອນມີຄວາມສ່ຽງ"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"ທ່ານອາດຢູ່ໃນຄວາມສ່ຽງ"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"ທ່ານຢູ່ໃນຄວາມສ່ຽງ"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"ຂໍ້ມູນຂອງທ່ານອາດຕົກຢູ່ໃນຄວາມສ່ຽງ"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"ຂໍ້ມູນຂອງທ່ານຕົກຢູ່ໃນຄວາມສ່ຽງ"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"ລະຫັດຜ່ານມີການຮົ່ວໄຫຼ (ເກົ່າ)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"ລະຫັດຜ່ານມີການຮົ່ວໄຫຼ (ໃໝ່)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"ທ່ານອາດຕົກຢູ່ໃນຄວາມສ່ຽງ"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"ທ່ານຕົກຢູ່ໃນຄວາມສ່ຽງ"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"ພົບຄວາມສ່ຽງທີ່ອາດເກີດຂຶ້ນ"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"ພົບຄວາມສ່ຽງ"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"ບັນຊີອາດຕົກຢູ່ໃນຄວາມສ່ຽງ"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"ບັນຊີຕົກຢູ່ໃນຄວາມສ່ຽງ"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# ແຈ້ງເຕືອນ}other{# ແຈ້ງເຕືອນ}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lt/strings.xml b/SafetyCenter/Resources/shared_res/values-lt/strings.xml
index 85045ea9f..bbef2ce6c 100644
--- a/SafetyCenter/Resources/shared_res/values-lt/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lt/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Tikrinti nustatymų sąrašą"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Įrenginiui gali grėsti pavojus"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Įrenginiui gresia pavojus"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Jums gali kilti pavojus"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Jums iškilo pavojus"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Jūsų duomenims galėjo kilti pavojus"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Jūsų duomenims kilo pavojus"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Slaptažodžiai pažeisti (seni)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Slaptažodžiai pažeisti (nauji)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Jums gali kilti pavojus"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Jums iškilo pavojus"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Aptikta potencialių pavojų"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Aptikta pavojų"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Paskyrai galėjo kilti pavojus"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Paskyrai kilo pavojus"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# įspėjimas}one{# įspėjimas}few{# įspėjimai}many{# įspėjimo}other{# įspėjimų}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lv/strings.xml b/SafetyCenter/Resources/shared_res/values-lv/strings.xml
index bcd895149..b3aaec710 100644
--- a/SafetyCenter/Resources/shared_res/values-lv/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lv/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Pārbaudiet iestatījumu sarakstu"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Iespējams, ierīce ir apdraudēta"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Ierīce ir apdraudēta"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Iespējams, jūs esat apdraudēts"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Jūs esat apdraudēts"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Jūsu dati var būt apdraudēti"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Jūsu dati ir apdraudēti"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Uzlauztas paroles (vecas)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Uzlauztas paroles (jaunas)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Iespējams, jūs esat apdraudēts"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Jūs esat apdraudēts"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Konstatēts iespējams risks"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Konstatēts risks"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Konts var būt apdraudēts"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Konts ir apdraudēts"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# ieteikums vai brīdinājums}zero{# ieteikumu vai brīdinājumu}one{# ieteikums vai brīdinājums}other{# ieteikumi vai brīdinājumi}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mk/strings.xml b/SafetyCenter/Resources/shared_res/values-mk/strings.xml
index c8aea82e4..446b374d5 100644
--- a/SafetyCenter/Resources/shared_res/values-mk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mk/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Проверете го списокот со поставки"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Уредот можеби е изложен на ризик"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Уредот е изложен на ризик"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Можеби сте под ризик"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Под ризик сте"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Податоците можеби се под ризик"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Податоците се под ризик"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Компромитирани лозинки (стари)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Компромитирани лозинки (нови)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Можеби сте под ризик"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Под ризик сте"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Најдени се потенцијални ризици"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Најдени се ризици"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Сметката можеби е под ризик"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Сметката е под ризик"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# предупредување}one{# предупредување}other{# предупредувања}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ml/strings.xml b/SafetyCenter/Resources/shared_res/values-ml/strings.xml
index 8674ccbb0..62586aa57 100644
--- a/SafetyCenter/Resources/shared_res/values-ml/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ml/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"ക്രമീകരണ ലിസ്റ്റ് പരിശോധിക്കുക"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"ഉപകരണം അപകടത്തിലാകാൻ സാധ്യതയുണ്ട്"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"ഉപകരണം അപകടത്തിലാണ്"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"നിങ്ങൾ അപകടത്തിലായിരിക്കാം"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"നിങ്ങൾ അപകടത്തിലാണ്"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"നിങ്ങളുടെ ഡാറ്റ അപകടത്തിലായേക്കാം"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"നിങ്ങളുടെ ഡാറ്റ അപകടത്തിലാണ്"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"പാസ്‌വേഡുകൾ അപഹരിച്ചു (പഴയത്)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"പാസ്‌വേഡുകൾ അപഹരിച്ചു (പുതിയത്)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"നിങ്ങൾ അപകടത്തിലായിരിക്കാം"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"നിങ്ങൾ അപകടത്തിലാണ്"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"സാധ്യതയുള്ള അപകടസാധ്യതകൾ കണ്ടെത്തി"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"അപകടസാധ്യതകൾ കണ്ടെത്തി"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"അക്കൗണ്ട് അപകടത്തിലായേക്കാം"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"അക്കൗണ്ട് അപകടത്തിലാണ്"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# മുന്നറിയിപ്പ്}other{# മുന്നറിയിപ്പുകൾ}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mn/strings.xml b/SafetyCenter/Resources/shared_res/values-mn/strings.xml
index 0a9308cd7..def573378 100644
--- a/SafetyCenter/Resources/shared_res/values-mn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mn/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Тохиргооны жагсаалтыг шалгах"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Төхөөрөмж эрсдэлд байж магадгүй"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Төхөөрөмж эрсдэлд байна"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Та эрсдэлд байж магадгүй"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Та эрсдэлд байна"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Таны өгөгдөл эрсдэлд байж магадгүй"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Таны өгөгдөл эрсдэлд байна"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Нууц үг алдагдсан (хуучин)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Нууц үг алдагдсан (шинэ)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Та эрсдэлд орсон байж магадгүй"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Та эрсдэлд орсон байна"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Боломжит эрсдэл илэрсэн"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Эрсдэл илэрсэн"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Бүртгэл эрсдэлд байж магадгүй"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Бүртгэл эрсдэлд байна"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# сэрэмжлүүлэг}other{# сэрэмжлүүлэг}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mr/strings.xml b/SafetyCenter/Resources/shared_res/values-mr/strings.xml
index be26bde74..143751f43 100644
--- a/SafetyCenter/Resources/shared_res/values-mr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mr/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"सेटिंग्जची सूची तपासा"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"डिव्हाइस धोक्यात असू शकते"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"डिव्हाइस धोक्यात आहे"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"तुमची सुरक्षा धोक्यात असू शकते"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"तुमची सुरक्षा धोक्यात आहे"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"तुमचा डेटा धोक्यात असू शकतो"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"तुमचा डेटा धोक्यात आहे"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"धोक्यात असलेले पासवर्ड (जुने)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"धोक्यात असलेले पासवर्ड (नवीन)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"तुमची सुरक्षा धोक्यात असू शकते"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"तुमची सुरक्षा धोक्यात आहे"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"संभाव्य धोके आढळले"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"धोके आढळले"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"खाते धोक्यात असू शकते"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"खाते धोक्यात आहे"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# इशारा}other{# इशारे}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ms/strings.xml b/SafetyCenter/Resources/shared_res/values-ms/strings.xml
index 8e4d6b595..8eb9c1ec5 100644
--- a/SafetyCenter/Resources/shared_res/values-ms/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ms/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Semak senarai tetapan"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Peranti mungkin berisiko"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Peranti berisiko"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Anda mungkin berisiko"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Anda berisiko"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Data anda mungkin berisiko"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Data anda berisiko"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Kata laluan terjejas (lama)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Kata laluan terjejas (baharu)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Anda mungkin berisiko"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Anda berisiko"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Risiko berpotensi ditemukan"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risiko ditemukan"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Akaun mungkin berisiko"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Akaun berisiko"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# amaran}other{# amaran}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-my/strings.xml b/SafetyCenter/Resources/shared_res/values-my/strings.xml
index 79c5fff12..a09b3f664 100644
--- a/SafetyCenter/Resources/shared_res/values-my/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-my/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"ဆက်တင်များစာရင်းကို စစ်ဆေးပါ"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"စက်တွင် အန္တရာယ်ရှိနိုင်သည်"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"စက်တွင် အန္တရာယ်ရှိနေသည်"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"သင့်တွင် အန္တရာယ်ရှိနိုင်သည်"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"သင့်တွင် အန္တရာယ်ရှိနေသည်"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"သင့်ဒေတာတွင် အန္တရာယ်ရှိနိုင်သည်"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"သင့်ဒေတာတွင် အန္တရာယ်ရှိသည်"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"စကားဝှက် ကျိုးပေါက်နေသည် (အဟောင်း)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"စကားဝှက် ကျိုးပေါက်နေသည် (အသစ်)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"သင့်တွင် အန္တရာယ်ရှိနိုင်သည်"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"သင့်တွင် အန္တရာယ်ရှိနေသည်"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"အန္တရာယ်ဖြစ်နိုင်ခြေ တွေ့သည်"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"အန္တရာယ် တွေ့ထားသည်"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"အကောင့်တွင် အန္တရာယ်ရှိနိုင်သည်"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"အကောင့်တွင် အန္တရာယ်ရှိသည်"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{သတိပေးချက် # ခု}other{သတိပေးချက် # ခု}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-nb/strings.xml b/SafetyCenter/Resources/shared_res/values-nb/strings.xml
index 12ac898d0..e0927ffd2 100644
--- a/SafetyCenter/Resources/shared_res/values-nb/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-nb/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Sjekk innstillingslisten"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Enheten kan være i faresonen"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Enheten er i faresonen"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Du kan være i faresonen"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Du er i faresonen"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Dataene dine kan være i faresonen"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Dataene dine er i faresonen"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Passord har sikkerhetsbrudd (gamle)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Passord har sikkerhetsbrudd (nye)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Du kan være utsatt for risiko"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Du er utsatt for risiko"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potensielle risikoer er registrert"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risikoer er registrert"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Kontoen kan være i faresonen"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Kontoen er i faresonen"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# varsel}other{# varsler}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ne/strings.xml b/SafetyCenter/Resources/shared_res/values-ne/strings.xml
index f89da5c4f..7e2cdd297 100644
--- a/SafetyCenter/Resources/shared_res/values-ne/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ne/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"सेटिङको सूची जाँच्नुहोस्"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"डिभाइस जोखिममा हुन सक्छ"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"डिभाइस जोखिममा छ"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"सुरक्षा र गोपनीयता जोखिममा हुन सक्छ"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"सुरक्षा र गोपनीयता जोखिममा छ"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"तपाईंको डेटा जोखिममा हुन सक्छ"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"तपाईंको डेटा जोखिममा छ"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"पासवर्ड ह्याक भएको छ (पुरानो)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"पासवर्ड ह्याक भएको छ (नयाँ पासवर्ड)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"तपाईंको सुरक्षा जोखिममा हुन सक्छ"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"तपाईंको खाता जोखिममा छ"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"सम्भावित जोखिम फेला परे"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"जोखिम फेला परे"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"खाता जोखिममा हुन सक्छ"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"खाता जोखिममा छ"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# अलर्ट}other{# वटा अलर्ट}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-nl/strings.xml b/SafetyCenter/Resources/shared_res/values-nl/strings.xml
index ef6041683..004c0ed23 100644
--- a/SafetyCenter/Resources/shared_res/values-nl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-nl/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Check de lijst met instellingen"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Apparaat loopt misschien gevaar"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Apparaat loopt gevaar"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Je loopt misschien risico"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Je loopt risico"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Je gegevens lopen misschien gevaar"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Je gegevens lopen risico"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Wachtwoorden gehackt (oud)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Wachtwoorden gehackt (nieuw)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Je loopt misschien gevaar"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Je account loopt gevaar"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potentiële risico\'s gevonden"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risico\'s gevonden"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Account loopt misschien gevaar"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Account loopt risico"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# melding}other{# meldingen}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-or/strings.xml b/SafetyCenter/Resources/shared_res/values-or/strings.xml
index 30a7f15f1..574d5f7e3 100644
--- a/SafetyCenter/Resources/shared_res/values-or/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-or/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"ସେଟିଂସ ତାଲିକା ଯାଞ୍ଚ କରନ୍ତୁ"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"ଡିଭାଇସଟି ରିସ୍କରେ ଥାଇପାରେ"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"ଡିଭାଇସଟି ରିସ୍କରେ ଅଛି"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"ଆପଣ ବିପଦରେ ଥାଇପାରନ୍ତି"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"ଆପଣ ବିପଦରେ ଅଛନ୍ତି"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"ଆପଣଙ୍କ ଡାଟା ବିପଦରେ ଥାଇପାରେ"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"ଆପଣଙ୍କ ଡାଟା ବିପଦରେ ଅଛି"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"ପାସୱାର୍ଡ ଚୋରି ହୋଇଯାଇଛି (ପୁରୁଣା)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"ପାସୱାର୍ଡ ଚୋରି ହୋଇଯାଇଛି (ନୂଆ)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"ଆପଣ ବିପଦରେ ଥାଇପାରନ୍ତି"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"ଆପଣ ବିପଦରେ ଅଛନ୍ତି"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"ସମ୍ଭାବ୍ୟ ବିପଦ ମିଳିଛି"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"ବିପଦ ମିଳିଛି"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"ଆକାଉଣ୍ଟ ବିପଦରେ ଥାଇପାରେ"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"ଆକାଉଣ୍ଟ ବିପଦରେ ଅଛି"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{#ଟି ଆଲର୍ଟ}other{#ଟି ଆଲର୍ଟ}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pa/strings.xml b/SafetyCenter/Resources/shared_res/values-pa/strings.xml
index b87a7efeb..55cc2adb7 100644
--- a/SafetyCenter/Resources/shared_res/values-pa/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pa/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"ਸੈਟਿੰਗਾਂ ਸੂਚੀ ਦੀ ਜਾਂਚ ਕਰੋ"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"ਡੀਵਾਈਸ ਜੋਖਮ ਵਿੱਚ ਹੋ ਸਕਦਾ ਹੈ"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"ਡੀਵਾਈਸ ਜੋਖਮ ਵਿੱਚ ਹੈ"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਜੋਖਮ ਵਿੱਚ ਹੋ ਸਕਦੀ ਹੈ"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਜੋਖਮ ਵਿੱਚ ਹੈ"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"ਤੁਹਾਡਾ ਡਾਟਾ ਜੋਖਮ ਵਿੱਚ ਹੋ ਸਕਦਾ ਹੈ"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"ਤੁਹਾਡਾ ਡਾਟਾ ਜੋਖਮ ਵਿੱਚ ਹੈ"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"ਪਾਸਵਰਡਾਂ ਨਾਲ ਛੇੜਛਾੜ ਹੋਈ (ਪੁਰਾਣਾ)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"ਪਾਸਵਰਡਾਂ ਨਾਲ ਛੇੜਛਾੜ ਹੋਈ (ਨਵਾਂ)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਜੋਖਮ ਵਿੱਚ ਹੋ ਸਕਦੀ ਹੈ"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"ਤੁਹਾਡੀ ਸੁਰੱਖਿਆ ਜੋਖਮ ਵਿੱਚ ਹੈ"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"ਸੰਭਾਵੀ ਜੋਖਮਾਂ ਦਾ ਪਤਾ ਲੱਗਿਆ"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"ਜੋਖਮਾਂ ਦਾ ਪਤਾ ਲੱਗਿਆ"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"ਖਾਤਾ ਜੋਖਮ ਵਿੱਚ ਹੋ ਸਕਦਾ ਹੈ"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"ਖਾਤਾ ਜੋਖਮ ਵਿੱਚ ਹੈ"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# ਸੁਚੇਤਨਾ}one{# ਸੁਚੇਤਨਾ}other{# ਸੁਚੇਤਨਾਵਾਂ}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pl/strings.xml b/SafetyCenter/Resources/shared_res/values-pl/strings.xml
index 0f36687b4..05911448f 100644
--- a/SafetyCenter/Resources/shared_res/values-pl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pl/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Sprawdź listę ustawień"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Urządzenie może być zagrożone"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Urządzenie jest zagrożone"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Twoje bezpieczeństwo może być zagrożone"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Twoje bezpieczeństwo jest zagrożone"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Dane mogą być zagrożone"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Dane są zagrożone"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Przejęte hasła (stare)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Przejęte hasła (nowe)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Twoje bezpieczeństwo może być zagrożone"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Twoje bezpieczeństwo jest zagrożone"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Wykryte potencjalne zagrożenia"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Wykryte zagrożenia"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Konto może być zagrożone"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Konto jest zagrożone"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alert}few{# alerty}many{# alertów}other{# alertu}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml b/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
index 721309dd1..e2d4423e6 100644
--- a/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Verificar lista de configurações"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"O dispositivo pode estar em risco"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"O dispositivo está em risco"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Você pode estar em risco"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Você está em risco"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Seus dados podem estar em risco"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Seus dados estão em risco"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Senhas comprometidas (antigas)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Senhas comprometidas (novas)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Você pode estar em risco"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Você está em risco"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Possíveis riscos encontrados"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Riscos encontrados"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"A conta pode estar em risco"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"A conta está em risco"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerta}one{# alerta}many{# de alertas}other{# alertas}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml b/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
index b7b917173..329fb9ebf 100644
--- a/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Verifique a lista de definições"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"O dispositivo pode estar em risco"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"O dispositivo está em risco"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Pode estar em risco"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Está em risco"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Os seus dados podem estar em risco"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Os seus dados estão em risco"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Pal.-passe antigas comprometidas"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Palavras-passe novas comprometidas"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Pode estar em risco"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Está em risco"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potenciais riscos encontrados"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Riscos encontrados"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"A conta pode estar em risco"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"A conta está em risco"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerta}many{# alertas}other{# alertas}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt/strings.xml b/SafetyCenter/Resources/shared_res/values-pt/strings.xml
index 721309dd1..e2d4423e6 100644
--- a/SafetyCenter/Resources/shared_res/values-pt/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Verificar lista de configurações"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"O dispositivo pode estar em risco"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"O dispositivo está em risco"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Você pode estar em risco"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Você está em risco"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Seus dados podem estar em risco"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Seus dados estão em risco"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Senhas comprometidas (antigas)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Senhas comprometidas (novas)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Você pode estar em risco"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Você está em risco"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Possíveis riscos encontrados"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Riscos encontrados"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"A conta pode estar em risco"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"A conta está em risco"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerta}one{# alerta}many{# de alertas}other{# alertas}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ro/strings.xml b/SafetyCenter/Resources/shared_res/values-ro/strings.xml
index 43b705382..21fc3dddc 100644
--- a/SafetyCenter/Resources/shared_res/values-ro/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ro/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Verifică lista de setări"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Dispozitivul poate fi expus la risc"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Dispozitivul este expus riscului"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"E posibil să fii expus(ă) la risc"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Ești expus(ă) la risc"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Datele tale pot fi în pericol"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Datele tale sunt în pericol"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Parole compromise (vechi)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Parole compromise (noi)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"E posibil să fii expus(ă) la risc"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Ești expus(ă) la risc"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"S-au găsit riscuri potențiale"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Riscuri descoperite"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Contul poate fi în pericol"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Contul este în pericol"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alertă}few{# alerte}other{# de alerte}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ru/strings.xml b/SafetyCenter/Resources/shared_res/values-ru/strings.xml
index b187ecfb6..cdf68fd97 100644
--- a/SafetyCenter/Resources/shared_res/values-ru/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ru/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Изучите список настроек"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Устройство может быть под угрозой"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Устройство под угрозой"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Безопасность может быть под угрозой"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Ваша безопасность под угрозой"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Ваши данные могут быть под угрозой"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Ваши данные под угрозой"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Раскрыты старые пароли"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Раскрыты новые пароли"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Безопасность может быть под угрозой"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Ваша безопасность под угрозой"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Обнаружены потенциальные риски"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Обнаружены риски"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Аккаунт может быть под угрозой"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Аккаунт под угрозой"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# оповещение}one{# оповещение}few{# оповещения}many{# оповещений}other{# оповещения}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-si/strings.xml b/SafetyCenter/Resources/shared_res/values-si/strings.xml
index 4fe648370..d6bb6dd86 100644
--- a/SafetyCenter/Resources/shared_res/values-si/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-si/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"සැකසීම් ලැයිස්තුව පරීක්ෂා කරන්න"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"උපාංගය අවදානමේ තිබිය හැක"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"උපාංගය අවදානමේ ඇත"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"ඔබ අවදානමේ සිටිය හැක"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"ඔබ අවදානමේ සිටියි"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"ඔබේ දත්ත අවදානමට ලක් විය හැක"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"ඔබේ දත්ත අවදානමේ ඇත"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"මුරපද අවදානමට ලක් විය (පැරණි)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"මුරපද අවදානමට ලක් විය (නව)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"ඔබ අවදානමේ සිටිය හැක"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"ඔබ අවදානමේ සිටියි"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"විය හැකි අවදානම් හමු විය"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"අවදානම් හමු විය"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"ගිණුම අවදානමේ තිබිය හැක"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"ගිණුම අවදානමේ පවතී"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# ඇඟවීමක්}one{ඇඟවීම් #ක්}other{ඇඟවීම් #ක්}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sk/strings.xml b/SafetyCenter/Resources/shared_res/values-sk/strings.xml
index 02c57e952..ca39b0658 100644
--- a/SafetyCenter/Resources/shared_res/values-sk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sk/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Kontrolný zoznam nastavení"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Zariadenie môže byť ohrozené"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Zariadenie je ohrozené"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Vaše zabezpečenie môže byť ohrozené"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Vaše zabezpečenie je ohrozené"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Vaše údaje môžu byť ohrozené"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Vaše údaje sú ohrozené"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Odhalené heslá (staré)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Odhalené heslá (nové)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Vaše zabezpečenie môže byť ohrozené"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Vaše zabezpečenie je ohrozené"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Boli nájdené potenciálne riziká"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Boli nájdené riziká"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Účet môže byť ohrozený"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Účet je ohrozený"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# upozornenie}few{# upozornenia}many{# alerts}other{# upozornení}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sl/strings.xml b/SafetyCenter/Resources/shared_res/values-sl/strings.xml
index b4c820896..7ce5e01af 100644
--- a/SafetyCenter/Resources/shared_res/values-sl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sl/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Seznam za preverjanje nastavitev"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Naprava je morda ogrožena"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Naprava je ogrožena"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Morda ste ogroženi"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Ste ogroženi"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Podatki so morda ogroženi"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Podatki so ogroženi"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Gesla so ogrožena (stara)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Gesla so ogrožena (nova)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Morda ste ogroženi"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Ste ogroženi"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Najdena so morebitna tveganja"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Najdena so tveganja"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Račun je morda ogrožen"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Račun je ogrožen"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# opozorilo}one{# opozorilo}two{# opozorili}few{# opozorila}other{# opozoril}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sq/strings.xml b/SafetyCenter/Resources/shared_res/values-sq/strings.xml
index 77263fd0d..7e934822a 100644
--- a/SafetyCenter/Resources/shared_res/values-sq/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sq/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Kontrollo listën e cilësimeve"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Pajisja mund të jetë në rrezik"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Pajisja është në rrezik"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Mund të jesh në rrezik"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Je në rrezik"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Të dhënat e tua mund të jenë në rrezik"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Të dhënat e tua janë në rrezik"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Fjalëkalime të komprometuara (të vjetra)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Fjalëkalime të komprometuara (të reja)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Mund të jesh në rrezik"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Je në rrezik"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"U gjetën rreziqe të mundshme"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"U gjetën rreziqe"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Llogaria mund të jetë në rrezik"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Llogaria është në rrezik"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# sinjalizim}other{# sinjalizime}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sr/strings.xml b/SafetyCenter/Resources/shared_res/values-sr/strings.xml
index e46e4f970..9bd52b9cf 100644
--- a/SafetyCenter/Resources/shared_res/values-sr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sr/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Проверите листу подешавања"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Уређај је можда угрожен"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Уређај је угрожен"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Можда сте угрожени"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Угрожени сте"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Подаци су можда угрожени"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Подаци су угрожени"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Лозинке су угрожене (старе)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Лозинке су угрожене (нове)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Можда сте угрожени"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Угрожени сте"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Пронађени су потенцијални ризици"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Пронађени су ризици"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Налог је можда угрожен"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Налог је угрожен"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# обавештење}one{# обавештење}few{# обавештења}other{# обавештења}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sv/strings.xml b/SafetyCenter/Resources/shared_res/values-sv/strings.xml
index cd3489950..5cf5f3455 100644
--- a/SafetyCenter/Resources/shared_res/values-sv/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sv/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Kontrollera listan med inställningar"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Enheten kan vara i fara"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Enheten är i fara"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Du kan vara utsatt för fara"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Du är i fara"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Din data kan vara i fara"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Din data är i fara"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Utsatta lösenord (gamla)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Utsatta lösenord (nya)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Du kan vara i riskzonen"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Du är i riskzonen"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potentiella risker har upptäckts"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risker har upptäckts"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Kontot kan vara i fara"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Kontot är i fara"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# varning}other{# varningar}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sw/strings.xml b/SafetyCenter/Resources/shared_res/values-sw/strings.xml
index 3226a3a10..323c75d25 100644
--- a/SafetyCenter/Resources/shared_res/values-sw/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sw/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Kagua orodha ya mipangilio"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Huenda kifaa kikawa hatarini"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Kifaa kiko hatarini"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Huenda uko hatarini"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Uko hatarini"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Huenda data yako iko hatarini"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Data yako iko hatarini"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Manenosiri yameathiriwa (ya zamani)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Manenosiri yameathiriwa (mapya)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Huenda uko hatarini"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Uko hatarini"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Tumegundua hatari zinazoweza kutokea"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Tumegundua hatari"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Huenda akaunti iko hatarini"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Akaunti iko hatarini"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{Arifa #}other{Arifa #}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ta/strings.xml b/SafetyCenter/Resources/shared_res/values-ta/strings.xml
index d8693c872..a534d0eaf 100644
--- a/SafetyCenter/Resources/shared_res/values-ta/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ta/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"அமைப்புகள் பட்டியலைச் சரிபாருங்கள்"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"சாதனம் ஆபத்தில் இருக்கக்கூடும்"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"சாதனம் ஆபத்தில் உள்ளது"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"நீங்கள் ஆபத்தில் இருக்கலாம்"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"நீங்கள் ஆபத்தில் இருக்கிறீர்கள்"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"உங்கள் தரவு ஆபத்தில் இருக்கலாம்"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"உங்கள் தரவு ஆபத்தில் உள்ளது"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"கடவுச்சொற்கள் களவாடப்பட்டுள்ளன (பழையவை)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"கடவுச்சொற்கள் களவாடப்பட்டுள்ளன (புதியவை)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"நீங்கள் ஆபத்தில் இருக்கலாம்"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"நீங்கள் ஆபத்தில் இருக்கிறீர்கள்"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"சாத்தியமுள்ள ஆபத்து கண்டறியப்பட்டன"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"ஆபத்துகள் கண்டறியப்பட்டுள்ளன"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"கணக்கு ஆபத்தில் இருக்கலாம்"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"கணக்கு ஆபத்தில் உள்ளது"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# எச்சரிக்கை}other{# எச்சரிக்கைகள்}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-te/strings.xml b/SafetyCenter/Resources/shared_res/values-te/strings.xml
index 9eb6341fe..98bbf9c31 100644
--- a/SafetyCenter/Resources/shared_res/values-te/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-te/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"సెట్టింగ్‌ల లిస్ట్‌ను చెక్ చేయండి"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"పరికరం ప్రమాదంలో ఉండవచ్చు"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"పరికరం ప్రమాదంలో ఉంది"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"మీరు రిస్క్‌లో ఉండవచ్చు"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"మీరు రిస్క్‌లో ఉన్నారు"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"మీ డేటా ప్రమాదంలో ఉండవచ్చు"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"మీ డేటా ప్రమాదంలో ఉంది"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"(పాత) సురక్షితం కాని పాస్‌వర్డ్స్"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"(కొత్త) సురక్షితం కాని పాస్‌వర్డ్స్"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"మీరు రిస్క్‌లో ఉండవచ్చు"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"మీరు రిస్క్‌లో ఉన్నారు"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"అవకాశం ఉన్న రిస్క్‌లు కనుగొనబడ్డాయి"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"రిస్క్‌లు కనుగొనబడ్డాయి"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"ఖాతా ప్రమాదంలో ఉండవచ్చు"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"ఖాతా ప్రమాదంలో ఉంది"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# అలర్ట్}other{# అలర్ట్‌లు}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-th/strings.xml b/SafetyCenter/Resources/shared_res/values-th/strings.xml
index e457d5ef6..c627090cb 100644
--- a/SafetyCenter/Resources/shared_res/values-th/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-th/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"ตรวจสอบรายการการตั้งค่า"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"อุปกรณ์อาจมีความเสี่ยง"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"อุปกรณ์มีความเสี่ยง"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"คุณอาจมีความเสี่ยง"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"คุณกำลังมีความเสี่ยง"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"ข้อมูลของคุณอาจมีความเสี่ยง"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"ข้อมูลของคุณมีความเสี่ยง"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"รหัสผ่าน (เก่า) ถูกละเมิด"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"รหัสผ่าน (ใหม่) ถูกละเมิด"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"คุณอาจมีความเสี่ยง"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"คุณกำลังมีความเสี่ยง"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"พบความเสี่ยงที่อาจเกิดขึ้น"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"พบความเสี่ยง"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"บัญชีอาจมีความเสี่ยง"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"บัญชีมีความเสี่ยง"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{การแจ้งเตือน # รายการ}other{การแจ้งเตือน # รายการ}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-tl/strings.xml b/SafetyCenter/Resources/shared_res/values-tl/strings.xml
index 21372a1a8..12ab86d86 100644
--- a/SafetyCenter/Resources/shared_res/values-tl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-tl/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Tingnan ang listahan ng mga setting"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Posibleng nanganganib ang device"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Nanganganib ang device"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Posibleng nanganganib ka"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Nanganganib ka"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Posibleng nanganganib ang data mo"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Nanganganib ang iyong data"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Nakompromiso ang password (luma)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Nakompromiso ang password (bago)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Posibleng nanganganib ka"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Nanganganib ka"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"May nakitang potensyal na panganib"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"May nakitang mga panganib"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Posibleng nanganganib ang account"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Nanganganib ang account"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# alerto}one{# alerto}other{# na alerto}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-tr/strings.xml b/SafetyCenter/Resources/shared_res/values-tr/strings.xml
index d340bccf1..3656c75d4 100644
--- a/SafetyCenter/Resources/shared_res/values-tr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-tr/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Ayarlar listesini kontrol edin"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Cihaz risk altında olabilir"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Cihaz risk altında"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Risk altında olabilirsiniz"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Risk altındasınız"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Verileriniz risk altında olabilir"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Verileriniz risk altında"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Şifrelerin güvenliği ihlal edildi (eski)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Şifrelerin güvenliği ihlal edildi (yeni)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Risk altında olabilirsiniz"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Risk altındasınız"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Olası riskler bulundu"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Risk bulundu"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Hesap risk altında olabilir"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Hesap risk altında"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# uyarı}other{# uyarı}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-uk/strings.xml b/SafetyCenter/Resources/shared_res/values-uk/strings.xml
index 369d5943f..944509dbe 100644
--- a/SafetyCenter/Resources/shared_res/values-uk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-uk/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Перегляньте список налаштувань"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Можливо, пристрій під загрозою"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Пристрій під загрозою"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Можливо, ваша безпека під загрозою"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Ваша безпека під загрозою"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Можлива загроза даним"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Ваші дані під загрозою"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Паролі зламано (старі)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Паролі зламано (нові)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Можливо, ваша безпека під загрозою"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Ваша безпека під загрозою"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Виявлено потенційні загрози безпеці"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Виявлено загрози безпеці"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Можлива загроза обліковому запису"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Обліковий запис під загрозою"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# сповіщення}one{# сповіщення}few{# сповіщення}many{# сповіщень}other{# сповіщення}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ur/strings.xml b/SafetyCenter/Resources/shared_res/values-ur/strings.xml
index 85734b28b..e6edf41a2 100644
--- a/SafetyCenter/Resources/shared_res/values-ur/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ur/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"ترتیبات کی فہرست چیک کریں"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"آلہ خطرے میں ہو سکتا ہے"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"آلہ خطرے میں ہے"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"آپ خطرے میں ہو سکتے ہیں"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"آپ خطرے میں ہے"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"آپ کا ڈیٹا خطرے میں ہو سکتا ہے"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"آپ کا ڈیٹا خطرے میں ہے"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"متاثرہ پاس ورڈز (پرانا)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"متاثرہ پاس ورڈز (نیا)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"آپ خطرے میں ہو سکتے ہیں"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"آپ خطرے میں ہے"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"ممکنہ خطرات پائے گئے"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"خطرات پائے گئے"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"اکاؤنٹ خطرے میں ہو سکتا ہے"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"اکاؤنٹ خطرے میں ہے"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# الرٹ}other{# الرٹس}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-uz/strings.xml b/SafetyCenter/Resources/shared_res/values-uz/strings.xml
index 8495f0ae8..3995f4995 100644
--- a/SafetyCenter/Resources/shared_res/values-uz/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-uz/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Sozlamalar roʻyxatini tekshirish"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Qurilma xavf ostida boʻlishi mumkin"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Qurilma xavf ostida"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Xavf ostida boʻlishingiz mumkin"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Xavf ostidasiz"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Maʼlumotlar xavf ostida shekilli"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Maʼlumotlaringiz xavf ostida"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Parollar oshkor qilindi (eski)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Parollar oshkor qilindi (yangi)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Xavf ostida boʻlishingiz mumkin"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Xavf ostidasiz"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Potentsial xavflar topildi"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Xavflar topildi"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Hisob xavf ostida shekilli"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Hisob xavf ostida"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# ta ogohlantirish}other{# ta ogohlantirish}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-vi/strings.xml b/SafetyCenter/Resources/shared_res/values-vi/strings.xml
index 8662a142f..f04eb0640 100644
--- a/SafetyCenter/Resources/shared_res/values-vi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-vi/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Kiểm tra danh sách cài đặt"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Thiết bị có thể gặp rủi ro"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Thiết bị đang gặp rủi ro"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Bạn có thể gặp rủi ro"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Bạn đang gặp rủi ro"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Dữ liệu của bạn có thể gặp rủi ro"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Dữ liệu của bạn đang gặp rủi ro"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Mật khẩu bị lộ (mật khẩu cũ)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Mật khẩu bị lộ (mật khẩu mới)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Bạn có thể gặp rủi ro"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Bạn đang gặp rủi ro"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Đã phát hiện rủi ro tiềm ẩn"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Đã phát hiện rủi ro"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"Tài khoản có thể gặp rủi ro"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"Tài khoản đang gặp rủi ro"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# cảnh báo}other{# cảnh báo}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
index b467b3258..da7395d1f 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"请检查设置列表"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"设备可能存在风险"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"设备存在风险"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"您可能有风险"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"您目前有风险"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"您的数据可能存在风险"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"您的数据存在风险"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"密码被盗(旧密码)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"密码被盗(新密码)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"您可能有风险"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"您目前有风险"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"发现潜在风险"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"发现风险"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"帐号可能存在风险"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"帐号存在风险"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# 条提醒}other{# 条提醒}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
index 6c99e38a2..420c5ddc5 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"查看設定清單"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"裝置可能存在風險"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"裝置存在風險"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"您可能面臨風險"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"您正面臨風險"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"您的資料可能面臨風險"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"您的資料正面臨風險"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"被盜用的密碼 (舊)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"被盜用的密碼 (新)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"您可能面臨風險"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"您正面臨風險"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"系統發現潛在風險"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"系統發現風險"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"帳戶可能面臨風險"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"裝置正面臨風險"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# 個警示}other{# 個警示}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
index ad9f255ed..4bc295695 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"檢查設定清單"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"裝置可能有風險"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"裝置有風險"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"你可能有風險"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"你目前有風險"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"你的資料可能面臨風險"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"你的資料目前有風險"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"舊密碼已遭外洩"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"新密碼已遭外洩"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"你的帳戶可能有風險"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"你的帳戶有風險"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"發現潛在風險"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"發現風險"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"帳戶可能面臨風險"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"帳戶目前有風險"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{# 個警示}other{# 個警示}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zu/strings.xml b/SafetyCenter/Resources/shared_res/values-zu/strings.xml
index fb23e9e35..637577a5e 100644
--- a/SafetyCenter/Resources/shared_res/values-zu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zu/strings.xml
@@ -25,8 +25,14 @@
<string name="overall_severity_level_ok_review_summary" msgid="7743619617413076275">"Hlola uhlu lwamasethingi"</string>
<string name="overall_severity_level_device_recommendation_title" msgid="5250040236433061827">"Idivayisi ingaba sengozini"</string>
<string name="overall_severity_level_critical_device_warning_title" msgid="5901771721834272596">"Idivayisi isengozini"</string>
- <string name="overall_severity_level_safety_recommendation_title" msgid="6436208984463981167">"Ungaba sengozini"</string>
- <string name="overall_severity_level_critical_safety_warning_title" msgid="1039142045555227172">"Usengozini"</string>
+ <string name="overall_severity_level_data_recommendation_title" msgid="1424269714861655302">"Idatha yakho isengozini"</string>
+ <string name="overall_severity_level_critical_data_warning_title" msgid="1012704126634698604">"Idatha yakho isengozini"</string>
+ <string name="overall_severity_level_passwords_recommendation_title" msgid="8625105570296877719">"Amaphasiwedi onakalisiwe (amadala)"</string>
+ <string name="overall_severity_level_critical_passwords_warning_title" msgid="7859047988648851217">"Amaphasiwedi onakalisiwe (amasha)"</string>
+ <string name="overall_severity_level_personal_recommendation_title" msgid="5493814377982623779">"Kungenzeka usengozini"</string>
+ <string name="overall_severity_level_critical_personal_warning_title" msgid="5070434468955164734">"Usengozini"</string>
+ <string name="overall_severity_level_safety_recommendation_title" msgid="4291797434760242793">"Izingozi ezingaba khona zitholakele"</string>
+ <string name="overall_severity_level_critical_safety_warning_title" msgid="6666640109779586868">"Izingozi ezitholakele"</string>
<string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"I-akhawunti ingaba sengozini"</string>
<string name="overall_severity_level_critical_account_warning_title" msgid="4402278408972158353">"I-akhawunti isengcupheni"</string>
<string name="overall_severity_n_alerts_summary" msgid="1105615451561197136">"{count,plural, =1{Isexwayiso esi-#}one{Izexwayiso ezingu-#}other{Izexwayiso ezingu-#}}"</string>
diff --git a/SafetyCenter/Resources/shared_res/values/strings.xml b/SafetyCenter/Resources/shared_res/values/strings.xml
index 42b91e012..964812e6d 100644
--- a/SafetyCenter/Resources/shared_res/values/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values/strings.xml
@@ -40,11 +40,29 @@
<!-- Title for the overall Safety Center status when the user security and privacy signals are putting them at risk [CHAR LIMIT=35] -->
<string name="overall_severity_level_critical_device_warning_title">Device is at risk</string>
+ <!-- Title for the overall Safety Center status when the user's data could potentially be at risk [CHAR LIMIT=35] -->
+ <string name="overall_severity_level_data_recommendation_title">Your data may be at risk</string>
+
+ <!-- Title for the overall Safety Center status when the user's data is at risk [CHAR LIMIT=35] -->
+ <string name="overall_severity_level_critical_data_warning_title">Your data is at risk</string>
+
+ <!-- Title for the overall Safety Center status when the user's passwords could potentially be at risk (old or unused passwords) [CHAR LIMIT=35] -->
+ <string name="overall_severity_level_passwords_recommendation_title">Passwords compromised (old)</string>
+
+ <!-- Title for the overall Safety Center status when the user's passwords are at risk (new passwords) [CHAR LIMIT=35] -->
+ <string name="overall_severity_level_critical_passwords_warning_title">Passwords compromised (new)</string>
+
+ <!-- Title for the overall Safety Center status when the user's personal safety could potentially be at risk [CHAR LIMIT=35] -->
+ <string name="overall_severity_level_personal_recommendation_title">You may be at risk</string>
+
+ <!-- Title for the overall Safety Center status when the user's personal safety is at risk [CHAR LIMIT=35] -->
+ <string name="overall_severity_level_critical_personal_warning_title">You are at risk</string>
+
<!-- Title for the overall Safety Center status when the user security and privacy signals could potentially put their general safety at risk [CHAR LIMIT=35] -->
- <string name="overall_severity_level_safety_recommendation_title">You may be at risk</string>
+ <string name="overall_severity_level_safety_recommendation_title">Potential risks found</string>
<!-- Title for the overall Safety Center status when the user security and privacy signals are putting their general safety at risk [CHAR LIMIT=35] -->
- <string name="overall_severity_level_critical_safety_warning_title">You are at risk</string>
+ <string name="overall_severity_level_critical_safety_warning_title">Risks found</string>
<!-- Title for the overall Safety Center status when the user security and privacy signals could potentially put their account at risk [CHAR LIMIT=35] -->
<string name="overall_severity_level_account_recommendation_title">Account may be at risk</string>
diff --git a/SafetyCenter/TEST_MAPPING b/SafetyCenter/TEST_MAPPING
index e2eb5ef5d..c702ee852 100644
--- a/SafetyCenter/TEST_MAPPING
+++ b/SafetyCenter/TEST_MAPPING
@@ -24,5 +24,15 @@
{
"name": "SafetyCenterFunctionalTestCases"
}
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsSafetyCenterTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
]
}
diff --git a/SafetyLabel/Android.bp b/SafetyLabel/Android.bp
new file mode 100644
index 000000000..119890d3f
--- /dev/null
+++ b/SafetyLabel/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+filegroup {
+ name: "safety-label-java-sources",
+ srcs: [
+ "java/**/*.java",
+ ],
+ path: "java",
+ visibility: ["//visibility:private"],
+}
+
+java_library {
+ name: "safety-label",
+ sdk_version: "system_current",
+ min_sdk_version: "30",
+ target_sdk_version: "33",
+ srcs: [
+ ":safety-label-java-sources",
+ ],
+ libs: [
+ "androidx.annotation_annotation",
+ "framework-annotations-lib",
+ ],
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ installable: false,
+ visibility: [
+ "//packages/modules/Permission:__subpackages__",
+ ],
+}
+
diff --git a/SafetyLabel/java/com/android/permission/safetylabel/DataCategory.java b/SafetyLabel/java/com/android/permission/safetylabel/DataCategory.java
new file mode 100644
index 000000000..395468c81
--- /dev/null
+++ b/SafetyLabel/java/com/android/permission/safetylabel/DataCategory.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel;
+
+import android.os.PersistableBundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.permission.safetylabel.DataLabelConstants.DataUsage;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Data usage category representation containing one or more {@link DataType}. Valid category keys
+ * are defined in {@link DataCategoryConstants}, each category has a valid set of types {@link
+ * DataType}, which are mapped in {@link DataTypeConstants}
+ */
+public class DataCategory {
+ private final Map<String, DataType> mDataTypes;
+
+ private DataCategory(@NonNull Map<String, DataType> dataTypes) {
+ this.mDataTypes = dataTypes;
+ }
+
+ /**
+ * Returns a {@link java.util.Collections.UnmodifiableMap} of {@link String} category to {@link
+ * DataCategory} created by parsing a {@link PersistableBundle}
+ */
+ @NonNull
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ static Map<String, DataCategory> getDataCategoryMap(
+ @Nullable PersistableBundle dataLabelBundle, @DataUsage @NonNull String dataUsage) {
+ if (dataLabelBundle == null) {
+ return Collections.emptyMap();
+ }
+
+ PersistableBundle dataCategoryMapBundle = dataLabelBundle.getPersistableBundle(dataUsage);
+ if (dataCategoryMapBundle == null) {
+ return Collections.emptyMap();
+ }
+
+ Map<String, DataCategory> dataCategoryMap = new HashMap<>();
+ for (String category : DataCategoryConstants.VALID_CATEGORIES) {
+ DataCategory dataCategory = getDataCategory(dataCategoryMapBundle, dataUsage, category);
+ if (dataCategory != null) {
+ dataCategoryMap.put(category, dataCategory);
+ }
+ }
+ return Collections.unmodifiableMap(dataCategoryMap);
+ }
+
+ /**
+ * Returns a {@link DataCategory} created by parsing a {@link PersistableBundle}, or {@code
+ * null} if parsing results in an invalid or empty DataCategory
+ */
+ @Nullable
+ @VisibleForTesting
+ static DataCategory getDataCategory(
+ @Nullable PersistableBundle dataCategoryMapBundle,
+ @NonNull String dataUsage,
+ @NonNull String category) {
+ if (dataCategoryMapBundle == null) {
+ return null;
+ }
+
+ PersistableBundle dataCategoryBundle = dataCategoryMapBundle.getPersistableBundle(category);
+
+ Map<String, DataType> dataTypeMap =
+ DataType.getDataTypeMap(dataCategoryBundle, dataUsage, category);
+ if (dataTypeMap.isEmpty()) {
+ return null;
+ }
+
+ return new DataCategory(dataTypeMap);
+ }
+
+ /** Return the type {@link Map} of String type key to {@link DataType} */
+ @NonNull
+ public Map<String, DataType> getDataTypes() {
+ return mDataTypes;
+ }
+}
diff --git a/SafetyLabel/java/com/android/permission/safetylabel/DataCategoryConstants.java b/SafetyLabel/java/com/android/permission/safetylabel/DataCategoryConstants.java
new file mode 100644
index 000000000..af04220c0
--- /dev/null
+++ b/SafetyLabel/java/com/android/permission/safetylabel/DataCategoryConstants.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Constants for determining valid {@link String} data types for usage within {@link SafetyLabel},
+ * {@link DataCategory}, and {@link DataType}
+ */
+public class DataCategoryConstants {
+
+ /** List of valid Safety Label data collection/sharing categories */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "CATEGORY_",
+ value = {
+ CATEGORY_PERSONAL,
+ CATEGORY_FINANCIAL,
+ CATEGORY_LOCATION,
+ CATEGORY_EMAIL_TEXT_MESSAGE,
+ CATEGORY_PHOTO_VIDEO,
+ CATEGORY_AUDIO,
+ CATEGORY_STORAGE,
+ CATEGORY_HEALTH_FITNESS,
+ CATEGORY_CONTACTS,
+ CATEGORY_CALENDAR,
+ CATEGORY_IDENTIFIERS,
+ CATEGORY_APP_PERFORMANCE,
+ CATEGORY_ACTIONS_IN_APP,
+ CATEGORY_SEARCH_AND_BROWSING,
+ })
+ public @interface Category {}
+
+ public static final String CATEGORY_PERSONAL = "personal";
+ public static final String CATEGORY_FINANCIAL = "financial";
+ public static final String CATEGORY_LOCATION = "location";
+ public static final String CATEGORY_EMAIL_TEXT_MESSAGE = "email_text_message";
+ public static final String CATEGORY_PHOTO_VIDEO = "photo_video";
+ public static final String CATEGORY_AUDIO = "audio";
+ public static final String CATEGORY_STORAGE = "storage";
+ public static final String CATEGORY_HEALTH_FITNESS = "health_fitness";
+ public static final String CATEGORY_CONTACTS = "contacts";
+ public static final String CATEGORY_CALENDAR = "calendar";
+ public static final String CATEGORY_IDENTIFIERS = "identifiers";
+ public static final String CATEGORY_APP_PERFORMANCE = "app_performance";
+ public static final String CATEGORY_ACTIONS_IN_APP = "actions_in_app";
+ public static final String CATEGORY_SEARCH_AND_BROWSING = "search_and_browsing";
+
+ /** Set of valid categories */
+ @DataCategoryConstants.Category
+ public static final Set<String> VALID_CATEGORIES =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ CATEGORY_PERSONAL,
+ CATEGORY_FINANCIAL,
+ CATEGORY_LOCATION,
+ CATEGORY_EMAIL_TEXT_MESSAGE,
+ CATEGORY_PHOTO_VIDEO,
+ CATEGORY_AUDIO,
+ CATEGORY_STORAGE,
+ CATEGORY_HEALTH_FITNESS,
+ CATEGORY_CONTACTS,
+ CATEGORY_CALENDAR,
+ CATEGORY_IDENTIFIERS,
+ CATEGORY_APP_PERFORMANCE,
+ CATEGORY_ACTIONS_IN_APP,
+ CATEGORY_SEARCH_AND_BROWSING)));
+
+ /** Returns {@link Set} of valid {@link String} category keys */
+ public static Set<String> getValidDataCategories() {
+ return VALID_CATEGORIES;
+ }
+
+ private DataCategoryConstants() {
+ /* do nothing - hide constructor */
+ }
+}
diff --git a/SafetyLabel/java/com/android/permission/safetylabel/DataLabel.java b/SafetyLabel/java/com/android/permission/safetylabel/DataLabel.java
new file mode 100644
index 000000000..564d5479f
--- /dev/null
+++ b/SafetyLabel/java/com/android/permission/safetylabel/DataLabel.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel;
+
+import static com.android.permission.safetylabel.DataLabelConstants.DATA_USAGE_COLLECTED;
+import static com.android.permission.safetylabel.DataLabelConstants.DATA_USAGE_SHARED;
+
+import android.os.PersistableBundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.Map;
+
+/**
+ * Data label representation with data shared and data collected maps containing zero or more
+ * {@link DataCategory}
+ */
+public class DataLabel {
+ @VisibleForTesting static final String KEY_DATA_LABEL = "data_labels";
+ private final Map<String, DataCategory> mDataCollected;
+ private final Map<String, DataCategory> mDataShared;
+
+ public DataLabel(
+ @NonNull Map<String, DataCategory> dataCollected,
+ @NonNull Map<String, DataCategory> dataShared) {
+ mDataCollected = dataCollected;
+ mDataShared = dataShared;
+ }
+
+ /** Returns a {@link DataLabel} created by parsing a SafetyLabel {@link PersistableBundle} */
+ @NonNull
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ public static DataLabel getDataLabel(@Nullable PersistableBundle safetyLabelBundle) {
+ if (safetyLabelBundle == null) {
+ return null;
+ }
+
+ PersistableBundle dataLabelBundle = safetyLabelBundle.getPersistableBundle(KEY_DATA_LABEL);
+ if (dataLabelBundle == null) {
+ return null;
+ }
+
+ Map<String, DataCategory> dataCollectedCategoryMap =
+ DataCategory.getDataCategoryMap(dataLabelBundle, DATA_USAGE_COLLECTED);
+ Map<String, DataCategory> dataSharedCategoryMap =
+ DataCategory.getDataCategoryMap(dataLabelBundle, DATA_USAGE_SHARED);
+ return new DataLabel(dataCollectedCategoryMap, dataSharedCategoryMap);
+ }
+
+ /**
+ * Returns the data collected {@link Map} of {@link
+ * com.android.permission.safetylabel.DataCategoryConstants.Category} to {@link DataCategory}
+ */
+ @NonNull
+ public Map<String, DataCategory> getDataCollected() {
+ return mDataCollected;
+ }
+
+ /**
+ * Returns the data shared {@link Map} of {@link
+ * com.android.permission.safetylabel.DataCategoryConstants.Category} to {@link DataCategory}
+ */
+ @NonNull
+ public Map<String, DataCategory> getDataShared() {
+ return mDataShared;
+ }
+}
diff --git a/SafetyLabel/java/com/android/permission/safetylabel/DataLabelConstants.java b/SafetyLabel/java/com/android/permission/safetylabel/DataLabelConstants.java
new file mode 100644
index 000000000..f592d9b0f
--- /dev/null
+++ b/SafetyLabel/java/com/android/permission/safetylabel/DataLabelConstants.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+
+/**
+ * Constants and util methods for determining valid {@link String} data types for usage within
+ * {@link SafetyLabel} and {@link DataLabel}
+ */
+public class DataLabelConstants {
+
+ /** List of valid Safety Label data usages. Shared vs Collected */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "DATA_USAGE_",
+ value = {
+ DATA_USAGE_COLLECTED,
+ DATA_USAGE_SHARED,
+ })
+ public @interface DataUsage {}
+ public static final String DATA_USAGE_COLLECTED = "data_collected";
+ public static final String DATA_USAGE_SHARED = "data_shared";
+}
diff --git a/SafetyLabel/java/com/android/permission/safetylabel/DataPurposeConstants.java b/SafetyLabel/java/com/android/permission/safetylabel/DataPurposeConstants.java
new file mode 100644
index 000000000..0e1bb31ce
--- /dev/null
+++ b/SafetyLabel/java/com/android/permission/safetylabel/DataPurposeConstants.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Constants for determining valid {@link Integer} data usage purposes for usage within
+ * {@link DataType}
+ */
+public class DataPurposeConstants {
+
+ /** List of valid data usage purposes */
+ @Retention(SOURCE)
+ @IntDef(
+ prefix = "PURPOSE_",
+ value = {
+ PURPOSE_APP_FUNCTIONALITY,
+ PURPOSE_ANALYTICS,
+ PURPOSE_DEVELOPER_COMMUNICATIONS,
+ PURPOSE_FRAUD_PREVENTION_SECURITY,
+ PURPOSE_ADVERTISING,
+ PURPOSE_PERSONALIZATION,
+ PURPOSE_ACCOUNT_MANAGEMENT,
+ })
+ public @interface Purpose {}
+
+ public static final int PURPOSE_APP_FUNCTIONALITY = 1;
+ public static final int PURPOSE_ANALYTICS = 2;
+ public static final int PURPOSE_DEVELOPER_COMMUNICATIONS = 3;
+ public static final int PURPOSE_FRAUD_PREVENTION_SECURITY = 4;
+ public static final int PURPOSE_ADVERTISING = 5;
+ public static final int PURPOSE_PERSONALIZATION = 6;
+ public static final int PURPOSE_ACCOUNT_MANAGEMENT = 7;
+ // RESERVED/DEPRECATED = 8
+ // RESERVED/DEPRECATED = 9
+
+ /** {@link Set} of valid {@link Integer} purposes */
+ @Purpose
+ public static final Set<Integer> VALID_PURPOSES =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ PURPOSE_APP_FUNCTIONALITY,
+ PURPOSE_ANALYTICS,
+ PURPOSE_DEVELOPER_COMMUNICATIONS,
+ PURPOSE_FRAUD_PREVENTION_SECURITY,
+ PURPOSE_ADVERTISING,
+ PURPOSE_PERSONALIZATION,
+ PURPOSE_ACCOUNT_MANAGEMENT)));
+
+ /** Returns {@link Set} of valid {@link Integer} purpose */
+ @Purpose
+ public static Set<Integer> getValidPurposes() {
+ return VALID_PURPOSES;
+ }
+}
diff --git a/SafetyLabel/java/com/android/permission/safetylabel/DataType.java b/SafetyLabel/java/com/android/permission/safetylabel/DataType.java
new file mode 100644
index 000000000..cc63b07ac
--- /dev/null
+++ b/SafetyLabel/java/com/android/permission/safetylabel/DataType.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel;
+
+import android.os.PersistableBundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.permission.safetylabel.DataPurposeConstants.Purpose;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Data usage type representation. Types are specific to a {@link DataCategory} and contains
+ * metadata related to the data usage purpose.
+ */
+public class DataType {
+ @VisibleForTesting static final String KEY_PURPOSES = "purposes";
+ @VisibleForTesting static final String KEY_USER_CONTROL = "user_control";
+ @VisibleForTesting static final String KEY_EPHEMERAL = "ephemeral";
+
+ @Purpose private final Set<Integer> mPurposeSet;
+ private final Boolean mUserControl;
+ private final Boolean mEphemeral;
+
+ private DataType(
+ @NonNull @Purpose Set<Integer> purposeSet,
+ @Nullable Boolean userControl,
+ @Nullable Boolean ephemeral) {
+ this.mPurposeSet = purposeSet;
+ this.mUserControl = userControl;
+ this.mEphemeral = ephemeral;
+ }
+
+ /**
+ * Returns a {@link java.util.Collections.UnmodifiableMap} of String type key to {@link
+ * DataType} created by parsing a {@link PersistableBundle}
+ */
+ @NonNull
+ @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
+ static Map<String, DataType> getDataTypeMap(
+ @Nullable PersistableBundle dataCategoryBundle,
+ @NonNull String dataUsage,
+ @NonNull String category) {
+ if (dataCategoryBundle == null || dataCategoryBundle.isEmpty()) {
+ return Collections.emptyMap();
+ }
+
+ Map<String, DataType> dataTypeMap = new HashMap<>();
+ Set<String> validDataTypesForCategory =
+ DataTypeConstants.getValidDataTypesForCategory(category);
+ for (String type : validDataTypesForCategory) {
+ PersistableBundle dataTypeBundle = dataCategoryBundle.getPersistableBundle(type);
+ DataType dataType = getDataType(dataTypeBundle, dataUsage);
+ if (dataType != null) {
+ dataTypeMap.put(type, dataType);
+ }
+ }
+ return Collections.unmodifiableMap(dataTypeMap);
+ }
+
+ /**
+ * Returns {@link DataType} created by parsing the {@link android.os.PersistableBundle}
+ * representation of DataType
+ */
+ @Nullable
+ @VisibleForTesting
+ static DataType getDataType(
+ @Nullable PersistableBundle dataTypeBundle, @NonNull String dataUsage) {
+ if (dataTypeBundle == null || dataTypeBundle.isEmpty()) {
+ return null;
+ }
+
+ // purposes are required, if not present, treat as invalid
+ int[] purposeList = dataTypeBundle.getIntArray(KEY_PURPOSES);
+ if (purposeList == null || purposeList.length == 0) {
+ return null;
+ }
+
+ // Filter to set of valid purposes, and return invalid if empty
+ Set<Integer> purposeSet = new HashSet<>();
+ for (int purpose : purposeList) {
+ if (DataPurposeConstants.getValidPurposes().contains(purpose)) {
+ purposeSet.add(purpose);
+ }
+ }
+ if (purposeSet.isEmpty()) {
+ return null;
+ }
+
+ // Only set/expected for DATA COLLECTED. DATA SHARED should be null
+ Boolean userControl = null;
+ Boolean ephemeral = null;
+ if (DataLabelConstants.DATA_USAGE_COLLECTED.equals(dataUsage)) {
+ userControl =
+ dataTypeBundle.containsKey(KEY_USER_CONTROL)
+ ? dataTypeBundle.getBoolean(KEY_USER_CONTROL)
+ : null;
+ ephemeral =
+ dataTypeBundle.containsKey(KEY_EPHEMERAL)
+ ? dataTypeBundle.getBoolean(KEY_EPHEMERAL)
+ : null;
+ }
+
+ return new DataType(purposeSet, userControl, ephemeral);
+ }
+
+ /**
+ * Returns {@link Set} of valid {@link Integer} purposes for using the associated data category
+ * and type
+ */
+ @NonNull
+ public Set<Integer> getPurposeSet() {
+ return mPurposeSet;
+ }
+
+ /**
+ * For data-collected, returns {@code true} if data usage is user optional and {@code false} if
+ * data usage is required. Should return {@code null} for data-shared.
+ */
+ @Nullable
+ public Boolean getUserControl() {
+ return mUserControl;
+ }
+
+ /**
+ * For data-collected, returns {@code true} if data usage is user optional and {@code false} if
+ * data usage is processed ephemerally. Should return {@code null} for data-shared.
+ */
+ @Nullable
+ public Boolean getEphemeral() {
+ return mEphemeral;
+ }
+}
diff --git a/SafetyLabel/java/com/android/permission/safetylabel/DataTypeConstants.java b/SafetyLabel/java/com/android/permission/safetylabel/DataTypeConstants.java
new file mode 100644
index 000000000..1b4819c41
--- /dev/null
+++ b/SafetyLabel/java/com/android/permission/safetylabel/DataTypeConstants.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.StringDef;
+import android.util.ArrayMap;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Constants and util methods for determining valid {@link String} data categories for usage within
+ * {@link SafetyLabel}, {@link DataCategory}, and {@link DataType}
+ */
+public class DataTypeConstants {
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_PERSONAL}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "PERSONAL_",
+ value = {
+ PERSONAL_NAME,
+ PERSONAL_EMAIL_ADDRESS,
+ PERSONAL_PHYSICAL_ADDRESS,
+ PERSONAL_PHONE_NUMBER,
+ PERSONAL_RACE_ETHNICITY,
+ PERSONAL_POLITICAL_OR_RELIGIOUS_BELIEFS,
+ PERSONAL_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY,
+ PERSONAL_IDENTIFIERS,
+ PERSONAL_OTHER,
+ })
+ public @interface PersonalType {}
+
+ public static final String PERSONAL_NAME = "NAME";
+ public static final String PERSONAL_EMAIL_ADDRESS = "EMAIL_ADDRESS";
+ public static final String PERSONAL_PHYSICAL_ADDRESS = "PHYSICAL_ADDRESS";
+ public static final String PERSONAL_PHONE_NUMBER = "PHONE_NUMBER";
+ public static final String PERSONAL_RACE_ETHNICITY = "RACE_ETHNICITY";
+ public static final String PERSONAL_POLITICAL_OR_RELIGIOUS_BELIEFS =
+ "POLITICAL_OR_RELIGIOUS_BELIEFS";
+ public static final String PERSONAL_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY =
+ "SEXUAL_ORIENTATION_OR_GENDER_IDENTITY";
+ public static final String PERSONAL_IDENTIFIERS = "PERSONAL_IDENTIFIERS";
+ public static final String PERSONAL_OTHER = "OTHER";
+
+ @PersonalType
+ private static final Set<String> VALID_TYPES_PERSONAL =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ PERSONAL_NAME,
+ PERSONAL_EMAIL_ADDRESS,
+ PERSONAL_PHYSICAL_ADDRESS,
+ PERSONAL_PHONE_NUMBER,
+ PERSONAL_RACE_ETHNICITY,
+ PERSONAL_POLITICAL_OR_RELIGIOUS_BELIEFS,
+ PERSONAL_SEXUAL_ORIENTATION_OR_GENDER_IDENTITY,
+ PERSONAL_IDENTIFIERS,
+ PERSONAL_OTHER)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_FINANCIAL}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "FINANCIAL_",
+ value = {
+ FINANCIAL_CARD_BANK_ACCOUNT,
+ FINANCIAL_PURCHASE_HISTORY,
+ FINANCIAL_CREDIT_SCORE,
+ FINANCIAL_OTHER,
+ })
+ public @interface FinancialType {}
+
+ public static final String FINANCIAL_CARD_BANK_ACCOUNT = "CARD_BANK_ACCOUNT";
+ public static final String FINANCIAL_PURCHASE_HISTORY = "PURCHASE_HISTORY";
+ public static final String FINANCIAL_CREDIT_SCORE = "CREDIT_SCORE";
+ public static final String FINANCIAL_OTHER = "OTHER";
+
+ @FinancialType
+ private static final Set<String> VALID_TYPES_FINANCIAL =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ FINANCIAL_CARD_BANK_ACCOUNT,
+ FINANCIAL_PURCHASE_HISTORY,
+ FINANCIAL_CREDIT_SCORE,
+ FINANCIAL_OTHER)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_LOCATION}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "LOCATION_",
+ value = {
+ LOCATION_APPROX_LOCATION,
+ LOCATION_PRECISE_LOCATION,
+ })
+ public @interface LocationType {}
+
+ public static final String LOCATION_APPROX_LOCATION = "APPROX_LOCATION";
+ public static final String LOCATION_PRECISE_LOCATION = "PRECISE_LOCATION";
+
+ @LocationType
+ private static final Set<String> VALID_TYPES_LOCATION =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(LOCATION_APPROX_LOCATION, LOCATION_PRECISE_LOCATION)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_EMAIL_TEXT_MESSAGE}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "EMAIL_TEXT_MESSAGE_",
+ value = {
+ EMAIL_TEXT_MESSAGE_EMAILS,
+ EMAIL_TEXT_MESSAGE_TEXT_MESSAGES,
+ EMAIL_TEXT_MESSAGE_OTHER,
+ })
+ public @interface EmailTextMessageType {}
+
+ public static final String EMAIL_TEXT_MESSAGE_EMAILS = "EMAILS";
+ public static final String EMAIL_TEXT_MESSAGE_TEXT_MESSAGES = "TEXT_MESSAGES";
+ public static final String EMAIL_TEXT_MESSAGE_OTHER = "OTHER";
+
+ @EmailTextMessageType
+ private static final Set<String> VALID_TYPES_EMAIL_TEXT_MESSAGE =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ EMAIL_TEXT_MESSAGE_EMAILS,
+ EMAIL_TEXT_MESSAGE_TEXT_MESSAGES,
+ EMAIL_TEXT_MESSAGE_OTHER)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_PHOTO_VIDEO}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "PHOTO_VIDEO_",
+ value = {
+ PHOTO_VIDEO_PHOTOS,
+ PHOTO_VIDEO_VIDEOS,
+ })
+ public @interface PhotoVideoType {}
+
+ public static final String PHOTO_VIDEO_PHOTOS = "PHOTOS";
+ public static final String PHOTO_VIDEO_VIDEOS = "VIDEOS";
+
+ @PhotoVideoType
+ private static final Set<String> VALID_TYPES_PHOTO_VIDEO =
+ Collections.unmodifiableSet(
+ new HashSet<>(Arrays.asList(PHOTO_VIDEO_PHOTOS, PHOTO_VIDEO_VIDEOS)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_AUDIO}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "AUDIO_",
+ value = {AUDIO_SOUND_RECORDINGS, AUDIO_MUSIC_FILES, AUDIO_OTHER})
+ public @interface AudioType {}
+
+ public static final String AUDIO_SOUND_RECORDINGS = "SOUND_RECORDINGS";
+ public static final String AUDIO_MUSIC_FILES = "MUSIC_FILES";
+ public static final String AUDIO_OTHER = "OTHER";
+
+ @AudioType
+ private static final Set<String> VALID_TYPES_AUDIO =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(AUDIO_SOUND_RECORDINGS, AUDIO_MUSIC_FILES, AUDIO_OTHER)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_STORAGE}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "STORAGE_",
+ value = {
+ STORAGE_FILES_DOCS,
+ })
+ public @interface StorageType {}
+
+ public static final String STORAGE_FILES_DOCS = "FILES_DOCS";
+
+ @StorageType
+ private static final Set<String> VALID_TYPES_STORAGE =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(STORAGE_FILES_DOCS)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_HEALTH_FITNESS}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "HEALTH_FITNESS_",
+ value = {
+ HEALTH_FITNESS_HEALTH,
+ HEALTH_FITNESS_FITNESS,
+ })
+ public @interface HealthFitnessType {}
+
+ public static final String HEALTH_FITNESS_HEALTH = "HEALTH";
+ public static final String HEALTH_FITNESS_FITNESS = "FITNESS";
+
+ @HealthFitnessType
+ private static final Set<String> VALID_TYPES_HEALTH_FITNESS =
+ Collections.unmodifiableSet(
+ new HashSet<>(Arrays.asList(HEALTH_FITNESS_HEALTH, HEALTH_FITNESS_FITNESS)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_CONTACTS}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "CONTACTS_",
+ value = {
+ CONTACTS_CONTACTS,
+ })
+ public @interface ContactsType {}
+
+ public static final String CONTACTS_CONTACTS = "CONTACTS";
+
+ @ContactsType
+ private static final Set<String> VALID_TYPES_CONTACTS =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(CONTACTS_CONTACTS)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_CALENDAR}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "CALENDAR_",
+ value = {
+ CALENDAR_CALENDAR,
+ })
+ public @interface CalendarType {}
+
+ public static final String CALENDAR_CALENDAR = "CALENDAR";
+
+ @CalendarType
+ private static final Set<String> VALID_TYPES_CALENDAR =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(CALENDAR_CALENDAR)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_IDENTIFIERS}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "IDENTIFIERS_",
+ value = {
+ IDENTIFIERS_OTHER,
+ })
+ public @interface IdentifiersType {}
+
+ public static final String IDENTIFIERS_OTHER = "OTHER";
+
+ @IdentifiersType
+ private static final Set<String> VALID_TYPES_IDENTIFIERS =
+ Collections.unmodifiableSet(new HashSet<>(Arrays.asList(IDENTIFIERS_OTHER)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_APP_PERFORMANCE}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "APP_PERFORMANCE_",
+ value = {
+ APP_PERFORMANCE_CRASH_LOGS,
+ APP_PERFORMANCE_PERFORMANCE_DIAGNOSTICS,
+ APP_PERFORMANCE_OTHER,
+ })
+ public @interface AppPerformanceType {}
+
+ public static final String APP_PERFORMANCE_CRASH_LOGS = "CRASH_LOGS";
+ public static final String APP_PERFORMANCE_PERFORMANCE_DIAGNOSTICS = "PERFORMANCE_DIAGNOSTICS";
+ public static final String APP_PERFORMANCE_OTHER = "OTHER";
+
+ @AppPerformanceType
+ private static final Set<String> VALID_TYPES_APP_PERFORMANCE =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ APP_PERFORMANCE_CRASH_LOGS,
+ APP_PERFORMANCE_PERFORMANCE_DIAGNOSTICS,
+ APP_PERFORMANCE_OTHER)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_ACTIONS_IN_APP}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "ACTIONS_IN_APP_",
+ value = {
+ ACTIONS_IN_APP_USER_INTERACTION,
+ ACTIONS_IN_APP_IN_APP_SEARCH_HISTORY,
+ ACTIONS_IN_APP_INSTALLED_APPS,
+ ACTIONS_IN_APP_USER_GENERATED_CONTENT,
+ ACTIONS_IN_APP_OTHER,
+ })
+ public @interface ActionsInAppType {}
+
+ public static final String ACTIONS_IN_APP_USER_INTERACTION = "USER_INTERACTION";
+ public static final String ACTIONS_IN_APP_IN_APP_SEARCH_HISTORY = "IN_APP_SEARCH_HISTORY";
+ public static final String ACTIONS_IN_APP_INSTALLED_APPS = "INSTALLED_APPS";
+ public static final String ACTIONS_IN_APP_USER_GENERATED_CONTENT = "USER_GENERATED_CONTENT";
+ public static final String ACTIONS_IN_APP_OTHER = "OTHER";
+
+ @ActionsInAppType
+ private static final Set<String> VALID_TYPES_ACTIONS_IN_APP =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ ACTIONS_IN_APP_USER_INTERACTION,
+ ACTIONS_IN_APP_IN_APP_SEARCH_HISTORY,
+ ACTIONS_IN_APP_INSTALLED_APPS,
+ ACTIONS_IN_APP_USER_GENERATED_CONTENT,
+ ACTIONS_IN_APP_OTHER)));
+
+ /**
+ * List of valid Safety Label data collection/sharing types for {@link
+ * DataCategoryConstants#CATEGORY_SEARCH_AND_BROWSING}
+ */
+ @Retention(SOURCE)
+ @StringDef(
+ prefix = "SEARCH_AND_BROWSING_",
+ value = {
+ SEARCH_AND_BROWSING_WEB_BROWSING_HISTORY,
+ })
+ public @interface SearchAndBrowsingType {}
+
+ public static final String SEARCH_AND_BROWSING_WEB_BROWSING_HISTORY = "WEB_BROWSING_HISTORY";
+
+ @SearchAndBrowsingType
+ private static final Set<String> VALID_TYPES_SEARCH_AND_BROWSING =
+ Collections.unmodifiableSet(
+ new HashSet<>(Arrays.asList(SEARCH_AND_BROWSING_WEB_BROWSING_HISTORY)));
+
+ private static final Map<String, Set<String>> VALID_TYPES_FOR_CATEGORY_MAP;
+
+ /** Returns {@link Set} of valid types for the specified {@link String} category key */
+ public static Set<String> getValidDataTypesForCategory(
+ @DataCategoryConstants.Category String category) {
+ return VALID_TYPES_FOR_CATEGORY_MAP.containsKey(category)
+ ? VALID_TYPES_FOR_CATEGORY_MAP.get(category)
+ : Collections.emptySet();
+ }
+
+ static {
+ VALID_TYPES_FOR_CATEGORY_MAP = new ArrayMap<>();
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_PERSONAL, VALID_TYPES_PERSONAL);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_FINANCIAL, VALID_TYPES_FINANCIAL);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_LOCATION, VALID_TYPES_LOCATION);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_EMAIL_TEXT_MESSAGE, VALID_TYPES_EMAIL_TEXT_MESSAGE);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_PHOTO_VIDEO, VALID_TYPES_PHOTO_VIDEO);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(DataCategoryConstants.CATEGORY_AUDIO, VALID_TYPES_AUDIO);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_STORAGE, VALID_TYPES_STORAGE);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_HEALTH_FITNESS, VALID_TYPES_HEALTH_FITNESS);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_CONTACTS, VALID_TYPES_CONTACTS);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_CALENDAR, VALID_TYPES_CALENDAR);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_IDENTIFIERS, VALID_TYPES_IDENTIFIERS);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_APP_PERFORMANCE, VALID_TYPES_APP_PERFORMANCE);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_ACTIONS_IN_APP, VALID_TYPES_ACTIONS_IN_APP);
+ VALID_TYPES_FOR_CATEGORY_MAP.put(
+ DataCategoryConstants.CATEGORY_SEARCH_AND_BROWSING,
+ VALID_TYPES_SEARCH_AND_BROWSING);
+ }
+
+ private DataTypeConstants() {
+ /* do nothing - hide constructor */
+ }
+}
diff --git a/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java b/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java
new file mode 100644
index 000000000..822e9c4fe
--- /dev/null
+++ b/SafetyLabel/java/com/android/permission/safetylabel/SafetyLabel.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel;
+
+import android.os.PersistableBundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/** Safety Label representation containing zero or more {@link DataCategory} for data shared */
+public class SafetyLabel {
+ @VisibleForTesting static final String KEY_SAFETY_LABEL = "safety_labels";
+ private final DataLabel mDataLabel;
+
+ private SafetyLabel(@NonNull DataLabel dataLabel) {
+ this.mDataLabel = dataLabel;
+ }
+
+ /** Returns {@link SafetyLabel} created by parsing a metadata {@link PersistableBundle} */
+ @Nullable
+ public static SafetyLabel getSafetyLabelFromMetadata(@Nullable PersistableBundle bundle) {
+ // TODO(b/261069412): add versioning and nonnull empty/invalid cases
+ if (bundle == null) {
+ return null;
+ }
+
+ PersistableBundle safetyLabelBundle = bundle.getPersistableBundle(KEY_SAFETY_LABEL);
+ if (safetyLabelBundle == null) {
+ return null;
+ }
+
+ DataLabel dataLabel = DataLabel.getDataLabel(safetyLabelBundle);
+ if (dataLabel == null) {
+ return null;
+ }
+
+ return new SafetyLabel(dataLabel);
+ }
+
+ /** Returns the data label for the safety label */
+ @NonNull
+ public DataLabel getDataLabel() {
+ return mDataLabel;
+ }
+}
diff --git a/SafetyLabel/tests/Android.bp b/SafetyLabel/tests/Android.bp
new file mode 100644
index 000000000..2026a6ac8
--- /dev/null
+++ b/SafetyLabel/tests/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "SafetyLabelTests",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+ target_sdk_version: "32",
+ srcs: [
+ "java/**/*.kt",
+ ],
+ per_testcase_directory: true,
+ static_libs: [
+ "compatibility-device-util-axt",
+ "kotlinx-coroutines-android",
+ "safety-label",
+ ],
+ test_suites: [
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/SafetyLabel/tests/AndroidManifest.xml b/SafetyLabel/tests/AndroidManifest.xml
new file mode 100644
index 000000000..4d1e62905
--- /dev/null
+++ b/SafetyLabel/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.permission.safetylabel.tests">
+
+ <application android:label="Safety Label Tests">
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.permission.safetylabel.tests"
+ android:label="Tests for the Safety Label library"/>
+</manifest>
diff --git a/SafetyLabel/tests/AndroidTest.xml b/SafetyLabel/tests/AndroidTest.xml
new file mode 100644
index 000000000..919e7ce2a
--- /dev/null
+++ b/SafetyLabel/tests/AndroidTest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<configuration description="Runs unit tests for the Safety Label library.">
+ <option name="test-tag" value="SafetyLabelTests"/>
+ <object type="module_controller"
+ class="com.android.tradefed.testtype.suite.module.Sdk33ModuleController"/>
+
+ <!-- Install test -->
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="SafetyLabelTests.apk"/>
+ <option name="cleanup-apks" value="true"/>
+ </target_preparer>
+
+ <!-- Create place to store apks -->
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/safetylabel/tests/"/>
+ <option name="teardown-command" value="rm -rf /data/local/tmp/safetylabel/"/>
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="com.android.permission.safetylabel.tests"/>
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner"/>
+ </test>
+</configuration>
diff --git a/SafetyLabel/tests/java/com/android/permission/safetylabel/DataCategoryTest.kt b/SafetyLabel/tests/java/com/android/permission/safetylabel/DataCategoryTest.kt
new file mode 100644
index 000000000..8ed705064
--- /dev/null
+++ b/SafetyLabel/tests/java/com/android/permission/safetylabel/DataCategoryTest.kt
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel
+
+import android.os.PersistableBundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.permission.safetylabel.DataCategoryConstants.CATEGORY_LOCATION
+import com.android.permission.safetylabel.DataCategoryConstants.VALID_CATEGORIES
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.INVALID_KEY
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createCategoryMapPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createCategoryMapPersistableBundleWithInvalidTypes
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createDataLabelPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createDataLabelPersistableBundleWithAdditonalInvalidCategory
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createInvalidCategoryMapPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createInvalidDataLabelPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createTypePersistableBundle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for [DataCategory]. */
+@RunWith(AndroidJUnit4::class)
+class DataCategoryTest {
+ @Test
+ fun getDataCategoryMap_dataUsageCollected_nullPersistableBundle_emptyMap() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(null, DataLabelConstants.DATA_USAGE_COLLECTED)
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageCollected_emptyPersistableBundle_emptyMap() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(
+ PersistableBundle.EMPTY, DataLabelConstants.DATA_USAGE_COLLECTED)
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageCollected_invalidBundle_emptyMap() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(
+ createInvalidDataLabelPersistableBundle(), DataLabelConstants.DATA_USAGE_COLLECTED)
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageCollected_validBundle_hasAllValidCategories() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(
+ createDataLabelPersistableBundle(), DataLabelConstants.DATA_USAGE_COLLECTED)
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap.keys).containsExactlyElementsIn(VALID_CATEGORIES)
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageCollected_additionalInvalidCategory_hasOnlyValidCategories() {
+ // Create valid data label and put an additional invalid/unknown category key
+ val dataLabelBundle = createDataLabelPersistableBundleWithAdditonalInvalidCategory()
+
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(
+ dataLabelBundle, DataLabelConstants.DATA_USAGE_COLLECTED)
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap.keys).containsExactlyElementsIn(VALID_CATEGORIES)
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageShared_nullPersistableBundle_emptyMap() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(null, DataLabelConstants.DATA_USAGE_SHARED)
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageShared_emptyPersistableBundle_emptyMap() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(
+ PersistableBundle.EMPTY, DataLabelConstants.DATA_USAGE_SHARED)
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageShared_invalidBundle_emptyMap() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(
+ createInvalidDataLabelPersistableBundle(), DataLabelConstants.DATA_USAGE_SHARED)
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageShared_validBundle_hasAllValidCategories() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(
+ createDataLabelPersistableBundle(), DataLabelConstants.DATA_USAGE_SHARED)
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap.keys).containsExactlyElementsIn(VALID_CATEGORIES)
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageShared_additionalInvalidCategory_hasOnlyValidCategories() {
+ // Create valid data label and put an additional invalid/unknown category key
+ val dataLabelBundle = createDataLabelPersistableBundleWithAdditonalInvalidCategory()
+
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(dataLabelBundle, DataLabelConstants.DATA_USAGE_SHARED)
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap.keys).containsExactlyElementsIn(VALID_CATEGORIES)
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageInvalid_nullPersistableBundle_emptyMap() {
+ val dataCategoryMap = DataCategory.getDataCategoryMap(null, "invalid_datausage_key")
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageInvalid_emptyPersistableBundle_emptyMap() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(PersistableBundle.EMPTY, "invalid_datausage_key")
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageInvalid_invalidBundle_emptyMap() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(
+ createInvalidDataLabelPersistableBundle(), "invalid_datausage_key")
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageInvalid_validBundle_emptyMap() {
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(
+ createDataLabelPersistableBundle(), "invalid_datausage_key")
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategoryMap_dataUsageInvalid_validBundleWithAdditionalInvalidCategory_null() {
+ // Create valid data label and put an additional invalid/unknown category key
+ val dataLabelBundle = createDataLabelPersistableBundleWithAdditonalInvalidCategory()
+
+ val dataCategoryMap =
+ DataCategory.getDataCategoryMap(dataLabelBundle, "invalid_datausage_key")
+
+ assertThat(dataCategoryMap).isNotNull()
+ assertThat(dataCategoryMap).isEmpty()
+ }
+
+ @Test
+ fun getDataCategory_nullBundle_nullDataCategory() {
+ val dataCategory =
+ DataCategory.getDataCategory(
+ null, DataLabelConstants.DATA_USAGE_SHARED, CATEGORY_LOCATION)
+
+ assertThat(dataCategory).isNull()
+ }
+
+ @Test
+ fun getDataCategory_emptyBundle_nullDataCategory() {
+ val dataCategory =
+ DataCategory.getDataCategory(
+ PersistableBundle.EMPTY, DataLabelConstants.DATA_USAGE_SHARED, CATEGORY_LOCATION)
+
+ assertThat(dataCategory).isNull()
+ }
+
+ @Test
+ fun getDataCategory_invalidBundle_nullDataCategory() {
+ val dataCategory =
+ DataCategory.getDataCategory(
+ createInvalidCategoryMapPersistableBundle(),
+ DataLabelConstants.DATA_USAGE_SHARED,
+ CATEGORY_LOCATION)
+
+ assertThat(dataCategory).isNull()
+ }
+
+ @Test
+ fun getDataCategory_validCategoriesAndInvalidType_nullDataCategory() {
+ val dataCategory =
+ DataCategory.getDataCategory(
+ createCategoryMapPersistableBundleWithInvalidTypes(),
+ DataLabelConstants.DATA_USAGE_SHARED,
+ INVALID_KEY)
+
+ assertThat(dataCategory).isNull()
+ }
+
+ @Test
+ fun getDataCategory_validBundle_validCategoryAndExpectedTypes() {
+ val dataCategory =
+ DataCategory.getDataCategory(
+ createCategoryMapPersistableBundle(),
+ DataLabelConstants.DATA_USAGE_SHARED,
+ CATEGORY_LOCATION)
+
+ assertThat(dataCategory).isNotNull()
+ assertThat(dataCategory?.dataTypes).isNotEmpty()
+ assertThat(dataCategory?.dataTypes?.keys)
+ .containsExactlyElementsIn(
+ DataTypeConstants.getValidDataTypesForCategory(CATEGORY_LOCATION))
+ }
+
+ @Test
+ fun getDataCategory_validBundleWithAddedInvalidType_validCategoryAndOnlyExpectedTypes() {
+ // Create valid bundle with additional invalid type
+ val dataTypeMapBundle = createCategoryMapPersistableBundle()
+ dataTypeMapBundle.putPersistableBundle(INVALID_KEY, createTypePersistableBundle())
+
+ val dataCategory =
+ DataCategory.getDataCategory(
+ dataTypeMapBundle, DataLabelConstants.DATA_USAGE_SHARED, CATEGORY_LOCATION)
+
+ assertThat(dataCategory).isNotNull()
+ assertThat(dataCategory?.dataTypes).isNotEmpty()
+ assertThat(dataCategory?.dataTypes?.keys)
+ .containsExactlyElementsIn(
+ DataTypeConstants.getValidDataTypesForCategory(CATEGORY_LOCATION))
+ }
+}
diff --git a/SafetyLabel/tests/java/com/android/permission/safetylabel/DataLabelTest.kt b/SafetyLabel/tests/java/com/android/permission/safetylabel/DataLabelTest.kt
new file mode 100644
index 000000000..1c0c56238
--- /dev/null
+++ b/SafetyLabel/tests/java/com/android/permission/safetylabel/DataLabelTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel
+
+import android.os.PersistableBundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createInvalidSafetyLabelPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createSafetyLabelPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createSafetyLabelPersistableBundleWithEmptyDataCollected
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createSafetyLabelPersistableBundleWithEmptyDataShared
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createSafetyLabelPersistableBundleWithInvalidDataCollected
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createSafetyLabelPersistableBundleWithInvalidDataShared
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createSafetyLabelPersistableBundleWithNullDataCollected
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createSafetyLabelPersistableBundleWithNullDataShared
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for [DataLabel]. */
+@RunWith(AndroidJUnit4::class)
+class DataLabelTest {
+ @Test
+ fun getDataLabel_nullBundle_nullDataLabel() {
+ val dataLabel: DataLabel? = DataLabel.getDataLabel(null)
+
+ assertThat(dataLabel).isNull()
+ }
+
+ @Test
+ fun getDataLabel_emptyBundle_nullDataLabel() {
+ val dataLabel: DataLabel? = DataLabel.getDataLabel(PersistableBundle.EMPTY)
+
+ assertThat(dataLabel).isNull()
+ }
+
+ @Test
+ fun getDataLabel_invalidBundle_nullDataLabel() {
+ val dataLabel: DataLabel? =
+ DataLabel.getDataLabel(createInvalidSafetyLabelPersistableBundle())
+
+ assertThat(dataLabel).isNull()
+ }
+
+ @Test
+ fun getDataLabel_nullDataCollectedBundle_dataCollectedEmpty() {
+ val dataLabel: DataLabel? =
+ DataLabel.getDataLabel(createSafetyLabelPersistableBundleWithNullDataCollected())
+
+ assertThat(dataLabel).isNotNull()
+ assertThat(dataLabel?.dataCollected).isEmpty()
+ assertThat(dataLabel?.dataShared).isNotEmpty()
+ }
+
+ @Test
+ fun getDataLabel_nullDataSharedBundle_dataSharedEmpty() {
+ val dataLabel: DataLabel? =
+ DataLabel.getDataLabel(createSafetyLabelPersistableBundleWithNullDataShared())
+
+ assertThat(dataLabel).isNotNull()
+ assertThat(dataLabel?.dataCollected).isNotEmpty()
+ assertThat(dataLabel?.dataShared).isEmpty()
+ }
+
+ @Test
+ fun getDataLabel_emptyDataCollectedBundle_dataCollectedEmpty() {
+ val dataLabel: DataLabel? =
+ DataLabel.getDataLabel(createSafetyLabelPersistableBundleWithEmptyDataCollected())
+
+ assertThat(dataLabel).isNotNull()
+ assertThat(dataLabel?.dataCollected).isEmpty()
+ assertThat(dataLabel?.dataShared).isNotEmpty()
+ }
+
+ @Test
+ fun getDataLabel_emptyDataSharedBundle_dataSharedEmpty() {
+ val dataLabel: DataLabel? =
+ DataLabel.getDataLabel(createSafetyLabelPersistableBundleWithEmptyDataShared())
+
+ assertThat(dataLabel).isNotNull()
+ assertThat(dataLabel?.dataCollected).isNotEmpty()
+ assertThat(dataLabel?.dataShared).isEmpty()
+ }
+
+ @Test
+ fun getDataLabel_invalidDataCollectedBundle_dataCollectedEmpty() {
+ val dataLabel: DataLabel? =
+ DataLabel.getDataLabel(createSafetyLabelPersistableBundleWithInvalidDataCollected())
+
+ assertThat(dataLabel).isNotNull()
+ assertThat(dataLabel?.dataCollected).isEmpty()
+ assertThat(dataLabel?.dataShared).isNotEmpty()
+ }
+
+ @Test
+ fun getDataLabel_invalidDataSharedBundle_dataSharedEmpty() {
+ val dataLabel: DataLabel? =
+ DataLabel.getDataLabel(createSafetyLabelPersistableBundleWithInvalidDataShared())
+
+ assertThat(dataLabel).isNotNull()
+ assertThat(dataLabel?.dataCollected).isNotEmpty()
+ assertThat(dataLabel?.dataShared).isEmpty()
+ }
+
+ @Test
+ fun getDataLabel_validBundle() {
+ val dataLabel: DataLabel? = DataLabel.getDataLabel(createSafetyLabelPersistableBundle())
+
+ assertThat(dataLabel).isNotNull()
+ assertThat(dataLabel?.dataCollected).isNotEmpty()
+ assertThat(dataLabel?.dataShared).isNotEmpty()
+ }
+}
diff --git a/SafetyLabel/tests/java/com/android/permission/safetylabel/DataTypeTest.kt b/SafetyLabel/tests/java/com/android/permission/safetylabel/DataTypeTest.kt
new file mode 100644
index 000000000..bfc9be726
--- /dev/null
+++ b/SafetyLabel/tests/java/com/android/permission/safetylabel/DataTypeTest.kt
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel
+
+import android.os.PersistableBundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.permission.safetylabel.DataCategoryConstants.CATEGORY_LOCATION
+import com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_ADVERTISING
+import com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_APP_FUNCTIONALITY
+import com.android.permission.safetylabel.DataType.KEY_EPHEMERAL
+import com.android.permission.safetylabel.DataType.KEY_PURPOSES
+import com.android.permission.safetylabel.DataType.KEY_USER_CONTROL
+import com.android.permission.safetylabel.DataTypeConstants.LOCATION_APPROX_LOCATION
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.INVALID_KEY
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createInvalidTypeMapPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createInvalidTypePersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createTypeMapPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createTypeMapWithInvalidTypeDataPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createTypePersistableBundle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for [DataType]. */
+@RunWith(AndroidJUnit4::class)
+class DataTypeTest {
+ @Test
+ fun getDataTypeMap_invalidCategory_nullPersistableBundle_emptyMap() {
+ val dataTypeMap =
+ DataType.getDataTypeMap(null, DataLabelConstants.DATA_USAGE_SHARED, INVALID_KEY)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap).isEmpty()
+ }
+
+ @Test
+ fun getDataTypeMap_invalidCategory_emptyPersistableBundle_emptyMap() {
+ val dataTypeMap =
+ DataType.getDataTypeMap(
+ PersistableBundle.EMPTY, DataLabelConstants.DATA_USAGE_SHARED, INVALID_KEY)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap).isEmpty()
+ }
+
+ @Test
+ fun getDataTypeMap_invalidCategory_invalidBundle_emptyMap() {
+ val dataTypeMap =
+ DataType.getDataTypeMap(
+ createInvalidTypeMapPersistableBundle(),
+ DataLabelConstants.DATA_USAGE_SHARED,
+ INVALID_KEY)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap).isEmpty()
+ }
+
+ @Test
+ fun getDataTypeMap_invalidCategory_validBundle_emptyMap() {
+ val dataTypeMap =
+ DataType.getDataTypeMap(
+ createTypeMapPersistableBundle(CATEGORY_LOCATION),
+ DataLabelConstants.DATA_USAGE_SHARED,
+ INVALID_KEY)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap).isEmpty()
+ }
+
+ @Test
+ fun getDataTypeMap_validCategory_nullPersistableBundle_emptyMap() {
+ val dataTypeMap =
+ DataType.getDataTypeMap(null, DataLabelConstants.DATA_USAGE_SHARED, CATEGORY_LOCATION)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap).isEmpty()
+ }
+
+ @Test
+ fun getDataTypeMap_validCategory_emptyPersistableBundle_emptyMap() {
+ val dataTypeMap =
+ DataType.getDataTypeMap(
+ PersistableBundle.EMPTY, DataLabelConstants.DATA_USAGE_SHARED, CATEGORY_LOCATION)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap).isEmpty()
+ }
+
+ @Test
+ fun getDataTypeMap_validCategory_invalidBundle_emptyMap() {
+ val dataTypeMap =
+ DataType.getDataTypeMap(
+ createInvalidTypeMapPersistableBundle(),
+ DataLabelConstants.DATA_USAGE_SHARED,
+ CATEGORY_LOCATION)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap).isEmpty()
+ }
+
+ @Test
+ fun getDataTypeMap_validCategory_validBundle_hasAllExpectedTypes() {
+ val typeMapPersistableBundle = createTypeMapPersistableBundle(CATEGORY_LOCATION)
+
+ val dataTypeMap =
+ DataType.getDataTypeMap(
+ typeMapPersistableBundle, DataLabelConstants.DATA_USAGE_SHARED, CATEGORY_LOCATION)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap.keys)
+ .containsExactlyElementsIn(
+ DataTypeConstants.getValidDataTypesForCategory(CATEGORY_LOCATION))
+ }
+
+ @Test
+ fun getDataTypeMap_validCategory_validBundleWithAddedInvalidType_hasOnlyExpectedTypes() {
+ val typeMapPersistableBundle = createTypeMapPersistableBundle(CATEGORY_LOCATION)
+ // Add additional valid persistable bundle under invalid key
+ typeMapPersistableBundle.putPersistableBundle(INVALID_KEY, createTypePersistableBundle())
+
+ val dataTypeMap =
+ DataType.getDataTypeMap(
+ typeMapPersistableBundle, DataLabelConstants.DATA_USAGE_SHARED, CATEGORY_LOCATION)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap.keys)
+ .containsExactlyElementsIn(
+ DataTypeConstants.getValidDataTypesForCategory(CATEGORY_LOCATION))
+ assertThat(dataTypeMap.keys).doesNotContain(INVALID_KEY)
+ }
+
+ @Test
+ fun getDataTypeMap_validCategory_validType_invalidData_emptyMap() {
+ val typeMapPersistableBundle =
+ createTypeMapWithInvalidTypeDataPersistableBundle(CATEGORY_LOCATION)
+
+ val dataTypeMap =
+ DataType.getDataTypeMap(
+ typeMapPersistableBundle, DataLabelConstants.DATA_USAGE_SHARED, CATEGORY_LOCATION)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap).isEmpty()
+ }
+
+ @Test
+ fun getDataTypeMap_dataCollected_validCategory_validBundle_validateSingleExpectedType() {
+ val dataTypeMap =
+ DataType.getDataTypeMap(
+ createTypeMapPersistableBundle(CATEGORY_LOCATION),
+ DataLabelConstants.DATA_USAGE_COLLECTED,
+ CATEGORY_LOCATION)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap).containsKey(LOCATION_APPROX_LOCATION)
+ val type = dataTypeMap[LOCATION_APPROX_LOCATION]!!
+ assertThat(type.purposeSet).containsExactly(PURPOSE_APP_FUNCTIONALITY, PURPOSE_ADVERTISING)
+ assertThat(type.userControl).isTrue()
+ assertThat(type.ephemeral).isTrue()
+ }
+
+ @Test
+ fun getDataTypeMap_dataShared_validCategory_validBundle_validateSingleExpectedType() {
+ val dataTypeMap =
+ DataType.getDataTypeMap(
+ createTypeMapPersistableBundle(CATEGORY_LOCATION),
+ DataLabelConstants.DATA_USAGE_SHARED,
+ CATEGORY_LOCATION)
+
+ assertThat(dataTypeMap).isNotNull()
+ assertThat(dataTypeMap).containsKey(LOCATION_APPROX_LOCATION)
+ val type = dataTypeMap[LOCATION_APPROX_LOCATION]!!
+ assertThat(type.purposeSet).containsExactly(PURPOSE_APP_FUNCTIONALITY, PURPOSE_ADVERTISING)
+ assertThat(type.userControl).isNull()
+ assertThat(type.ephemeral).isNull()
+ }
+
+ @Test
+ fun getDataType_invalidBundle_nullDataType() {
+ val dataType =
+ DataType.getDataType(
+ createInvalidTypePersistableBundle(), DataLabelConstants.DATA_USAGE_SHARED)
+
+ assertThat(dataType).isNull()
+ }
+
+ @Test
+ fun getDataType_dataCollected_validDataType() {
+ val dataType =
+ DataType.getDataType(
+ createTypePersistableBundle(), DataLabelConstants.DATA_USAGE_COLLECTED)
+
+ assertThat(dataType).isNotNull()
+ assertThat(dataType?.purposeSet)
+ .containsExactly(PURPOSE_APP_FUNCTIONALITY, PURPOSE_ADVERTISING)
+ assertThat(dataType?.userControl).isTrue()
+ assertThat(dataType?.ephemeral).isTrue()
+ }
+
+ @Test
+ fun getDataType_dataShared_validDataType() {
+ val dataType =
+ DataType.getDataType(
+ createTypePersistableBundle(), DataLabelConstants.DATA_USAGE_SHARED)
+
+ assertThat(dataType).isNotNull()
+ assertThat(dataType?.purposeSet)
+ .containsExactly(PURPOSE_APP_FUNCTIONALITY, PURPOSE_ADVERTISING)
+ assertThat(dataType?.userControl).isNull()
+ assertThat(dataType?.ephemeral).isNull()
+ }
+
+ @Test
+ fun getDataType_validDataTypeWithAddedInvalidPurpose_onlyValidPurposes() {
+ val typePersistableBundle = createTypePersistableBundle()
+ val purposes: IntArray = typePersistableBundle.getIntArray(KEY_PURPOSES)!!
+ val updatedPurposes: IntArray = intArrayOf(-1, *purposes)
+ typePersistableBundle.putIntArray(KEY_PURPOSES, updatedPurposes)
+
+ val dataType =
+ DataType.getDataType(typePersistableBundle, DataLabelConstants.DATA_USAGE_SHARED)
+
+ assertThat(dataType).isNotNull()
+ // Should not contain the additional "-1" purpose added above
+ assertThat(dataType?.purposeSet)
+ .containsExactly(PURPOSE_APP_FUNCTIONALITY, PURPOSE_ADVERTISING)
+ }
+
+ @Test
+ fun getDataType_dataTypeWithInvalidPurpose_nullDataType() {
+ val typePersistableBundle = createTypePersistableBundle()
+ typePersistableBundle.remove(KEY_PURPOSES)
+ val updatedPurposes: IntArray = intArrayOf(-1)
+ typePersistableBundle.putIntArray(KEY_PURPOSES, updatedPurposes)
+
+ val dataType =
+ DataType.getDataType(typePersistableBundle, DataLabelConstants.DATA_USAGE_SHARED)
+
+ assertThat(dataType).isNull()
+ }
+
+ @Test
+ fun getDataType_noPurpose_nullDataType() {
+ val typePersistableBundle = createTypePersistableBundle()
+ typePersistableBundle.remove(KEY_PURPOSES)
+
+ val dataType =
+ DataType.getDataType(typePersistableBundle, DataLabelConstants.DATA_USAGE_SHARED)
+
+ assertThat(dataType).isNull()
+ }
+
+ @Test
+ fun getDataType_dataCollected_validDataType_noUserControl_noEphemeral() {
+ val bundle = createTypePersistableBundle()
+ bundle.remove(KEY_USER_CONTROL)
+ bundle.remove(KEY_EPHEMERAL)
+
+ val dataType = DataType.getDataType(bundle, DataLabelConstants.DATA_USAGE_COLLECTED)
+
+ assertThat(dataType).isNotNull()
+ assertThat(dataType?.purposeSet)
+ .containsExactly(PURPOSE_APP_FUNCTIONALITY, PURPOSE_ADVERTISING)
+ assertThat(dataType?.userControl).isNull()
+ assertThat(dataType?.ephemeral).isNull()
+ }
+}
diff --git a/SafetyLabel/tests/java/com/android/permission/safetylabel/SafetyLabelTest.kt b/SafetyLabel/tests/java/com/android/permission/safetylabel/SafetyLabelTest.kt
new file mode 100644
index 000000000..ce9f24634
--- /dev/null
+++ b/SafetyLabel/tests/java/com/android/permission/safetylabel/SafetyLabelTest.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel
+
+import android.os.PersistableBundle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createInvalidMetadataPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createMetadataPersistableBundle
+import com.android.permission.safetylabel.SafetyLabelTestPersistableBundles.createMetadataPersistableBundleWithInvalidSafetyLabel
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for [SafetyLabel]. */
+@RunWith(AndroidJUnit4::class)
+class SafetyLabelTest {
+
+ @Test
+ fun getSafetyLabelFromMetaData_nullMetadataBundle_nullSafetyLabel() {
+ val safetyLabel = SafetyLabel.getSafetyLabelFromMetadata(null)
+
+ assertThat(safetyLabel).isNull()
+ }
+
+ @Test
+ fun getSafetyLabelFromMetaData_emptyMetadataBundle_nullSafetyLabel() {
+ val safetyLabel = SafetyLabel.getSafetyLabelFromMetadata(PersistableBundle.EMPTY)
+
+ assertThat(safetyLabel).isNull()
+ }
+
+ @Test
+ fun getSafetyLabelFromMetaData_invalidMetadataBundle_nullSafetyLabel() {
+ val safetyLabel =
+ SafetyLabel.getSafetyLabelFromMetadata(createInvalidMetadataPersistableBundle())
+
+ assertThat(safetyLabel).isNull()
+ }
+
+ @Test
+ fun getSafetyLabelFromMetaData_invalidSafetyLabelBundle_dataSharedEmpty() {
+ val safetyLabel =
+ SafetyLabel.getSafetyLabelFromMetadata(
+ createMetadataPersistableBundleWithInvalidSafetyLabel())
+
+ assertThat(safetyLabel).isNull()
+ }
+
+ @Test
+ fun getSafetyLabelFromMetaData_validBundle_hasDataShared() {
+ val safetyLabel = SafetyLabel.getSafetyLabelFromMetadata(createMetadataPersistableBundle())
+
+ assertThat(safetyLabel).isNotNull()
+ assertThat(safetyLabel?.dataLabel).isNotNull()
+ }
+}
diff --git a/SafetyLabel/tests/java/com/android/permission/safetylabel/SafetyLabelTestPersistableBundles.kt b/SafetyLabel/tests/java/com/android/permission/safetylabel/SafetyLabelTestPersistableBundles.kt
new file mode 100644
index 000000000..2d2811633
--- /dev/null
+++ b/SafetyLabel/tests/java/com/android/permission/safetylabel/SafetyLabelTestPersistableBundles.kt
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permission.safetylabel
+
+import android.os.PersistableBundle
+import com.android.permission.safetylabel.DataCategoryConstants.CATEGORY_LOCATION
+import com.android.permission.safetylabel.DataCategoryConstants.Category
+import com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_ADVERTISING
+import com.android.permission.safetylabel.DataPurposeConstants.PURPOSE_APP_FUNCTIONALITY
+import com.android.permission.safetylabel.DataType.KEY_EPHEMERAL
+import com.android.permission.safetylabel.DataType.KEY_PURPOSES
+import com.android.permission.safetylabel.DataType.KEY_USER_CONTROL
+
+/** A class that facilitates creating test safety label persistable bundles. */
+object SafetyLabelTestPersistableBundles {
+ const val INVALID_KEY = "invalid_key"
+
+ fun createMetadataPersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(SafetyLabel.KEY_SAFETY_LABEL, createSafetyLabelPersistableBundle())
+ }
+ }
+
+ fun createInvalidMetadataPersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(INVALID_KEY, createSafetyLabelPersistableBundle())
+ }
+ }
+
+ fun createMetadataPersistableBundleWithInvalidSafetyLabel(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(
+ SafetyLabel.KEY_SAFETY_LABEL, createInvalidSafetyLabelPersistableBundle())
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of a valid safety label */
+ fun createSafetyLabelPersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(DataLabel.KEY_DATA_LABEL, createDataLabelPersistableBundle())
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of an ivnalid safety label */
+ fun createInvalidSafetyLabelPersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(INVALID_KEY, createDataLabelPersistableBundle())
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of an ivnalid safety label */
+ fun createSafetyLabelPersistableBundleWithNullDataCollected(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(
+ DataLabel.KEY_DATA_LABEL, createDataLabelPersistableBundleWithNullDataCollected())
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of an ivnalid safety label */
+ fun createSafetyLabelPersistableBundleWithNullDataShared(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(
+ DataLabel.KEY_DATA_LABEL, createDataLabelPersistableBundleWithNullDataShared())
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of an ivnalid safety label */
+ fun createSafetyLabelPersistableBundleWithEmptyDataCollected(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(
+ DataLabel.KEY_DATA_LABEL, createDataLabelPersistableBundleWithEmptyDataCollected())
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of an ivnalid safety label */
+ fun createSafetyLabelPersistableBundleWithEmptyDataShared(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(
+ DataLabel.KEY_DATA_LABEL, createDataLabelPersistableBundleWithEmptyDataShared())
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of an ivnalid safety label */
+ fun createSafetyLabelPersistableBundleWithInvalidDataCollected(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(
+ DataLabel.KEY_DATA_LABEL,
+ createDataLabelPersistableBundleWithInvalidDataCollected())
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of an ivnalid safety label */
+ fun createSafetyLabelPersistableBundleWithInvalidDataShared(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(
+ DataLabel.KEY_DATA_LABEL, createDataLabelPersistableBundleWithInvalidDataShared())
+ }
+ }
+
+ fun createDataLabelPersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(
+ DataLabelConstants.DATA_USAGE_SHARED, createCategoryMapPersistableBundle())
+ putPersistableBundle(
+ DataLabelConstants.DATA_USAGE_COLLECTED, createCategoryMapPersistableBundle())
+ }
+ }
+
+ fun createInvalidDataLabelPersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(INVALID_KEY, createCategoryMapPersistableBundle())
+ }
+ }
+
+ private fun createDataLabelPersistableBundleWithNullDataCollected(): PersistableBundle {
+ val bundle = createDataLabelPersistableBundle()
+ bundle.remove(DataLabelConstants.DATA_USAGE_COLLECTED)
+ return bundle
+ }
+
+ private fun createDataLabelPersistableBundleWithNullDataShared(): PersistableBundle {
+ val bundle = createDataLabelPersistableBundle()
+ bundle.remove(DataLabelConstants.DATA_USAGE_SHARED)
+ return bundle
+ }
+
+ private fun createDataLabelPersistableBundleWithEmptyDataCollected(): PersistableBundle {
+ val bundle = createDataLabelPersistableBundle()
+ bundle.remove(DataLabelConstants.DATA_USAGE_COLLECTED)
+ bundle.putPersistableBundle(
+ DataLabelConstants.DATA_USAGE_COLLECTED, PersistableBundle.EMPTY)
+ return bundle
+ }
+
+ private fun createDataLabelPersistableBundleWithEmptyDataShared(): PersistableBundle {
+ val bundle = createDataLabelPersistableBundle()
+ bundle.remove(DataLabelConstants.DATA_USAGE_SHARED)
+ bundle.putPersistableBundle(DataLabelConstants.DATA_USAGE_SHARED, PersistableBundle.EMPTY)
+ return bundle
+ }
+
+ private fun createDataLabelPersistableBundleWithInvalidDataCollected(): PersistableBundle {
+ val bundle = createDataLabelPersistableBundle()
+ bundle.remove(DataLabelConstants.DATA_USAGE_COLLECTED)
+ bundle.putPersistableBundle(
+ DataLabelConstants.DATA_USAGE_COLLECTED, createInvalidCategoryMapPersistableBundle())
+ return bundle
+ }
+
+ private fun createDataLabelPersistableBundleWithInvalidDataShared(): PersistableBundle {
+ val bundle = createDataLabelPersistableBundle()
+ bundle.remove(DataLabelConstants.DATA_USAGE_SHARED)
+ bundle.putPersistableBundle(
+ DataLabelConstants.DATA_USAGE_SHARED, createInvalidCategoryMapPersistableBundle())
+ return bundle
+ }
+
+ fun createDataLabelPersistableBundleWithAdditonalInvalidCategory(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(
+ DataLabelConstants.DATA_USAGE_SHARED,
+ createCategoryMapPersistableBundleWithAdditionalInvalidCategory())
+ putPersistableBundle(
+ DataLabelConstants.DATA_USAGE_COLLECTED,
+ createCategoryMapPersistableBundleWithAdditionalInvalidCategory())
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of a [Map] of valid data categories */
+ fun createCategoryMapPersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply {
+ DataCategoryConstants.VALID_CATEGORIES.forEach { categoryKey ->
+ putPersistableBundle(categoryKey, createTypeMapPersistableBundle(categoryKey))
+ }
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of a [Map] of valid data categories */
+ fun createCategoryMapPersistableBundleWithAdditionalInvalidCategory(): PersistableBundle {
+ return PersistableBundle().apply {
+ DataCategoryConstants.VALID_CATEGORIES.forEach { categoryKey ->
+ putPersistableBundle(categoryKey, createTypeMapPersistableBundle(categoryKey))
+ }
+ putPersistableBundle(INVALID_KEY, createTypeMapPersistableBundle(CATEGORY_LOCATION))
+ }
+ }
+
+ /**
+ * Returns [PersistableBundle] representation of a [Map] of valid data categories and invalid
+ * types
+ */
+ fun createCategoryMapPersistableBundleWithInvalidTypes(): PersistableBundle {
+ return PersistableBundle().apply {
+ DataCategoryConstants.VALID_CATEGORIES.forEach { categoryKey ->
+ putPersistableBundle(categoryKey, createInvalidTypeMapPersistableBundle())
+ }
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of a [Map] of invalid data categories */
+ fun createInvalidCategoryMapPersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(INVALID_KEY, createTypeMapPersistableBundle(CATEGORY_LOCATION))
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of a [Map] of valid data type */
+ fun createTypeMapPersistableBundle(@Category category: String): PersistableBundle {
+ return PersistableBundle().apply {
+ DataTypeConstants.getValidDataTypesForCategory(category).forEach { type ->
+ putPersistableBundle(type, createTypePersistableBundle())
+ }
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of a [Map] of invalid data type */
+ fun createInvalidTypeMapPersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply {
+ putPersistableBundle(INVALID_KEY, createTypePersistableBundle())
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of a [Map] of valid type, with invalid data */
+ fun createTypeMapWithInvalidTypeDataPersistableBundle(
+ @Category category: String
+ ): PersistableBundle {
+ return PersistableBundle().apply {
+ DataTypeConstants.getValidDataTypesForCategory(category).forEach { type ->
+ putPersistableBundle(type, createInvalidTypePersistableBundle())
+ }
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of a valid data type */
+ fun createTypePersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply {
+ putIntArray(KEY_PURPOSES, intArrayOf(PURPOSE_APP_FUNCTIONALITY, PURPOSE_ADVERTISING))
+ putBoolean(KEY_USER_CONTROL, true)
+ putBoolean(KEY_EPHEMERAL, true)
+ }
+ }
+
+ /** Returns [PersistableBundle] representation of an invalid data type */
+ fun createInvalidTypePersistableBundle(): PersistableBundle {
+ return PersistableBundle().apply { putLong(INVALID_KEY, 0) }
+ }
+}
diff --git a/framework-s/Android.bp b/framework-s/Android.bp
index ec33f13ee..2199553e8 100644
--- a/framework-s/Android.bp
+++ b/framework-s/Android.bp
@@ -34,8 +34,8 @@ filegroup {
}
filegroup {
- name: "safetycenter-config-schema",
- srcs: ["java/android/safetycenter/config/safety_center_config.xsd"],
+ name: "safetycenter-config-schemas",
+ srcs: ["java/android/safetycenter/config/safety_center_config*.xsd"],
path: "java/android/safetycenter/config/",
visibility: ["//packages/modules/Permission/SafetyCenter/ConfigLintChecker"],
}
@@ -47,6 +47,9 @@ java_library {
"framework-annotations-lib",
"unsupportedappusage",
],
+ static_libs: [
+ "modules-utils-build",
+ ],
apex_available: [
"com.android.permission",
"test_com.android.permission",
@@ -84,12 +87,22 @@ java_sdk_library {
lint: {
strict_updatability_linting: true,
},
- min_sdk_version: "30",
+ min_sdk_version: "31",
permitted_packages: [
"android.permission",
"android.app.role",
"android.safetycenter",
+ "android.safetylabel",
// For com.android.permission.jarjar.
"com.android.permission",
],
}
+
+java_api_contribution {
+ name: "framework-permission-s-public-stubs",
+ api_surface: "public",
+ api_file: "api/current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
diff --git a/framework-s/api/current.txt b/framework-s/api/current.txt
index 4ecc98980..d54af92f5 100644
--- a/framework-s/api/current.txt
+++ b/framework-s/api/current.txt
@@ -12,6 +12,7 @@ package android.app.role {
field public static final String ROLE_DIALER = "android.app.role.DIALER";
field public static final String ROLE_EMERGENCY = "android.app.role.EMERGENCY";
field public static final String ROLE_HOME = "android.app.role.HOME";
+ field public static final String ROLE_NOTES = "android.app.role.NOTES";
field public static final String ROLE_SMS = "android.app.role.SMS";
}
diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt
index db2d140ca..8a32bb7f4 100644
--- a/framework-s/api/system-current.txt
+++ b/framework-s/api/system-current.txt
@@ -36,6 +36,7 @@ package android.app.role {
method @Deprecated @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public void setRoleNamesFromController(@NonNull java.util.List<java.lang.String>);
field public static final int MANAGE_HOLDERS_FLAG_DONT_KILL_APP = 1; // 0x1
field public static final String ROLE_DEVICE_POLICY_MANAGEMENT = "android.app.role.DEVICE_POLICY_MANAGEMENT";
+ field public static final String ROLE_FINANCED_DEVICE_KIOSK = "android.app.role.FINANCED_DEVICE_KIOSK";
field public static final String ROLE_SYSTEM_ACTIVITY_RECOGNIZER = "android.app.role.SYSTEM_ACTIVITY_RECOGNIZER";
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";
@@ -48,7 +49,9 @@ package android.safetycenter {
public final class SafetyCenterData implements android.os.Parcelable {
ctor public SafetyCenterData(@NonNull android.safetycenter.SafetyCenterStatus, @NonNull java.util.List<android.safetycenter.SafetyCenterIssue>, @NonNull java.util.List<android.safetycenter.SafetyCenterEntryOrGroup>, @NonNull java.util.List<android.safetycenter.SafetyCenterStaticEntryGroup>);
method public int describeContents();
+ method @NonNull public java.util.List<android.safetycenter.SafetyCenterIssue> getDismissedIssues();
method @NonNull public java.util.List<android.safetycenter.SafetyCenterEntryOrGroup> getEntriesOrGroups();
+ method @NonNull public android.os.Bundle getExtras();
method @NonNull public java.util.List<android.safetycenter.SafetyCenterIssue> getIssues();
method @NonNull public java.util.List<android.safetycenter.SafetyCenterStaticEntryGroup> getStaticEntryGroups();
method @NonNull public android.safetycenter.SafetyCenterStatus getStatus();
@@ -56,6 +59,22 @@ package android.safetycenter {
field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetyCenterData> CREATOR;
}
+ public static final class SafetyCenterData.Builder {
+ ctor public SafetyCenterData.Builder(@NonNull android.safetycenter.SafetyCenterStatus);
+ ctor public SafetyCenterData.Builder(@NonNull android.safetycenter.SafetyCenterData);
+ method @NonNull public android.safetycenter.SafetyCenterData.Builder addDismissedIssue(@NonNull android.safetycenter.SafetyCenterIssue);
+ method @NonNull public android.safetycenter.SafetyCenterData.Builder addEntryOrGroup(@NonNull android.safetycenter.SafetyCenterEntryOrGroup);
+ method @NonNull public android.safetycenter.SafetyCenterData.Builder addIssue(@NonNull android.safetycenter.SafetyCenterIssue);
+ method @NonNull public android.safetycenter.SafetyCenterData.Builder addStaticEntryGroup(@NonNull android.safetycenter.SafetyCenterStaticEntryGroup);
+ method @NonNull public android.safetycenter.SafetyCenterData build();
+ method @NonNull public android.safetycenter.SafetyCenterData.Builder clearDismissedIssues();
+ method @NonNull public android.safetycenter.SafetyCenterData.Builder clearEntriesOrGroups();
+ method @NonNull public android.safetycenter.SafetyCenterData.Builder clearExtras();
+ method @NonNull public android.safetycenter.SafetyCenterData.Builder clearIssues();
+ method @NonNull public android.safetycenter.SafetyCenterData.Builder clearStaticEntryGroups();
+ method @NonNull public android.safetycenter.SafetyCenterData.Builder setExtras(@NonNull android.os.Bundle);
+ }
+
public final class SafetyCenterEntry implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.safetycenter.SafetyCenterEntry.IconAction getIconAction();
@@ -149,6 +168,8 @@ package android.safetycenter {
public final class SafetyCenterIssue implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<android.safetycenter.SafetyCenterIssue.Action> getActions();
+ method @Nullable public CharSequence getAttributionTitle();
+ method @Nullable public String getGroupId();
method @NonNull public String getId();
method public int getSeverityLevel();
method @Nullable public CharSequence getSubtitle();
@@ -165,6 +186,7 @@ package android.safetycenter {
public static final class SafetyCenterIssue.Action implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public android.safetycenter.SafetyCenterIssue.Action.ConfirmationDialogDetails getConfirmationDialogDetails();
method @NonNull public String getId();
method @NonNull public CharSequence getLabel();
method @NonNull public android.app.PendingIntent getPendingIntent();
@@ -177,7 +199,9 @@ package android.safetycenter {
public static final class SafetyCenterIssue.Action.Builder {
ctor public SafetyCenterIssue.Action.Builder(@NonNull String, @NonNull CharSequence, @NonNull android.app.PendingIntent);
+ ctor public SafetyCenterIssue.Action.Builder(@NonNull android.safetycenter.SafetyCenterIssue.Action);
method @NonNull public android.safetycenter.SafetyCenterIssue.Action build();
+ method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setConfirmationDialogDetails(@Nullable android.safetycenter.SafetyCenterIssue.Action.ConfirmationDialogDetails);
method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setId(@NonNull String);
method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setIsInFlight(boolean);
method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setLabel(@NonNull CharSequence);
@@ -186,12 +210,25 @@ package android.safetycenter {
method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setWillResolve(boolean);
}
+ public static final class SafetyCenterIssue.Action.ConfirmationDialogDetails implements android.os.Parcelable {
+ ctor public SafetyCenterIssue.Action.ConfirmationDialogDetails(@NonNull CharSequence, @NonNull CharSequence, @NonNull CharSequence, @NonNull CharSequence);
+ method public int describeContents();
+ method @NonNull public CharSequence getAcceptButtonText();
+ method @NonNull public CharSequence getDenyButtonText();
+ method @NonNull public CharSequence getText();
+ method @NonNull public CharSequence getTitle();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetyCenterIssue.Action.ConfirmationDialogDetails> CREATOR;
+ }
+
public static final class SafetyCenterIssue.Builder {
ctor public SafetyCenterIssue.Builder(@NonNull String, @NonNull CharSequence, @NonNull CharSequence);
ctor public SafetyCenterIssue.Builder(@NonNull android.safetycenter.SafetyCenterIssue);
method @NonNull public android.safetycenter.SafetyCenterIssue build();
method @NonNull public android.safetycenter.SafetyCenterIssue.Builder setActions(@NonNull java.util.List<android.safetycenter.SafetyCenterIssue.Action>);
+ method @NonNull public android.safetycenter.SafetyCenterIssue.Builder setAttributionTitle(@Nullable CharSequence);
method @NonNull public android.safetycenter.SafetyCenterIssue.Builder setDismissible(boolean);
+ method @NonNull public android.safetycenter.SafetyCenterIssue.Builder setGroupId(@Nullable String);
method @NonNull public android.safetycenter.SafetyCenterIssue.Builder setId(@NonNull String);
method @NonNull public android.safetycenter.SafetyCenterIssue.Builder setSeverityLevel(int);
method @NonNull public android.safetycenter.SafetyCenterIssue.Builder setShouldConfirmDismissal(boolean);
@@ -211,6 +248,7 @@ package android.safetycenter {
method @Nullable @RequiresPermission(android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE) public android.safetycenter.SafetySourceData getSafetySourceData(@NonNull String);
method @RequiresPermission(anyOf={android.Manifest.permission.READ_SAFETY_CENTER_STATUS, android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE}) public boolean isSafetyCenterEnabled();
method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void refreshSafetySources(int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void refreshSafetySources(int, @NonNull java.util.List<java.lang.String>);
method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void removeOnSafetyCenterDataChangedListener(@NonNull android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener);
method @RequiresPermission(android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE) public void reportSafetySourceError(@NonNull String, @NonNull android.safetycenter.SafetySourceErrorDetails);
method @RequiresPermission(android.Manifest.permission.MANAGE_SAFETY_CENTER) public void setSafetyCenterConfigForTests(@NonNull android.safetycenter.config.SafetyCenterConfig);
@@ -222,6 +260,7 @@ package android.safetycenter {
field public static final String EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID = "android.safetycenter.extra.REFRESH_SAFETY_SOURCES_BROADCAST_ID";
field public static final String EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE = "android.safetycenter.extra.REFRESH_SAFETY_SOURCES_REQUEST_TYPE";
field public static final String EXTRA_REFRESH_SAFETY_SOURCE_IDS = "android.safetycenter.extra.REFRESH_SAFETY_SOURCE_IDS";
+ field public static final String EXTRA_SAFETY_SOURCES_GROUP_ID = "android.safetycenter.extra.SAFETY_SOURCES_GROUP_ID";
field public static final String EXTRA_SAFETY_SOURCE_ID = "android.safetycenter.extra.SAFETY_SOURCE_ID";
field public static final String EXTRA_SAFETY_SOURCE_ISSUE_ID = "android.safetycenter.extra.SAFETY_SOURCE_ISSUE_ID";
field public static final String EXTRA_SAFETY_SOURCE_USER_HANDLE = "android.safetycenter.extra.SAFETY_SOURCE_USER_HANDLE";
@@ -229,6 +268,7 @@ package android.safetycenter {
field public static final int REFRESH_REASON_DEVICE_REBOOT = 300; // 0x12c
field public static final int REFRESH_REASON_OTHER = 600; // 0x258
field public static final int REFRESH_REASON_PAGE_OPEN = 100; // 0x64
+ field public static final int REFRESH_REASON_PERIODIC = 700; // 0x2bc
field public static final int REFRESH_REASON_RESCAN_BUTTON_CLICK = 200; // 0xc8
field public static final int REFRESH_REASON_SAFETY_CENTER_ENABLED = 500; // 0x1f4
}
@@ -310,6 +350,7 @@ package android.safetycenter {
public static final class SafetyEvent.Builder {
ctor public SafetyEvent.Builder(int);
+ ctor public SafetyEvent.Builder(@NonNull android.safetycenter.SafetyEvent);
method @NonNull public android.safetycenter.SafetyEvent build();
method @NonNull public android.safetycenter.SafetyEvent.Builder setRefreshBroadcastId(@Nullable String);
method @NonNull public android.safetycenter.SafetyEvent.Builder setSafetySourceIssueActionId(@Nullable String);
@@ -318,6 +359,7 @@ package android.safetycenter {
public final class SafetySourceData implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public android.os.Bundle getExtras();
method @NonNull public java.util.List<android.safetycenter.SafetySourceIssue> getIssues();
method @Nullable public android.safetycenter.SafetySourceStatus getStatus();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -330,9 +372,12 @@ package android.safetycenter {
public static final class SafetySourceData.Builder {
ctor public SafetySourceData.Builder();
+ ctor public SafetySourceData.Builder(@NonNull android.safetycenter.SafetySourceData);
method @NonNull public android.safetycenter.SafetySourceData.Builder addIssue(@NonNull android.safetycenter.SafetySourceIssue);
method @NonNull public android.safetycenter.SafetySourceData build();
+ method @NonNull public android.safetycenter.SafetySourceData.Builder clearExtras();
method @NonNull public android.safetycenter.SafetySourceData.Builder clearIssues();
+ method @NonNull public android.safetycenter.SafetySourceData.Builder setExtras(@NonNull android.os.Bundle);
method @NonNull public android.safetycenter.SafetySourceData.Builder setStatus(@Nullable android.safetycenter.SafetySourceStatus);
}
@@ -347,9 +392,14 @@ package android.safetycenter {
public final class SafetySourceIssue implements android.os.Parcelable {
method public int describeContents();
method @NonNull public java.util.List<android.safetycenter.SafetySourceIssue.Action> getActions();
+ method @Nullable public CharSequence getAttributionTitle();
+ method @Nullable public android.safetycenter.SafetySourceIssue.Notification getCustomNotification();
+ method @Nullable public String getDeduplicationId();
method @NonNull public String getId();
+ method public int getIssueActionability();
method public int getIssueCategory();
method @NonNull public String getIssueTypeId();
+ method public int getNotificationBehavior();
method @Nullable public android.app.PendingIntent getOnDismissPendingIntent();
method public int getSeverityLevel();
method @Nullable public CharSequence getSubtitle();
@@ -357,13 +407,24 @@ package android.safetycenter {
method @NonNull public CharSequence getTitle();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetySourceIssue> CREATOR;
+ field public static final int ISSUE_ACTIONABILITY_AUTOMATIC = 200; // 0xc8
+ field public static final int ISSUE_ACTIONABILITY_MANUAL = 0; // 0x0
+ field public static final int ISSUE_ACTIONABILITY_TIP = 100; // 0x64
field public static final int ISSUE_CATEGORY_ACCOUNT = 200; // 0xc8
+ field public static final int ISSUE_CATEGORY_DATA = 400; // 0x190
field public static final int ISSUE_CATEGORY_DEVICE = 100; // 0x64
field public static final int ISSUE_CATEGORY_GENERAL = 300; // 0x12c
+ field public static final int ISSUE_CATEGORY_PASSWORDS = 500; // 0x1f4
+ field public static final int ISSUE_CATEGORY_PERSONAL_SAFETY = 600; // 0x258
+ field public static final int NOTIFICATION_BEHAVIOR_DELAYED = 200; // 0xc8
+ field public static final int NOTIFICATION_BEHAVIOR_IMMEDIATELY = 300; // 0x12c
+ field public static final int NOTIFICATION_BEHAVIOR_NEVER = 100; // 0x64
+ field public static final int NOTIFICATION_BEHAVIOR_UNSPECIFIED = 0; // 0x0
}
public static final class SafetySourceIssue.Action implements android.os.Parcelable {
method public int describeContents();
+ method @Nullable public android.safetycenter.SafetySourceIssue.Action.ConfirmationDialogDetails getConfirmationDialogDetails();
method @NonNull public String getId();
method @NonNull public CharSequence getLabel();
method @NonNull public android.app.PendingIntent getPendingIntent();
@@ -375,21 +436,58 @@ package android.safetycenter {
public static final class SafetySourceIssue.Action.Builder {
ctor public SafetySourceIssue.Action.Builder(@NonNull String, @NonNull CharSequence, @NonNull android.app.PendingIntent);
+ ctor public SafetySourceIssue.Action.Builder(@NonNull android.safetycenter.SafetySourceIssue.Action);
method @NonNull public android.safetycenter.SafetySourceIssue.Action build();
+ method @NonNull public android.safetycenter.SafetySourceIssue.Action.Builder setConfirmationDialogDetails(@Nullable android.safetycenter.SafetySourceIssue.Action.ConfirmationDialogDetails);
method @NonNull public android.safetycenter.SafetySourceIssue.Action.Builder setSuccessMessage(@Nullable CharSequence);
method @NonNull public android.safetycenter.SafetySourceIssue.Action.Builder setWillResolve(boolean);
}
+ public static final class SafetySourceIssue.Action.ConfirmationDialogDetails implements android.os.Parcelable {
+ ctor public SafetySourceIssue.Action.ConfirmationDialogDetails(@NonNull CharSequence, @NonNull CharSequence, @NonNull CharSequence, @NonNull CharSequence);
+ method public int describeContents();
+ method @NonNull public CharSequence getAcceptButtonText();
+ method @NonNull public CharSequence getDenyButtonText();
+ method @NonNull public CharSequence getText();
+ method @NonNull public CharSequence getTitle();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetySourceIssue.Action.ConfirmationDialogDetails> CREATOR;
+ }
+
public static final class SafetySourceIssue.Builder {
ctor public SafetySourceIssue.Builder(@NonNull String, @NonNull CharSequence, @NonNull CharSequence, int, @NonNull String);
+ ctor public SafetySourceIssue.Builder(@NonNull android.safetycenter.SafetySourceIssue);
method @NonNull public android.safetycenter.SafetySourceIssue.Builder addAction(@NonNull android.safetycenter.SafetySourceIssue.Action);
method @NonNull public android.safetycenter.SafetySourceIssue build();
method @NonNull public android.safetycenter.SafetySourceIssue.Builder clearActions();
+ method @NonNull public android.safetycenter.SafetySourceIssue.Builder setAttributionTitle(@Nullable CharSequence);
+ method @NonNull public android.safetycenter.SafetySourceIssue.Builder setCustomNotification(@Nullable android.safetycenter.SafetySourceIssue.Notification);
+ method @NonNull public android.safetycenter.SafetySourceIssue.Builder setDeduplicationId(@Nullable String);
+ method @NonNull public android.safetycenter.SafetySourceIssue.Builder setIssueActionability(int);
method @NonNull public android.safetycenter.SafetySourceIssue.Builder setIssueCategory(int);
+ method @NonNull public android.safetycenter.SafetySourceIssue.Builder setNotificationBehavior(int);
method @NonNull public android.safetycenter.SafetySourceIssue.Builder setOnDismissPendingIntent(@Nullable android.app.PendingIntent);
method @NonNull public android.safetycenter.SafetySourceIssue.Builder setSubtitle(@Nullable CharSequence);
}
+ public static final class SafetySourceIssue.Notification implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<android.safetycenter.SafetySourceIssue.Action> getActions();
+ method @NonNull public CharSequence getText();
+ method @NonNull public CharSequence getTitle();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetySourceIssue.Notification> CREATOR;
+ }
+
+ public static final class SafetySourceIssue.Notification.Builder {
+ ctor public SafetySourceIssue.Notification.Builder(@NonNull CharSequence, @NonNull CharSequence);
+ ctor public SafetySourceIssue.Notification.Builder(@NonNull android.safetycenter.SafetySourceIssue.Notification);
+ method @NonNull public android.safetycenter.SafetySourceIssue.Notification.Builder addAction(@NonNull android.safetycenter.SafetySourceIssue.Action);
+ method @NonNull public android.safetycenter.SafetySourceIssue.Notification.Builder addActions(@NonNull java.util.List<android.safetycenter.SafetySourceIssue.Action>);
+ method @NonNull public android.safetycenter.SafetySourceIssue.Notification build();
+ method @NonNull public android.safetycenter.SafetySourceIssue.Notification.Builder clearActions();
+ }
+
public final class SafetySourceStatus implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.safetycenter.SafetySourceStatus.IconAction getIconAction();
@@ -404,6 +502,7 @@ package android.safetycenter {
public static final class SafetySourceStatus.Builder {
ctor public SafetySourceStatus.Builder(@NonNull CharSequence, @NonNull CharSequence, int);
+ ctor public SafetySourceStatus.Builder(@NonNull android.safetycenter.SafetySourceStatus);
method @NonNull public android.safetycenter.SafetySourceStatus build();
method @NonNull public android.safetycenter.SafetySourceStatus.Builder setEnabled(boolean);
method @NonNull public android.safetycenter.SafetySourceStatus.Builder setIconAction(@Nullable android.safetycenter.SafetySourceStatus.IconAction);
@@ -434,16 +533,21 @@ package android.safetycenter.config {
public static final class SafetyCenterConfig.Builder {
ctor public SafetyCenterConfig.Builder();
+ ctor public SafetyCenterConfig.Builder(@NonNull android.safetycenter.config.SafetyCenterConfig);
method @NonNull public android.safetycenter.config.SafetyCenterConfig.Builder addSafetySourcesGroup(@NonNull android.safetycenter.config.SafetySourcesGroup);
method @NonNull public android.safetycenter.config.SafetyCenterConfig build();
}
public final class SafetySource implements android.os.Parcelable {
+ method public boolean areNotificationsAllowed();
method public int describeContents();
+ method @Nullable public String getDeduplicationGroup();
method @NonNull public String getId();
method public int getInitialDisplayState();
method @Nullable public String getIntentAction();
method public int getMaxSeverityLevel();
+ method @Nullable public String getOptionalPackageName();
+ method @NonNull public java.util.Set<java.lang.String> getPackageCertificateHashes();
method @NonNull public String getPackageName();
method public int getProfile();
method @StringRes public int getSearchTermsResId();
@@ -468,12 +572,16 @@ package android.safetycenter.config {
public static final class SafetySource.Builder {
ctor public SafetySource.Builder(int);
+ ctor public SafetySource.Builder(@NonNull android.safetycenter.config.SafetySource);
+ method @NonNull public android.safetycenter.config.SafetySource.Builder addPackageCertificateHash(@NonNull String);
method @NonNull public android.safetycenter.config.SafetySource build();
+ method @NonNull public android.safetycenter.config.SafetySource.Builder setDeduplicationGroup(@Nullable String);
method @NonNull public android.safetycenter.config.SafetySource.Builder setId(@Nullable String);
method @NonNull public android.safetycenter.config.SafetySource.Builder setInitialDisplayState(int);
method @NonNull public android.safetycenter.config.SafetySource.Builder setIntentAction(@Nullable String);
method @NonNull public android.safetycenter.config.SafetySource.Builder setLoggingAllowed(boolean);
method @NonNull public android.safetycenter.config.SafetySource.Builder setMaxSeverityLevel(int);
+ method @NonNull public android.safetycenter.config.SafetySource.Builder setNotificationsAllowed(boolean);
method @NonNull public android.safetycenter.config.SafetySource.Builder setPackageName(@Nullable String);
method @NonNull public android.safetycenter.config.SafetySource.Builder setProfile(int);
method @NonNull public android.safetycenter.config.SafetySource.Builder setRefreshOnPageOpenAllowed(boolean);
@@ -493,21 +601,34 @@ package android.safetycenter.config {
method public int getType();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.config.SafetySourcesGroup> CREATOR;
- field public static final int SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE = 0; // 0x0
+ field @Deprecated public static final int SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE = 0; // 0x0
field public static final int SAFETY_SOURCES_GROUP_TYPE_HIDDEN = 2; // 0x2
- field public static final int SAFETY_SOURCES_GROUP_TYPE_RIGID = 1; // 0x1
+ field @Deprecated public static final int SAFETY_SOURCES_GROUP_TYPE_RIGID = 1; // 0x1
+ field public static final int SAFETY_SOURCES_GROUP_TYPE_STATEFUL = 0; // 0x0
+ field public static final int SAFETY_SOURCES_GROUP_TYPE_STATELESS = 1; // 0x1
field public static final int STATELESS_ICON_TYPE_NONE = 0; // 0x0
field public static final int STATELESS_ICON_TYPE_PRIVACY = 1; // 0x1
}
public static final class SafetySourcesGroup.Builder {
ctor public SafetySourcesGroup.Builder();
+ ctor public SafetySourcesGroup.Builder(@NonNull android.safetycenter.config.SafetySourcesGroup);
method @NonNull public android.safetycenter.config.SafetySourcesGroup.Builder addSafetySource(@NonNull android.safetycenter.config.SafetySource);
method @NonNull public android.safetycenter.config.SafetySourcesGroup build();
method @NonNull public android.safetycenter.config.SafetySourcesGroup.Builder setId(@Nullable String);
method @NonNull public android.safetycenter.config.SafetySourcesGroup.Builder setStatelessIconType(int);
method @NonNull public android.safetycenter.config.SafetySourcesGroup.Builder setSummaryResId(@StringRes int);
method @NonNull public android.safetycenter.config.SafetySourcesGroup.Builder setTitleResId(@StringRes int);
+ method @NonNull public android.safetycenter.config.SafetySourcesGroup.Builder setType(int);
+ }
+
+}
+
+package android.safetylabel {
+
+ public final class SafetyLabelConstants {
+ field public static final String PERMISSION_RATIONALE_ENABLED = "permission_rationale_enabled";
+ field public static final String SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED = "safety_label_change_notifications_enabled";
}
}
diff --git a/framework-s/java/android/app/role/RoleManager.java b/framework-s/java/android/app/role/RoleManager.java
index bd8831070..aeec3e15d 100644
--- a/framework-s/java/android/app/role/RoleManager.java
+++ b/framework-s/java/android/app/role/RoleManager.java
@@ -129,6 +129,14 @@ public final class RoleManager {
public static final String ROLE_CALL_SCREENING = "android.app.role.CALL_SCREENING";
/**
+ * The name of the notes role.
+ *
+ * @see Intent#ACTION_CREATE_NOTE
+ * @see Intent#EXTRA_USE_STYLUS_MODE
+ */
+ public static final String ROLE_NOTES = "android.app.role.NOTES";
+
+ /**
* The name of the system wellbeing role.
*
* @hide
@@ -163,6 +171,15 @@ public final class RoleManager {
"android.app.role.DEVICE_POLICY_MANAGEMENT";
/**
+ * The name of the financed device kiosk role.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String ROLE_FINANCED_DEVICE_KIOSK =
+ "android.app.role.FINANCED_DEVICE_KIOSK";
+
+ /**
* @hide
*/
@IntDef(flag = true, value = { MANAGE_HOLDERS_FLAG_DONT_KILL_APP })
diff --git a/framework-s/java/android/app/role/TEST_MAPPING b/framework-s/java/android/app/role/TEST_MAPPING
index f8f140dd7..ce53dca05 100644
--- a/framework-s/java/android/app/role/TEST_MAPPING
+++ b/framework-s/java/android/app/role/TEST_MAPPING
@@ -4,7 +4,24 @@
"name": "CtsRoleTestCases",
"options": [
{
- "include-filter": "android.app.role.cts.RoleManagerTest"
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsRoleTestCases[com.google.android.permission.apex]",
+ "options": [
+ // TODO(b/238677748): These two tests currently fails on R base image
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList"
+ },
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
}
]
}
diff --git a/framework-s/java/android/safetycenter/ISafetyCenterManager.aidl b/framework-s/java/android/safetycenter/ISafetyCenterManager.aidl
index a00bfa19d..3290c221b 100644
--- a/framework-s/java/android/safetycenter/ISafetyCenterManager.aidl
+++ b/framework-s/java/android/safetycenter/ISafetyCenterManager.aidl
@@ -22,6 +22,7 @@ import android.safetycenter.SafetyEvent;
import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceErrorDetails;
import android.safetycenter.config.SafetyCenterConfig;
+import java.util.List;
/**
* AIDL Interface for communicating with the Safety Center, which consolidates UI for security and
@@ -72,6 +73,12 @@ interface ISafetyCenterManager {
/** Requests safety sources to set their latest SafetySourceData for Safety Center. */
void refreshSafetySources(int refreshReason, int userId);
+ /**
+ * Requests a specific subset of safety sources to set their latest SafetySourceData for
+ * Safety Center.
+ */
+ void refreshSpecificSafetySources(int refreshReason, int userId, in List<String> safetySourceIds);
+
/** Returns the current SafetyCenterConfig, if available. */
SafetyCenterConfig getSafetyCenterConfig();
@@ -90,8 +97,7 @@ interface ISafetyCenterManager {
int userId);
/**
- * Dismiss a Safety Center issue and prevent it from appearing in the Safety Center or affecting
- * the overall safety status.
+ * Dismiss a Safety Center issue and prevent it affecting the overall safety status.
*/
void dismissSafetyCenterIssue(String issueId, int userId);
diff --git a/framework-s/java/android/safetycenter/SafetyCenterData.java b/framework-s/java/android/safetycenter/SafetyCenterData.java
index 9a6e7f0ec..005adb2be 100644
--- a/framework-s/java/android/safetycenter/SafetyCenterData.java
+++ b/framework-s/java/android/safetycenter/SafetyCenterData.java
@@ -17,17 +17,21 @@
package android.safetycenter;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -53,7 +57,32 @@ public final class SafetyCenterData implements Parcelable {
in.createTypedArrayList(SafetyCenterEntryOrGroup.CREATOR);
List<SafetyCenterStaticEntryGroup> staticEntryGroups =
in.createTypedArrayList(SafetyCenterStaticEntryGroup.CREATOR);
- return new SafetyCenterData(status, issues, entryOrGroups, staticEntryGroups);
+
+ if (SdkLevel.isAtLeastU()) {
+ List<SafetyCenterIssue> dismissedIssues =
+ in.createTypedArrayList(SafetyCenterIssue.CREATOR);
+ Bundle extras = in.readBundle(getClass().getClassLoader());
+ SafetyCenterData.Builder builder = new SafetyCenterData.Builder(status);
+ for (int i = 0; i < issues.size(); i++) {
+ builder.addIssue(issues.get(i));
+ }
+ for (int i = 0; i < entryOrGroups.size(); i++) {
+ builder.addEntryOrGroup(entryOrGroups.get(i));
+ }
+ for (int i = 0; i < staticEntryGroups.size(); i++) {
+ builder.addStaticEntryGroup(staticEntryGroups.get(i));
+ }
+ for (int i = 0; i < dismissedIssues.size(); i++) {
+ builder.addDismissedIssue(dismissedIssues.get(i));
+ }
+ if (extras != null) {
+ builder.setExtras(extras);
+ }
+ return builder.build();
+ } else {
+ return new SafetyCenterData(
+ status, issues, entryOrGroups, staticEntryGroups);
+ }
}
@Override
@@ -66,6 +95,8 @@ public final class SafetyCenterData implements Parcelable {
@NonNull private final List<SafetyCenterIssue> mIssues;
@NonNull private final List<SafetyCenterEntryOrGroup> mEntriesOrGroups;
@NonNull private final List<SafetyCenterStaticEntryGroup> mStaticEntryGroups;
+ @NonNull private final List<SafetyCenterIssue> mDismissedIssues;
+ @NonNull private final Bundle mExtras;
/** Creates a {@link SafetyCenterData}. */
public SafetyCenterData(
@@ -77,6 +108,23 @@ public final class SafetyCenterData implements Parcelable {
mIssues = unmodifiableList(new ArrayList<>(requireNonNull(issues)));
mEntriesOrGroups = unmodifiableList(new ArrayList<>(requireNonNull(entriesOrGroups)));
mStaticEntryGroups = unmodifiableList(new ArrayList<>(requireNonNull(staticEntryGroups)));
+ mDismissedIssues = unmodifiableList(new ArrayList<>());
+ mExtras = Bundle.EMPTY;
+ }
+
+ private SafetyCenterData(
+ @NonNull SafetyCenterStatus status,
+ @NonNull List<SafetyCenterIssue> issues,
+ @NonNull List<SafetyCenterEntryOrGroup> entriesOrGroups,
+ @NonNull List<SafetyCenterStaticEntryGroup> staticEntryGroups,
+ @NonNull List<SafetyCenterIssue> dismissedIssues,
+ @NonNull Bundle extras) {
+ mStatus = status;
+ mIssues = issues;
+ mEntriesOrGroups = entriesOrGroups;
+ mStaticEntryGroups = staticEntryGroups;
+ mDismissedIssues = dismissedIssues;
+ mExtras = extras;
}
/** Returns the overall {@link SafetyCenterStatus} of the Safety Center. */
@@ -106,6 +154,31 @@ public final class SafetyCenterData implements Parcelable {
return mStaticEntryGroups;
}
+ /** Returns the list of dismissed {@link SafetyCenterIssue} objects in the Safety Center. */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public List<SafetyCenterIssue> getDismissedIssues() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mDismissedIssues;
+ }
+
+ /**
+ * Returns a {@link Bundle} containing additional information, {@link Bundle#EMPTY} by default.
+ *
+ * <p>Note: internal state of this {@link Bundle} is not used for {@link Object#equals} and
+ * {@link Object#hashCode} implementation of {@link SafetyCenterData}.
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Bundle getExtras() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mExtras;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -114,12 +187,14 @@ public final class SafetyCenterData implements Parcelable {
return Objects.equals(mStatus, that.mStatus)
&& Objects.equals(mIssues, that.mIssues)
&& Objects.equals(mEntriesOrGroups, that.mEntriesOrGroups)
- && Objects.equals(mStaticEntryGroups, that.mStaticEntryGroups);
+ && Objects.equals(mStaticEntryGroups, that.mStaticEntryGroups)
+ && Objects.equals(mDismissedIssues, that.mDismissedIssues);
}
@Override
public int hashCode() {
- return Objects.hash(mStatus, mIssues, mEntriesOrGroups, mStaticEntryGroups);
+ return Objects.hash(
+ mStatus, mIssues, mEntriesOrGroups, mStaticEntryGroups, mDismissedIssues);
}
@Override
@@ -133,6 +208,9 @@ public final class SafetyCenterData implements Parcelable {
+ mEntriesOrGroups
+ ", mStaticEntryGroups="
+ mStaticEntryGroups
+ + ", mDismissedIssues="
+ + mDismissedIssues
+ + (!mExtras.isEmpty() ? ", (has extras)" : "")
+ '}';
}
@@ -147,5 +225,155 @@ public final class SafetyCenterData implements Parcelable {
dest.writeTypedList(mIssues);
dest.writeTypedList(mEntriesOrGroups);
dest.writeTypedList(mStaticEntryGroups);
+ if (SdkLevel.isAtLeastU()) {
+ dest.writeTypedList(mDismissedIssues);
+ dest.writeBundle(mExtras);
+ }
+ }
+
+ /** Builder class for {@link SafetyCenterData}. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final class Builder {
+
+ @NonNull private final SafetyCenterStatus mStatus;
+ @NonNull private final List<SafetyCenterIssue> mIssues = new ArrayList<>();
+ @NonNull private final List<SafetyCenterEntryOrGroup> mEntriesOrGroups = new ArrayList<>();
+
+ @NonNull
+ private final List<SafetyCenterStaticEntryGroup> mStaticEntryGroups = new ArrayList<>();
+
+ @NonNull private final List<SafetyCenterIssue> mDismissedIssues = new ArrayList<>();
+ @NonNull private Bundle mExtras = Bundle.EMPTY;
+
+ public Builder(@NonNull SafetyCenterStatus status) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mStatus = requireNonNull(status);
+ }
+
+ /** Creates a {@link Builder} with the values from the given {@link SafetyCenterData}. */
+ public Builder(@NonNull SafetyCenterData safetyCenterData) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ requireNonNull(safetyCenterData);
+ mStatus = safetyCenterData.mStatus;
+ mIssues.addAll(safetyCenterData.mIssues);
+ mEntriesOrGroups.addAll(safetyCenterData.mEntriesOrGroups);
+ mStaticEntryGroups.addAll(safetyCenterData.mStaticEntryGroups);
+ mDismissedIssues.addAll(safetyCenterData.mDismissedIssues);
+ mExtras = safetyCenterData.mExtras.deepCopy();
+ }
+
+ /** Adds data for a {@link SafetyCenterIssue} to be shown in UI. */
+ @NonNull
+ public SafetyCenterData.Builder addIssue(@NonNull SafetyCenterIssue safetyCenterIssue) {
+ mIssues.add(requireNonNull(safetyCenterIssue));
+ return this;
+ }
+
+ /** Adds data for a {@link SafetyCenterEntryOrGroup} to be shown in UI. */
+ @NonNull
+ @SuppressWarnings("MissingGetterMatchingBuilder") // incorrectly expects "getEntryOrGroups"
+ public SafetyCenterData.Builder addEntryOrGroup(
+ @NonNull SafetyCenterEntryOrGroup safetyCenterEntryOrGroup) {
+ mEntriesOrGroups.add(requireNonNull(safetyCenterEntryOrGroup));
+ return this;
+ }
+
+ /** Adds data for a {@link SafetyCenterStaticEntryGroup} to be shown in UI. */
+ @NonNull
+ public SafetyCenterData.Builder addStaticEntryGroup(
+ @NonNull SafetyCenterStaticEntryGroup safetyCenterStaticEntryGroup) {
+ mStaticEntryGroups.add(requireNonNull(safetyCenterStaticEntryGroup));
+ return this;
+ }
+
+ /** Adds data for a dismissed {@link SafetyCenterIssue} to be shown in UI. */
+ @NonNull
+ public SafetyCenterData.Builder addDismissedIssue(
+ @NonNull SafetyCenterIssue dismissedSafetyCenterIssue) {
+ mDismissedIssues.add(requireNonNull(dismissedSafetyCenterIssue));
+ return this;
+ }
+
+ /**
+ * Sets additional information for the {@link SafetyCenterData}.
+ *
+ * If not set, the default value is {@link Bundle#EMPTY}.
+ */
+ @NonNull
+ public SafetyCenterData.Builder setExtras(@NonNull Bundle extras) {
+ mExtras = requireNonNull(extras);
+ return this;
+ }
+
+ /**
+ * Resets additional information for the {@link SafetyCenterData} to the default value of
+ * {@link Bundle#EMPTY}.
+ */
+ @NonNull
+ public SafetyCenterData.Builder clearExtras() {
+ mExtras = Bundle.EMPTY;
+ return this;
+ }
+
+ /**
+ * Clears data for all the {@link SafetyCenterIssue}s that were added to this {@link
+ * SafetyCenterData.Builder}.
+ */
+ @NonNull
+ public SafetyCenterData.Builder clearIssues() {
+ mIssues.clear();
+ return this;
+ }
+
+ /**
+ * Clears data for all the {@link SafetyCenterEntryOrGroup}s that were added to this {@link
+ * SafetyCenterData.Builder}.
+ */
+ @NonNull
+ public SafetyCenterData.Builder clearEntriesOrGroups() {
+ mEntriesOrGroups.clear();
+ return this;
+ }
+
+ /**
+ * Clears data for all the {@link SafetyCenterStaticEntryGroup}s that were added to this
+ * {@link SafetyCenterData.Builder}.
+ */
+ @NonNull
+ public SafetyCenterData.Builder clearStaticEntryGroups() {
+ mStaticEntryGroups.clear();
+ return this;
+ }
+
+ /**
+ * Clears data for all the dismissed {@link SafetyCenterIssue}s that were added to this
+ * {@link SafetyCenterData.Builder}.
+ */
+ @NonNull
+ public SafetyCenterData.Builder clearDismissedIssues() {
+ mDismissedIssues.clear();
+ return this;
+ }
+
+ /**
+ * Creates the {@link SafetyCenterData} defined by this {@link SafetyCenterData.Builder}.
+ */
+ @NonNull
+ public SafetyCenterData build() {
+ List<SafetyCenterIssue> issues = unmodifiableList(new ArrayList<>(mIssues));
+ List<SafetyCenterEntryOrGroup> entriesOrGroups =
+ unmodifiableList(new ArrayList<>(mEntriesOrGroups));
+ List<SafetyCenterStaticEntryGroup> staticEntryGroups =
+ unmodifiableList(new ArrayList<>(mStaticEntryGroups));
+ List<SafetyCenterIssue> dismissedIssues =
+ unmodifiableList(new ArrayList<>(mDismissedIssues));
+
+ return new SafetyCenterData(
+ mStatus, issues, entriesOrGroups, staticEntryGroups, dismissedIssues, mExtras);
+ }
}
}
diff --git a/framework-s/java/android/safetycenter/SafetyCenterIssue.java b/framework-s/java/android/safetycenter/SafetyCenterIssue.java
index 6d52e025d..cee872a3a 100644
--- a/framework-s/java/android/safetycenter/SafetyCenterIssue.java
+++ b/framework-s/java/android/safetycenter/SafetyCenterIssue.java
@@ -17,6 +17,7 @@
package android.safetycenter;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
@@ -29,10 +30,13 @@ import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.os.Parcel;
import android.os.Parcelable;
+import android.safetycenter.config.SafetySourcesGroup;
import android.text.TextUtils;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -91,13 +95,19 @@ public final class SafetyCenterIssue implements Parcelable {
CharSequence title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
CharSequence subtitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
CharSequence summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- return new Builder(id, title, summary)
- .setSubtitle(subtitle)
- .setSeverityLevel(in.readInt())
- .setDismissible(in.readBoolean())
- .setShouldConfirmDismissal(in.readBoolean())
- .setActions(in.createTypedArrayList(Action.CREATOR))
- .build();
+ SafetyCenterIssue.Builder builder =
+ new Builder(id, title, summary)
+ .setSubtitle(subtitle)
+ .setSeverityLevel(in.readInt())
+ .setDismissible(in.readBoolean())
+ .setShouldConfirmDismissal(in.readBoolean())
+ .setActions(in.createTypedArrayList(Action.CREATOR));
+ if (SdkLevel.isAtLeastU()) {
+ builder.setAttributionTitle(
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
+ builder.setGroupId(in.readString());
+ }
+ return builder.build();
}
@Override
@@ -114,6 +124,8 @@ public final class SafetyCenterIssue implements Parcelable {
private final boolean mDismissible;
private final boolean mShouldConfirmDismissal;
@NonNull private final List<Action> mActions;
+ @Nullable private final CharSequence mAttributionTitle;
+ @Nullable private final String mGroupId;
private SafetyCenterIssue(
@NonNull String id,
@@ -123,7 +135,9 @@ public final class SafetyCenterIssue implements Parcelable {
@IssueSeverityLevel int severityLevel,
boolean isDismissible,
boolean shouldConfirmDismissal,
- @NonNull List<Action> actions) {
+ @NonNull List<Action> actions,
+ @Nullable CharSequence attributionTitle,
+ @Nullable String groupId) {
mId = id;
mTitle = title;
mSubtitle = subtitle;
@@ -132,6 +146,8 @@ public final class SafetyCenterIssue implements Parcelable {
mDismissible = isDismissible;
mShouldConfirmDismissal = shouldConfirmDismissal;
mActions = actions;
+ mAttributionTitle = attributionTitle;
+ mGroupId = groupId;
}
/**
@@ -161,6 +177,24 @@ public final class SafetyCenterIssue implements Parcelable {
return mSummary;
}
+ /**
+ * Returns the attribution title of this issue, or {@code null} if it has none.
+ *
+ * <p>This is displayed in the UI and helps to attribute issue cards to a particular source.
+ *
+ * @throws UnsupportedOperationException if accessed from a version lower than {@link
+ * UPSIDE_DOWN_CAKE}
+ */
+ @Nullable
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public CharSequence getAttributionTitle() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException(
+ "Method not supported for versions lower than UPSIDE_DOWN_CAKE");
+ }
+ return mAttributionTitle;
+ }
+
/** Returns the {@link IssueSeverityLevel} of this issue. */
@IssueSeverityLevel
public int getSeverityLevel() {
@@ -188,6 +222,26 @@ public final class SafetyCenterIssue implements Parcelable {
return mActions;
}
+ /**
+ * Returns the ID of the {@link SafetySourcesGroup} that this issue belongs to, or {@code null}
+ * if it has none.
+ *
+ * <p>This ID is used for displaying the issue on its corresponding subpage in the Safety Center
+ * UI.
+ *
+ * @throws UnsupportedOperationException if accessed from a version lower than {@link
+ * UPSIDE_DOWN_CAKE}
+ */
+ @Nullable
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public String getGroupId() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException(
+ "Method not supported for versions lower than UPSIDE_DOWN_CAKE");
+ }
+ return mGroupId;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -200,7 +254,9 @@ public final class SafetyCenterIssue implements Parcelable {
&& TextUtils.equals(mTitle, that.mTitle)
&& TextUtils.equals(mSubtitle, that.mSubtitle)
&& TextUtils.equals(mSummary, that.mSummary)
- && Objects.equals(mActions, that.mActions);
+ && Objects.equals(mActions, that.mActions)
+ && TextUtils.equals(mAttributionTitle, that.mAttributionTitle)
+ && Objects.equals(mGroupId, that.mGroupId);
}
@Override
@@ -213,7 +269,9 @@ public final class SafetyCenterIssue implements Parcelable {
mSeverityLevel,
mDismissible,
mShouldConfirmDismissal,
- mActions);
+ mActions,
+ mAttributionTitle,
+ mGroupId);
}
@Override
@@ -235,6 +293,10 @@ public final class SafetyCenterIssue implements Parcelable {
+ mShouldConfirmDismissal
+ ", mActions="
+ mActions
+ + ", mAttributionTitle="
+ + mAttributionTitle
+ + ", mGroupId="
+ + mGroupId
+ '}';
}
@@ -253,6 +315,10 @@ public final class SafetyCenterIssue implements Parcelable {
dest.writeBoolean(mDismissible);
dest.writeBoolean(mShouldConfirmDismissal);
dest.writeTypedList(mActions);
+ if (SdkLevel.isAtLeastU()) {
+ TextUtils.writeToParcel(mAttributionTitle, dest, flags);
+ dest.writeString(mGroupId);
+ }
}
/** Builder class for {@link SafetyCenterIssue}. */
@@ -266,6 +332,8 @@ public final class SafetyCenterIssue implements Parcelable {
private boolean mDismissible = true;
private boolean mShouldConfirmDismissal = true;
private List<Action> mActions = new ArrayList<>();
+ @Nullable private CharSequence mAttributionTitle;
+ @Nullable private String mGroupId;
/**
* Creates a {@link Builder} for a {@link SafetyCenterIssue}.
@@ -291,6 +359,8 @@ public final class SafetyCenterIssue implements Parcelable {
mDismissible = issue.mDismissible;
mShouldConfirmDismissal = issue.mShouldConfirmDismissal;
mActions = new ArrayList<>(issue.mActions);
+ mAttributionTitle = issue.mAttributionTitle;
+ mGroupId = issue.mGroupId;
}
/** Sets the ID for this issue. */
@@ -322,6 +392,25 @@ public final class SafetyCenterIssue implements Parcelable {
}
/**
+ * Sets or clears the optional attribution title for this issue.
+ *
+ * <p>This is displayed in the UI and helps to attribute issue cards to a particular source.
+ *
+ * @throws UnsupportedOperationException if accessed from a version lower than {@link
+ * UPSIDE_DOWN_CAKE}
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setAttributionTitle(@Nullable CharSequence attributionTitle) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException(
+ "Method not supported for versions lower than UPSIDE_DOWN_CAKE");
+ }
+ mAttributionTitle = attributionTitle;
+ return this;
+ }
+
+ /**
* Sets {@link IssueSeverityLevel} for this issue. Defaults to {@link
* #ISSUE_SEVERITY_LEVEL_OK}.
*/
@@ -357,6 +446,27 @@ public final class SafetyCenterIssue implements Parcelable {
return this;
}
+ /**
+ * Sets the ID of {@link SafetySourcesGroup} that this issue belongs to. Defaults to a
+ * {@code null} value.
+ *
+ * <p>This ID is used for displaying the issue on its corresponding subpage in the Safety
+ * Center UI.
+ *
+ * @throws UnsupportedOperationException if accessed from a version lower than {@link
+ * UPSIDE_DOWN_CAKE}
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setGroupId(@Nullable String groupId) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException(
+ "Method not supported for versions lower than UPSIDE_DOWN_CAKE");
+ }
+ mGroupId = groupId;
+ return this;
+ }
+
/** Creates the {@link SafetyCenterIssue} defined by this {@link Builder}. */
@NonNull
public SafetyCenterIssue build() {
@@ -368,7 +478,9 @@ public final class SafetyCenterIssue implements Parcelable {
mSeverityLevel,
mDismissible,
mShouldConfirmDismissal,
- unmodifiableList(new ArrayList<>(mActions)));
+ unmodifiableList(new ArrayList<>(mActions)),
+ mAttributionTitle,
+ mGroupId);
}
}
@@ -388,12 +500,19 @@ public final class SafetyCenterIssue implements Parcelable {
String id = in.readString();
CharSequence label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR);
- return new Builder(id, label, pendingIntent)
- .setWillResolve(in.readBoolean())
- .setIsInFlight(in.readBoolean())
- .setSuccessMessage(
- TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in))
- .build();
+ Builder builder =
+ new Builder(id, label, pendingIntent)
+ .setWillResolve(in.readBoolean())
+ .setIsInFlight(in.readBoolean())
+ .setSuccessMessage(
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(
+ in));
+ if (SdkLevel.isAtLeastU()) {
+ ConfirmationDialogDetails confirmationDialogDetails =
+ in.readTypedObject(ConfirmationDialogDetails.CREATOR);
+ builder.setConfirmationDialogDetails(confirmationDialogDetails);
+ }
+ return builder.build();
}
@Override
@@ -408,6 +527,7 @@ public final class SafetyCenterIssue implements Parcelable {
private final boolean mWillResolve;
private final boolean mInFlight;
@Nullable private final CharSequence mSuccessMessage;
+ @Nullable private final ConfirmationDialogDetails mConfirmationDialogDetails;
private Action(
@NonNull String id,
@@ -415,13 +535,15 @@ public final class SafetyCenterIssue implements Parcelable {
@NonNull PendingIntent pendingIntent,
boolean willResolve,
boolean inFlight,
- @Nullable CharSequence successMessage) {
+ @Nullable CharSequence successMessage,
+ @Nullable ConfirmationDialogDetails confirmationDialogDetails) {
mId = id;
mLabel = label;
mPendingIntent = pendingIntent;
mWillResolve = willResolve;
mInFlight = inFlight;
mSuccessMessage = successMessage;
+ mConfirmationDialogDetails = confirmationDialogDetails;
}
/** Returns the ID of this action. */
@@ -469,6 +591,19 @@ public final class SafetyCenterIssue implements Parcelable {
return mSuccessMessage;
}
+ /**
+ * Returns the optional data to be displayed in the confirmation dialog prior to launching
+ * the {@link PendingIntent} when the action is clicked on.
+ */
+ @Nullable
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public ConfirmationDialogDetails getConfirmationDialogDetails() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mConfirmationDialogDetails;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -479,13 +614,21 @@ public final class SafetyCenterIssue implements Parcelable {
&& Objects.equals(mPendingIntent, action.mPendingIntent)
&& mWillResolve == action.mWillResolve
&& mInFlight == action.mInFlight
- && TextUtils.equals(mSuccessMessage, action.mSuccessMessage);
+ && TextUtils.equals(mSuccessMessage, action.mSuccessMessage)
+ && Objects.equals(
+ mConfirmationDialogDetails, action.mConfirmationDialogDetails);
}
@Override
public int hashCode() {
return Objects.hash(
- mId, mLabel, mSuccessMessage, mWillResolve, mInFlight, mPendingIntent);
+ mId,
+ mLabel,
+ mSuccessMessage,
+ mWillResolve,
+ mInFlight,
+ mPendingIntent,
+ mConfirmationDialogDetails);
}
@Override
@@ -503,6 +646,8 @@ public final class SafetyCenterIssue implements Parcelable {
+ mInFlight
+ ", mSuccessMessage="
+ mSuccessMessage
+ + ", mConfirmationDialogDetails="
+ + mConfirmationDialogDetails
+ '}';
}
@@ -519,6 +664,120 @@ public final class SafetyCenterIssue implements Parcelable {
dest.writeBoolean(mWillResolve);
dest.writeBoolean(mInFlight);
TextUtils.writeToParcel(mSuccessMessage, dest, flags);
+ if (SdkLevel.isAtLeastU()) {
+ dest.writeTypedObject(mConfirmationDialogDetails, flags);
+ }
+ }
+
+ /** Data for an action confirmation dialog to be shown before action is executed. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final class ConfirmationDialogDetails implements Parcelable {
+
+ @NonNull
+ public static final Creator<ConfirmationDialogDetails> CREATOR =
+ new Creator<ConfirmationDialogDetails>() {
+ @Override
+ public ConfirmationDialogDetails createFromParcel(Parcel in) {
+ CharSequence title =
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ CharSequence text =
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ CharSequence acceptButtonText =
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ CharSequence denyButtonText =
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ return new ConfirmationDialogDetails(
+ title, text, acceptButtonText, denyButtonText);
+ }
+
+ @Override
+ public ConfirmationDialogDetails[] newArray(int size) {
+ return new ConfirmationDialogDetails[size];
+ }
+ };
+
+ @NonNull private final CharSequence mTitle;
+ @NonNull private final CharSequence mText;
+ @NonNull private final CharSequence mAcceptButtonText;
+ @NonNull private final CharSequence mDenyButtonText;
+
+ public ConfirmationDialogDetails(
+ @NonNull CharSequence title,
+ @NonNull CharSequence text,
+ @NonNull CharSequence acceptButtonText,
+ @NonNull CharSequence denyButtonText) {
+ mTitle = requireNonNull(title);
+ mText = requireNonNull(text);
+ mAcceptButtonText = requireNonNull(acceptButtonText);
+ mDenyButtonText = requireNonNull(denyButtonText);
+ }
+
+ /** Returns the title of action confirmation dialog. */
+ @NonNull
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /** Returns the text of action confirmation dialog. */
+ @NonNull
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /** Returns the text of the button to accept action execution. */
+ @NonNull
+ public CharSequence getAcceptButtonText() {
+ return mAcceptButtonText;
+ }
+
+ /** Returns the text of the button to deny action execution. */
+ @NonNull
+ public CharSequence getDenyButtonText() {
+ return mDenyButtonText;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ TextUtils.writeToParcel(mTitle, dest, flags);
+ TextUtils.writeToParcel(mText, dest, flags);
+ TextUtils.writeToParcel(mAcceptButtonText, dest, flags);
+ TextUtils.writeToParcel(mDenyButtonText, dest, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ConfirmationDialogDetails)) return false;
+ ConfirmationDialogDetails that = (ConfirmationDialogDetails) o;
+ return TextUtils.equals(mTitle, that.mTitle)
+ && TextUtils.equals(mText, that.mText)
+ && TextUtils.equals(mAcceptButtonText, that.mAcceptButtonText)
+ && TextUtils.equals(mDenyButtonText, that.mDenyButtonText);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTitle, mText, mAcceptButtonText, mDenyButtonText);
+ }
+
+ @Override
+ public String toString() {
+ return "ConfirmationDialogDetails{"
+ + "mTitle="
+ + mTitle
+ + ", mText="
+ + mText
+ + ", mAcceptButtonText="
+ + mAcceptButtonText
+ + ", mDenyButtonText="
+ + mDenyButtonText
+ + '}';
+ }
}
/** Builder class for {@link Action}. */
@@ -530,6 +789,7 @@ public final class SafetyCenterIssue implements Parcelable {
private boolean mWillResolve;
private boolean mInFlight;
@Nullable private CharSequence mSuccessMessage;
+ @Nullable private ConfirmationDialogDetails mConfirmationDialogDetails;
/**
* Creates a new {@link Builder} for an {@link Action}.
@@ -547,6 +807,22 @@ public final class SafetyCenterIssue implements Parcelable {
mPendingIntent = requireNonNull(pendingIntent);
}
+ /** Creates a {@link Builder} with the values from the given {@link Action}. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder(@NonNull Action action) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ requireNonNull(action);
+ mId = action.mId;
+ mLabel = action.mLabel;
+ mPendingIntent = action.mPendingIntent;
+ mWillResolve = action.mWillResolve;
+ mInFlight = action.mInFlight;
+ mSuccessMessage = action.mSuccessMessage;
+ mConfirmationDialogDetails = action.mConfirmationDialogDetails;
+ }
+
/** Sets the ID of this {@link Action} */
@NonNull
public Builder setId(@NonNull String id) {
@@ -605,11 +881,32 @@ public final class SafetyCenterIssue implements Parcelable {
return this;
}
+ /**
+ * Sets the optional data to be displayed in the confirmation dialog prior to launching
+ * the {@link PendingIntent} when the action is clicked on.
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setConfirmationDialogDetails(
+ @Nullable ConfirmationDialogDetails confirmationDialogDetails) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mConfirmationDialogDetails = confirmationDialogDetails;
+ return this;
+ }
+
/** Creates the {@link Action} defined by this {@link Builder}. */
@NonNull
public Action build() {
return new Action(
- mId, mLabel, mPendingIntent, mWillResolve, mInFlight, mSuccessMessage);
+ mId,
+ mLabel,
+ mPendingIntent,
+ mWillResolve,
+ mInFlight,
+ mSuccessMessage,
+ mConfirmationDialogDetails);
}
}
}
diff --git a/framework-s/java/android/safetycenter/SafetyCenterManager.java b/framework-s/java/android/safetycenter/SafetyCenterManager.java
index acee6112e..bb67f578f 100644
--- a/framework-s/java/android/safetycenter/SafetyCenterManager.java
+++ b/framework-s/java/android/safetycenter/SafetyCenterManager.java
@@ -21,6 +21,7 @@ import static android.Manifest.permission.READ_SAFETY_CENTER_STATUS;
import static android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE;
import static android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static java.util.Objects.requireNonNull;
@@ -32,6 +33,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
@@ -42,9 +44,11 @@ import android.util.ArrayMap;
import androidx.annotation.RequiresApi;
import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.build.SdkLevel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;
@@ -195,12 +199,21 @@ public final class SafetyCenterManager {
* disambiguate personal profile vs. managed profiles issues).
*
* <p>This extra can be used in conjunction with {@link #EXTRA_SAFETY_SOURCE_ISSUE_ID} and
- * {@link #EXTRA_SAFETY_SOURCE_ID}. Otherwise, no redirection will occur.
+ * {@link #EXTRA_SAFETY_SOURCE_ID}. Otherwise, the device's primary user will be used.
*/
public static final String EXTRA_SAFETY_SOURCE_USER_HANDLE =
"android.safetycenter.extra.SAFETY_SOURCE_USER_HANDLE";
/**
+ * Used as a {@code String} extra field in {@link Intent#ACTION_SAFETY_CENTER} intents to
+ * specify the ID for a group of safety sources. If applicable, this will redirect to the
+ * group's corresponding subpage in the UI.
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final String EXTRA_SAFETY_SOURCES_GROUP_ID =
+ "android.safetycenter.extra.SAFETY_SOURCES_GROUP_ID";
+
+ /**
* Used as an int value for {@link #EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE} to indicate that
* the safety source should fetch fresh data relating to their safety state upon receiving a
* broadcast with intent action {@link #ACTION_REFRESH_SAFETY_SOURCES} and provide it to Safety
@@ -214,7 +227,7 @@ public final class SafetyCenterManager {
/**
* Used as an int value for {@link #EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE} to indicate that
- * upon receiving a broadcasts with intent action {@link #ACTION_REFRESH_SAFETY_SOURCES}, the
+ * upon receiving a broadcast with intent action {@link #ACTION_REFRESH_SAFETY_SOURCES}, the
* safety source should provide data relating to their safety state to Safety Center.
*
* <p>If the source already has its safety data cached, it may provide it without triggering a
@@ -255,6 +268,10 @@ public final class SafetyCenterManager {
/** Indicates a generic reason for Safety Center refresh. */
public static final int REFRESH_REASON_OTHER = 600;
+ /** Indicates a periodic background refresh. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int REFRESH_REASON_PERIODIC = 700;
+
/**
* The reason for requesting a refresh of {@link SafetySourceData} from safety sources.
*
@@ -268,9 +285,11 @@ public final class SafetyCenterManager {
REFRESH_REASON_DEVICE_REBOOT,
REFRESH_REASON_DEVICE_LOCALE_CHANGE,
REFRESH_REASON_SAFETY_CENTER_ENABLED,
- REFRESH_REASON_OTHER
+ REFRESH_REASON_OTHER,
+ REFRESH_REASON_PERIODIC
})
@Retention(RetentionPolicy.SOURCE)
+ @TargetApi(UPSIDE_DOWN_CAKE)
public @interface RefreshReason {}
/** Listener for changes to {@link SafetyCenterData}. */
@@ -429,6 +448,42 @@ public final class SafetyCenterManager {
}
}
+ /**
+ * Requests a specific subset of safety sources to set their latest {@link SafetySourceData} for
+ * Safety Center.
+ *
+ * <p>This API sends a broadcast to safety sources with action {@link
+ * #ACTION_REFRESH_SAFETY_SOURCES} and {@link #EXTRA_REFRESH_SAFETY_SOURCE_IDS} to specify the
+ * IDs of safety sources being requested for data by Safety Center.
+ *
+ * <p>This API is an overload of {@link #refreshSafetySources(int)} and is used to request data
+ * from safety sources that are part of a subpage in the Safety Center UI.
+ *
+ * @see #refreshSafetySources(int)
+ * @param refreshReason the reason for the refresh
+ * @param safetySourceIds list of IDs for the safety sources being refreshed
+ * @throws UnsupportedOperationException if accessed from a version lower than {@link
+ * UPSIDE_DOWN_CAKE}
+ */
+ @RequiresPermission(MANAGE_SAFETY_CENTER)
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public void refreshSafetySources(
+ @RefreshReason int refreshReason, @NonNull List<String> safetySourceIds) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException(
+ "Method not supported for versions lower than UPSIDE_DOWN_CAKE");
+ }
+
+ requireNonNull(safetySourceIds, "safetySourceIds cannot be null");
+
+ try {
+ mService.refreshSpecificSafetySources(
+ refreshReason, mContext.getUser().getIdentifier(), safetySourceIds);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/** Returns the current {@link SafetyCenterConfig}, if available. */
@RequiresPermission(MANAGE_SAFETY_CENTER)
@Nullable
@@ -507,8 +562,7 @@ public final class SafetyCenterManager {
}
/**
- * Dismiss a Safety Center issue and prevent it from appearing in the Safety Center or affecting
- * the overall safety status.
+ * Dismiss a Safety Center issue and prevent it from affecting the overall safety status.
*
* @param safetyCenterIssueId the target issue ID returned by {@link SafetyCenterIssue#getId()}
*/
diff --git a/framework-s/java/android/safetycenter/SafetyEvent.java b/framework-s/java/android/safetycenter/SafetyEvent.java
index 694274f90..72e8defaa 100644
--- a/framework-s/java/android/safetycenter/SafetyEvent.java
+++ b/framework-s/java/android/safetycenter/SafetyEvent.java
@@ -17,6 +17,9 @@
package android.safetycenter;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -27,6 +30,8 @@ import android.os.Parcelable;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -231,6 +236,19 @@ public final class SafetyEvent implements Parcelable {
mType = validateType(type);
}
+ /** Creates a {@link Builder} with the values from the given {@link SafetyEvent}. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder(@NonNull SafetyEvent safetyEvent) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ requireNonNull(safetyEvent);
+ mType = safetyEvent.mType;
+ mRefreshBroadcastId = safetyEvent.mRefreshBroadcastId;
+ mSafetySourceIssueId = safetyEvent.mSafetySourceIssueId;
+ mSafetySourceIssueActionId = safetyEvent.mSafetySourceIssueActionId;
+ }
+
/**
* Sets an optional broadcast id provided by Safety Center when requesting a refresh,
* through {@link SafetyCenterManager#EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID}.
diff --git a/framework-s/java/android/safetycenter/SafetySourceData.java b/framework-s/java/android/safetycenter/SafetySourceData.java
index 25ab63b5d..2e80621a2 100644
--- a/framework-s/java/android/safetycenter/SafetySourceData.java
+++ b/framework-s/java/android/safetycenter/SafetySourceData.java
@@ -17,6 +17,7 @@
package android.safetycenter;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -27,11 +28,14 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -162,6 +166,12 @@ public final class SafetySourceData implements Parcelable {
for (int i = 0; i < issues.size(); i++) {
builder.addIssue(issues.get(i));
}
+ if (SdkLevel.isAtLeastU()) {
+ Bundle extras = in.readBundle(getClass().getClassLoader());
+ if (extras != null) {
+ builder.setExtras(extras);
+ }
+ }
return builder.build();
}
@@ -173,11 +183,15 @@ public final class SafetySourceData implements Parcelable {
@Nullable private final SafetySourceStatus mStatus;
@NonNull private final List<SafetySourceIssue> mIssues;
+ @NonNull private final Bundle mExtras;
private SafetySourceData(
- @Nullable SafetySourceStatus status, @NonNull List<SafetySourceIssue> issues) {
+ @Nullable SafetySourceStatus status,
+ @NonNull List<SafetySourceIssue> issues,
+ @NonNull Bundle extras) {
this.mStatus = status;
this.mIssues = issues;
+ this.mExtras = extras;
}
/** Returns the data for the {@link SafetySourceStatus} to be shown in UI. */
@@ -192,6 +206,21 @@ public final class SafetySourceData implements Parcelable {
return mIssues;
}
+ /**
+ * Returns a {@link Bundle} containing additional information, {@link Bundle#EMPTY} by default.
+ *
+ * <p>Note: internal state of this {@link Bundle} is not used for {@link Object#equals} and
+ * {@link Object#hashCode} implementation of {@link SafetySourceData}.
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Bundle getExtras() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mExtras;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -201,6 +230,9 @@ public final class SafetySourceData implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeTypedObject(mStatus, flags);
dest.writeTypedList(mIssues);
+ if (SdkLevel.isAtLeastU()) {
+ dest.writeBundle(mExtras);
+ }
}
@Override
@@ -218,7 +250,13 @@ public final class SafetySourceData implements Parcelable {
@Override
public String toString() {
- return "SafetySourceData{mStatus=" + mStatus + ", mIssues=" + mIssues + '}';
+ return "SafetySourceData{"
+ + "mStatus="
+ + mStatus
+ + ", mIssues="
+ + mIssues
+ + (!mExtras.isEmpty() ? ", (has extras)" : "")
+ + '}';
}
/** Builder class for {@link SafetySourceData}. */
@@ -227,6 +265,22 @@ public final class SafetySourceData implements Parcelable {
@NonNull private final List<SafetySourceIssue> mIssues = new ArrayList<>();
@Nullable private SafetySourceStatus mStatus;
+ @NonNull private Bundle mExtras = Bundle.EMPTY;
+
+ /** Creates a {@link Builder} for a {@link SafetySourceData}. */
+ public Builder() {}
+
+ /** Creates a {@link Builder} with the values from the given {@link SafetySourceData}. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder(@NonNull SafetySourceData safetySourceData) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ requireNonNull(safetySourceData);
+ mIssues.addAll(safetySourceData.mIssues);
+ mStatus = safetySourceData.mStatus;
+ mExtras = safetySourceData.mExtras.deepCopy();
+ }
/** Sets data for the {@link SafetySourceStatus} to be shown in UI. */
@NonNull
@@ -243,6 +297,35 @@ public final class SafetySourceData implements Parcelable {
}
/**
+ * Sets additional information for the {@link SafetySourceData}.
+ *
+ * If not set, the default value is {@link Bundle#EMPTY}.
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setExtras(@NonNull Bundle extras) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mExtras = requireNonNull(extras);
+ return this;
+ }
+
+ /**
+ * Resets additional information for the {@link SafetySourceData} to the default value of
+ * {@link Bundle#EMPTY}.
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder clearExtras() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mExtras = Bundle.EMPTY;
+ return this;
+ }
+
+ /**
* Clears data for all the {@link SafetySourceIssue}s that were added to this {@link
* Builder}.
*/
@@ -258,7 +341,7 @@ public final class SafetySourceData implements Parcelable {
List<SafetySourceIssue> issues = unmodifiableList(new ArrayList<>(mIssues));
int issuesMaxSeverityLevel = getIssuesMaxSeverityLevelEnforcingUniqueIds(issues);
if (mStatus == null) {
- return new SafetySourceData(null, issues);
+ return new SafetySourceData(null, issues, mExtras);
}
int statusSeverityLevel = mStatus.getSeverityLevel();
boolean requiresAttention = issuesMaxSeverityLevel > SEVERITY_LEVEL_INFORMATION;
@@ -268,7 +351,8 @@ public final class SafetySourceData implements Parcelable {
"Safety source data cannot have issues that are more severe than its"
+ " status");
}
- return new SafetySourceData(mStatus, issues);
+
+ return new SafetySourceData(mStatus, issues, mExtras);
}
private static int getIssuesMaxSeverityLevelEnforcingUniqueIds(
diff --git a/framework-s/java/android/safetycenter/SafetySourceIssue.java b/framework-s/java/android/safetycenter/SafetySourceIssue.java
index cf64818b9..985131764 100644
--- a/framework-s/java/android/safetycenter/SafetySourceIssue.java
+++ b/framework-s/java/android/safetycenter/SafetySourceIssue.java
@@ -17,6 +17,7 @@
package android.safetycenter;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -28,13 +29,17 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TargetApi;
import android.app.PendingIntent;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -64,9 +69,26 @@ public final class SafetySourceIssue implements Parcelable {
/** Indicates that the risk associated with the issue is related to a user's account safety. */
public static final int ISSUE_CATEGORY_ACCOUNT = 200;
- /** Indicates that the risk associated with the issue is related to a user's general safety. */
+ /**
+ * Indicates that the risk associated with the issue is related to a user's general safety.
+ *
+ * <p>This is the default. It is a generic value used when the category is not known or is not
+ * relevant.
+ */
public static final int ISSUE_CATEGORY_GENERAL = 300;
+ /** Indicates that the risk associated with the issue is related to a user's data. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int ISSUE_CATEGORY_DATA = 400;
+
+ /** Indicates that the risk associated with the issue is related to a user's passwords. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int ISSUE_CATEGORY_PASSWORDS = 500;
+
+ /** Indicates that the risk associated with the issue is related to a user's personal safety. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int ISSUE_CATEGORY_PERSONAL_SAFETY = 600;
+
/**
* All possible issue categories.
*
@@ -84,10 +106,108 @@ public final class SafetySourceIssue implements Parcelable {
ISSUE_CATEGORY_DEVICE,
ISSUE_CATEGORY_ACCOUNT,
ISSUE_CATEGORY_GENERAL,
+ ISSUE_CATEGORY_DATA,
+ ISSUE_CATEGORY_PASSWORDS,
+ ISSUE_CATEGORY_PERSONAL_SAFETY
})
@Retention(RetentionPolicy.SOURCE)
+ @TargetApi(UPSIDE_DOWN_CAKE)
public @interface IssueCategory {}
+ /** Value signifying that the source has not specified a particular notification behavior. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int NOTIFICATION_BEHAVIOR_UNSPECIFIED = 0;
+
+ /** An issue which Safety Center should never notify the user about. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int NOTIFICATION_BEHAVIOR_NEVER = 100;
+
+ /**
+ * An issue which Safety Center may notify the user about after a delay if it has not been
+ * resolved. Safety Center does not provide any guarantee about the duration of the delay.
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int NOTIFICATION_BEHAVIOR_DELAYED = 200;
+
+ /** An issue which Safety Center may notify the user about immediately. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int NOTIFICATION_BEHAVIOR_IMMEDIATELY = 300;
+
+ /**
+ * All possible notification behaviors.
+ *
+ * <p>The notification behavior of a {@link SafetySourceIssue} determines if and when Safety
+ * Center should notify the user about it.
+ *
+ * @hide
+ * @see Builder#setNotificationBehavior(int)
+ */
+ @IntDef(
+ prefix = {"NOTIFICATION_BEHAVIOR_"},
+ value = {
+ NOTIFICATION_BEHAVIOR_UNSPECIFIED,
+ NOTIFICATION_BEHAVIOR_NEVER,
+ NOTIFICATION_BEHAVIOR_DELAYED,
+ NOTIFICATION_BEHAVIOR_IMMEDIATELY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @TargetApi(UPSIDE_DOWN_CAKE)
+ public @interface NotificationBehavior {}
+
+ /**
+ * An issue which requires manual user input to be resolved.
+ *
+ * <p>This is the default.
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int ISSUE_ACTIONABILITY_MANUAL = 0;
+
+ /**
+ * An issue which is just a "tip" and may not require any user input.
+ *
+ * <p>It is still possible to provide {@link Action}s to e.g. "learn more" about it or
+ * acknowledge it.
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int ISSUE_ACTIONABILITY_TIP = 100;
+
+ /**
+ * An issue which has already been actioned and may not require any user input.
+ *
+ * <p>It is still possible to provide {@link Action}s to e.g. "learn more" about it or
+ * acknowledge it.
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final int ISSUE_ACTIONABILITY_AUTOMATIC = 200;
+
+ /**
+ * All possible issue actionability.
+ *
+ * <p>An issue's actionability represent what action is expected from the user as a result of
+ * showing them this issue.
+ *
+ * <p>If the user needs to manually resolve it; this is typically achieved using an {@link
+ * Action} (e.g. by resolving the issue directly through the Safety Center screen, or by
+ * navigating to another page).
+ *
+ * <p>If the issue does not need to be resolved manually by the user, it is possible not to
+ * provide any {@link Action}. However, this may still be desirable to e.g. to "learn more"
+ * about it or acknowledge it.
+ *
+ * @hide
+ * @see Builder#setIssueActionability(int)
+ */
+ @IntDef(
+ prefix = {"ISSUE_ACTIONABILITY_"},
+ value = {
+ ISSUE_ACTIONABILITY_MANUAL,
+ ISSUE_ACTIONABILITY_TIP,
+ ISSUE_ACTIONABILITY_AUTOMATIC
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @TargetApi(UPSIDE_DOWN_CAKE)
+ public @interface IssueActionability {}
+
@NonNull
public static final Creator<SafetySourceIssue> CREATOR =
new Creator<SafetySourceIssue>() {
@@ -111,6 +231,14 @@ public final class SafetySourceIssue implements Parcelable {
for (int i = 0; i < actions.size(); i++) {
builder.addAction(actions.get(i));
}
+ if (SdkLevel.isAtLeastU()) {
+ builder.setCustomNotification(in.readTypedObject(Notification.CREATOR));
+ builder.setNotificationBehavior(in.readInt());
+ builder.setAttributionTitle(
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in));
+ builder.setDeduplicationId(in.readString());
+ builder.setIssueActionability(in.readInt());
+ }
return builder.build();
}
@@ -129,6 +257,11 @@ public final class SafetySourceIssue implements Parcelable {
@Nullable private final PendingIntent mOnDismissPendingIntent;
@IssueCategory private final int mIssueCategory;
@NonNull private final String mIssueTypeId;
+ @Nullable private final Notification mCustomNotification;
+ @NotificationBehavior private final int mNotificationBehavior;
+ @Nullable private final CharSequence mAttributionTitle;
+ @Nullable private final String mDeduplicationId;
+ @IssueActionability private final int mIssueActionability;
private SafetySourceIssue(
@NonNull String id,
@@ -139,7 +272,12 @@ public final class SafetySourceIssue implements Parcelable {
@IssueCategory int issueCategory,
@NonNull List<Action> actions,
@Nullable PendingIntent onDismissPendingIntent,
- @NonNull String issueTypeId) {
+ @NonNull String issueTypeId,
+ @Nullable Notification customNotification,
+ @NotificationBehavior int notificationBehavior,
+ @Nullable CharSequence attributionTitle,
+ @Nullable String deduplicationId,
+ @IssueActionability int issueActionability) {
this.mId = id;
this.mTitle = title;
this.mSubtitle = subtitle;
@@ -149,6 +287,11 @@ public final class SafetySourceIssue implements Parcelable {
this.mActions = actions;
this.mOnDismissPendingIntent = onDismissPendingIntent;
this.mIssueTypeId = issueTypeId;
+ this.mCustomNotification = customNotification;
+ this.mNotificationBehavior = notificationBehavior;
+ this.mAttributionTitle = attributionTitle;
+ this.mDeduplicationId = deduplicationId;
+ this.mIssueActionability = issueActionability;
}
/**
@@ -183,6 +326,22 @@ public final class SafetySourceIssue implements Parcelable {
return mSummary;
}
+ /**
+ * Returns the localized attribution title of the issue to be displayed in the UI.
+ *
+ * <p>This is displayed in the UI and helps to attribute issue cards to a particular source. If
+ * this value is {@code null}, the title of the group that contains the Safety Source will be
+ * used.
+ */
+ @Nullable
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public CharSequence getAttributionTitle() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mAttributionTitle;
+ }
+
/** Returns the {@link SafetySourceData.SeverityLevel} of the issue. */
@SafetySourceData.SeverityLevel
public int getSeverityLevel() {
@@ -243,6 +402,114 @@ public final class SafetySourceIssue implements Parcelable {
return mIssueTypeId;
}
+ /**
+ * Returns the optional custom {@link Notification} for this issue which overrides the title,
+ * text and actions for any {@link android.app.Notification} generated for this {@link
+ * SafetySourceIssue}.
+ *
+ * <p>Safety Center may still generate a default notification from the other details of this
+ * issue when no custom notification has been set. See {@link #getNotificationBehavior()} for
+ * details
+ *
+ * @see Builder#setCustomNotification(android.safetycenter.SafetySourceIssue.Notification
+ * @see #getNotificationBehavior()
+ */
+ @Nullable
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Notification getCustomNotification() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mCustomNotification;
+ }
+
+ /**
+ * Returns the {@link NotificationBehavior} for this issue which determines if and when Safety
+ * Center will post a notification for this issue.
+ *
+ * <p>Any notification will be based on the {@link #getCustomNotification()} if set, or the
+ * other properties of this issue otherwise.
+ *
+ * <ul>
+ * <li>If {@link #NOTIFICATION_BEHAVIOR_IMMEDIATELY} then Safety Center will immediately
+ * create and post a notification
+ * <li>If {@link #NOTIFICATION_BEHAVIOR_DELAYED} then a notification will only be posted after
+ * a delay, if this issue has not been resolved.
+ * <li>If {@link #NOTIFICATION_BEHAVIOR_UNSPECIFIED} then a notification may or may not be
+ * posted, the exact behavior is defined by Safety Center.
+ * <li>If {@link #NOTIFICATION_BEHAVIOR_NEVER} Safety Center will never post a notification
+ * about this issue. Sources should specify this behavior when they wish to handle their
+ * own notifications. When this behavior is set sources should not set a custom
+ * notification.
+ * </ul>
+ *
+ * @see Builder#setNotificationBehavior(int)
+ */
+ @NotificationBehavior
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public int getNotificationBehavior() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mNotificationBehavior;
+ }
+
+ /**
+ * Returns the identifier used to deduplicate this issue against other issues with the same
+ * deduplication identifiers.
+ *
+ * <p>Deduplication identifier will be used to identify duplicate issues. This identifier
+ * applies across all safety sources which are part of the same deduplication group.
+ * Deduplication groups can be set, for each source, in the SafetyCenter config. Therefore, two
+ * issues are considered duplicate if their sources are part of the same deduplication group and
+ * they have the same deduplication identifier.
+ *
+ * <p>Out of all issues that are found to be duplicates, only one will be shown in the UI (the
+ * one with the highest severity, or in case of same severities, the one placed highest in the
+ * config).
+ *
+ * <p>Expected usage implies different sources will coordinate to set the same deduplication
+ * identifiers on issues that they want to deduplicate.
+ *
+ * <p>This shouldn't be a default mechanism for deduplication of issues. Most of the time
+ * sources should coordinate or communicate to only send the issue from one of them. That would
+ * also allow sources to choose which one will be displaying the issue, instead of depending on
+ * severity and config order. This API should only be needed if for some reason this isn't
+ * possible, for example, when sources can't communicate with each other and/or send issues at
+ * different times and/or issues can be of different severities.
+ */
+ @Nullable
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public String getDeduplicationId() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mDeduplicationId;
+ }
+
+ /**
+ * Returns the {@link IssueActionability} for this issue which determines what type of action is
+ * required from the user:
+ *
+ * <ul>
+ * <li>If {@link #ISSUE_ACTIONABILITY_MANUAL} then user input is required to resolve the issue
+ * <li>If {@link #ISSUE_ACTIONABILITY_TIP} then the user needs to review this issue as a tip
+ * to improve their overall safety, and possibly acknowledge it
+ * <li>If {@link #ISSUE_ACTIONABILITY_AUTOMATIC} then the user needs to review this issue as
+ * something that has been resolved on their behalf, and possibly acknowledge it
+ * </ul>
+ *
+ * @see Builder#setIssueActionability(int)
+ */
+ @IssueActionability
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public int getIssueActionability() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mIssueActionability;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -259,6 +526,13 @@ public final class SafetySourceIssue implements Parcelable {
dest.writeTypedList(mActions);
dest.writeTypedObject(mOnDismissPendingIntent, flags);
dest.writeString(mIssueTypeId);
+ if (SdkLevel.isAtLeastU()) {
+ dest.writeTypedObject(mCustomNotification, flags);
+ dest.writeInt(mNotificationBehavior);
+ TextUtils.writeToParcel(mAttributionTitle, dest, flags);
+ dest.writeString(mDeduplicationId);
+ dest.writeInt(mIssueActionability);
+ }
}
@Override
@@ -274,7 +548,12 @@ public final class SafetySourceIssue implements Parcelable {
&& mIssueCategory == that.mIssueCategory
&& mActions.equals(that.mActions)
&& Objects.equals(mOnDismissPendingIntent, that.mOnDismissPendingIntent)
- && TextUtils.equals(mIssueTypeId, that.mIssueTypeId);
+ && TextUtils.equals(mIssueTypeId, that.mIssueTypeId)
+ && Objects.equals(mCustomNotification, that.mCustomNotification)
+ && mNotificationBehavior == that.mNotificationBehavior
+ && TextUtils.equals(mAttributionTitle, that.mAttributionTitle)
+ && TextUtils.equals(mDeduplicationId, that.mDeduplicationId)
+ && mIssueActionability == that.mIssueActionability;
}
@Override
@@ -288,7 +567,12 @@ public final class SafetySourceIssue implements Parcelable {
mIssueCategory,
mActions,
mOnDismissPendingIntent,
- mIssueTypeId);
+ mIssueTypeId,
+ mCustomNotification,
+ mNotificationBehavior,
+ mAttributionTitle,
+ mDeduplicationId,
+ mIssueActionability);
}
@Override
@@ -312,6 +596,16 @@ public final class SafetySourceIssue implements Parcelable {
+ mOnDismissPendingIntent
+ ", mIssueTypeId="
+ mIssueTypeId
+ + ", mCustomNotification="
+ + mCustomNotification
+ + ", mNotificationBehavior="
+ + mNotificationBehavior
+ + ", mAttributionTitle="
+ + mAttributionTitle
+ + ", mDeduplicationId="
+ + mDeduplicationId
+ + ", mIssueActionability="
+ + mIssueActionability
+ '}';
}
@@ -336,11 +630,18 @@ public final class SafetySourceIssue implements Parcelable {
String id = in.readString();
CharSequence label = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
PendingIntent pendingIntent = in.readTypedObject(PendingIntent.CREATOR);
- return new Builder(id, label, pendingIntent)
- .setWillResolve(in.readBoolean())
- .setSuccessMessage(
- TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in))
- .build();
+ Builder builder =
+ new Builder(id, label, pendingIntent)
+ .setWillResolve(in.readBoolean())
+ .setSuccessMessage(
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(
+ in));
+ if (SdkLevel.isAtLeastU()) {
+ ConfirmationDialogDetails confirmationDialogDetails =
+ in.readTypedObject(ConfirmationDialogDetails.CREATOR);
+ builder.setConfirmationDialogDetails(confirmationDialogDetails);
+ }
+ return builder.build();
}
@Override
@@ -349,23 +650,38 @@ public final class SafetySourceIssue implements Parcelable {
}
};
+ private static void enforceUniqueActionIds(
+ @NonNull List<SafetySourceIssue.Action> actions, @NonNull String message) {
+ Set<String> actionIds = new HashSet<>();
+ for (int i = 0; i < actions.size(); i++) {
+ SafetySourceIssue.Action action = actions.get(i);
+
+ String actionId = action.getId();
+ checkArgument(!actionIds.contains(actionId), message);
+ actionIds.add(actionId);
+ }
+ }
+
@NonNull private final String mId;
@NonNull private final CharSequence mLabel;
@NonNull private final PendingIntent mPendingIntent;
private final boolean mWillResolve;
@Nullable private final CharSequence mSuccessMessage;
+ @Nullable private final ConfirmationDialogDetails mConfirmationDialogDetails;
private Action(
@NonNull String id,
@NonNull CharSequence label,
@NonNull PendingIntent pendingIntent,
boolean willResolve,
- @Nullable CharSequence successMessage) {
+ @Nullable CharSequence successMessage,
+ @Nullable ConfirmationDialogDetails confirmationDialogDetails) {
mId = id;
mLabel = label;
mPendingIntent = pendingIntent;
mWillResolve = willResolve;
mSuccessMessage = successMessage;
+ mConfirmationDialogDetails = confirmationDialogDetails;
}
/**
@@ -415,6 +731,19 @@ public final class SafetySourceIssue implements Parcelable {
return mSuccessMessage;
}
+ /**
+ * Returns the optional data to be displayed in the confirmation dialog prior to launching
+ * the {@link PendingIntent} when the action is clicked on.
+ */
+ @Nullable
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public ConfirmationDialogDetails getConfirmationDialogDetails() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mConfirmationDialogDetails;
+ }
+
@Override
public int describeContents() {
return 0;
@@ -427,6 +756,9 @@ public final class SafetySourceIssue implements Parcelable {
dest.writeTypedObject(mPendingIntent, flags);
dest.writeBoolean(mWillResolve);
TextUtils.writeToParcel(mSuccessMessage, dest, flags);
+ if (SdkLevel.isAtLeastU()) {
+ dest.writeTypedObject(mConfirmationDialogDetails, flags);
+ }
}
@Override
@@ -438,12 +770,19 @@ public final class SafetySourceIssue implements Parcelable {
&& TextUtils.equals(mLabel, that.mLabel)
&& mPendingIntent.equals(that.mPendingIntent)
&& mWillResolve == that.mWillResolve
- && TextUtils.equals(mSuccessMessage, that.mSuccessMessage);
+ && TextUtils.equals(mSuccessMessage, that.mSuccessMessage)
+ && Objects.equals(mConfirmationDialogDetails, that.mConfirmationDialogDetails);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mLabel, mPendingIntent, mWillResolve, mSuccessMessage);
+ return Objects.hash(
+ mId,
+ mLabel,
+ mPendingIntent,
+ mWillResolve,
+ mSuccessMessage,
+ mConfirmationDialogDetails);
}
@Override
@@ -459,9 +798,122 @@ public final class SafetySourceIssue implements Parcelable {
+ mWillResolve
+ ", mSuccessMessage="
+ mSuccessMessage
+ + ", mConfirmationDialogDetails="
+ + mConfirmationDialogDetails
+ '}';
}
+ /** Data for an action confirmation dialog to be shown before action is executed. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final class ConfirmationDialogDetails implements Parcelable {
+
+ @NonNull
+ public static final Creator<ConfirmationDialogDetails> CREATOR =
+ new Creator<ConfirmationDialogDetails>() {
+ @Override
+ public ConfirmationDialogDetails createFromParcel(Parcel in) {
+ CharSequence title =
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ CharSequence text =
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ CharSequence acceptButtonText =
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ CharSequence denyButtonText =
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ return new ConfirmationDialogDetails(
+ title, text, acceptButtonText, denyButtonText);
+ }
+
+ @Override
+ public ConfirmationDialogDetails[] newArray(int size) {
+ return new ConfirmationDialogDetails[size];
+ }
+ };
+
+ @NonNull private final CharSequence mTitle;
+ @NonNull private final CharSequence mText;
+ @NonNull private final CharSequence mAcceptButtonText;
+ @NonNull private final CharSequence mDenyButtonText;
+
+ public ConfirmationDialogDetails(
+ @NonNull CharSequence title,
+ @NonNull CharSequence text,
+ @NonNull CharSequence acceptButtonText,
+ @NonNull CharSequence denyButtonText) {
+ mTitle = requireNonNull(title);
+ mText = requireNonNull(text);
+ mAcceptButtonText = requireNonNull(acceptButtonText);
+ mDenyButtonText = requireNonNull(denyButtonText);
+ }
+
+ /** Returns the title of action confirmation dialog. */
+ @NonNull
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /** Returns the text of action confirmation dialog. */
+ @NonNull
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /** Returns the text of the button to accept action execution. */
+ @NonNull
+ public CharSequence getAcceptButtonText() {
+ return mAcceptButtonText;
+ }
+
+ /** Returns the text of the button to deny action execution. */
+ @NonNull
+ public CharSequence getDenyButtonText() {
+ return mDenyButtonText;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ TextUtils.writeToParcel(mTitle, dest, flags);
+ TextUtils.writeToParcel(mText, dest, flags);
+ TextUtils.writeToParcel(mAcceptButtonText, dest, flags);
+ TextUtils.writeToParcel(mDenyButtonText, dest, flags);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ConfirmationDialogDetails)) return false;
+ ConfirmationDialogDetails that = (ConfirmationDialogDetails) o;
+ return TextUtils.equals(mTitle, that.mTitle)
+ && TextUtils.equals(mText, that.mText)
+ && TextUtils.equals(mAcceptButtonText, that.mAcceptButtonText)
+ && TextUtils.equals(mDenyButtonText, that.mDenyButtonText);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTitle, mText, mAcceptButtonText, mDenyButtonText);
+ }
+
+ @Override
+ public String toString() {
+ return "ConfirmationDialogDetails{"
+ + "mTitle="
+ + mTitle
+ + ", mText="
+ + mText
+ + ", mAcceptButtonText="
+ + mAcceptButtonText
+ + ", mDenyButtonText="
+ + mDenyButtonText
+ + '}';
+ }
+ }
+
/** Builder class for {@link Action}. */
public static final class Builder {
@@ -470,6 +922,7 @@ public final class SafetySourceIssue implements Parcelable {
@NonNull private final PendingIntent mPendingIntent;
private boolean mWillResolve = false;
@Nullable private CharSequence mSuccessMessage;
+ @Nullable private ConfirmationDialogDetails mConfirmationDialogDetails;
/** Creates a {@link Builder} for an {@link Action}. */
public Builder(
@@ -481,9 +934,28 @@ public final class SafetySourceIssue implements Parcelable {
mPendingIntent = requireNonNull(pendingIntent);
}
+ /** Creates a {@link Builder} with the values from the given {@link Action}. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder(@NonNull Action action) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ requireNonNull(action);
+ mId = action.mId;
+ mLabel = action.mLabel;
+ mPendingIntent = action.mPendingIntent;
+ mWillResolve = action.mWillResolve;
+ mSuccessMessage = action.mSuccessMessage;
+ mConfirmationDialogDetails = action.mConfirmationDialogDetails;
+ }
+
/**
* Sets whether the action will resolve the safety issue. Defaults to {@code false}.
*
+ * <p>Note: It is not allowed for resolvable actions to have a {@link PendingIntent}
+ * that launches activity. When extra confirmation is needed consider using {@link
+ * Builder#setConfirmationDialogDetails}.
+ *
* @see #willResolve()
*/
@SuppressLint("MissingGetterMatchingBuilder")
@@ -503,10 +975,212 @@ public final class SafetySourceIssue implements Parcelable {
return this;
}
+ /**
+ * Sets the optional data to be displayed in the confirmation dialog prior to launching
+ * the {@link PendingIntent} when the action is clicked on.
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setConfirmationDialogDetails(
+ @Nullable ConfirmationDialogDetails confirmationDialogDetails) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mConfirmationDialogDetails = confirmationDialogDetails;
+ return this;
+ }
+
/** Creates the {@link Action} defined by this {@link Builder}. */
@NonNull
public Action build() {
- return new Action(mId, mLabel, mPendingIntent, mWillResolve, mSuccessMessage);
+ if (SdkLevel.isAtLeastU()) {
+ boolean willResolveWithActivity = mWillResolve && mPendingIntent.isActivity();
+ checkArgument(
+ !willResolveWithActivity,
+ "Launching activity from Action that should resolve the"
+ + " SafetySourceIssue is not allowed. Consider using setting a"
+ + " Confirmation if needed, and either set the willResolve to"
+ + " false or make PendingIntent to start a service/send a"
+ + " broadcast.");
+ }
+ return new Action(
+ mId,
+ mLabel,
+ mPendingIntent,
+ mWillResolve,
+ mSuccessMessage,
+ mConfirmationDialogDetails);
+ }
+ }
+ }
+
+ /**
+ * Data for Safety Center to use when constructing a system {@link android.app.Notification}
+ * about a related {@link SafetySourceIssue}.
+ *
+ * <p>Safety Center can construct a default notification for any issue, but sources may use
+ * {@link Builder#setCustomNotification(android.safetycenter.SafetySourceIssue.Notification)} if
+ * they want to override the title, text or actions.
+ *
+ * @see #getCustomNotification()
+ * @see Builder#setCustomNotification(android.safetycenter.SafetySourceIssue.Notification)
+ * @see #getNotificationBehavior()
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final class Notification implements Parcelable {
+
+ @NonNull
+ public static final Creator<Notification> CREATOR =
+ new Creator<Notification>() {
+ @Override
+ public Notification createFromParcel(Parcel in) {
+ return new Builder(
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in),
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in))
+ .addActions(in.createTypedArrayList(Action.CREATOR))
+ .build();
+ }
+
+ @Override
+ public Notification[] newArray(int size) {
+ return new Notification[size];
+ }
+ };
+
+ @NonNull private final CharSequence mTitle;
+ @NonNull private final CharSequence mText;
+ @NonNull private final List<Action> mActions;
+
+ private Notification(
+ @NonNull CharSequence title,
+ @NonNull CharSequence text,
+ @NonNull List<Action> actions) {
+ mTitle = title;
+ mText = text;
+ mActions = actions;
+ }
+
+ /**
+ * Custom title which will be used instead of {@link SafetySourceIssue#getTitle()} when
+ * building a {@link android.app.Notification} for this issue.
+ */
+ @NonNull
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Custom text which will be used instead of {@link SafetySourceIssue#getSummary()} when
+ * building a {@link android.app.Notification} for this issue.
+ */
+ @NonNull
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Custom list of {@link Action} instances which will be used instead of {@link
+ * SafetySourceIssue#getActions()} when building a {@link android.app.Notification} for this
+ * issue.
+ *
+ * <p>If this list is empty then the resulting {@link android.app.Notification} will have
+ * zero action buttons.
+ */
+ @NonNull
+ public List<Action> getActions() {
+ return mActions;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ TextUtils.writeToParcel(mTitle, dest, flags);
+ TextUtils.writeToParcel(mText, dest, flags);
+ dest.writeTypedList(mActions);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Notification)) return false;
+ Notification that = (Notification) o;
+ return TextUtils.equals(mTitle, that.mTitle)
+ && TextUtils.equals(mText, that.mText)
+ && mActions.equals(that.mActions);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTitle, mText, mActions);
+ }
+
+ @Override
+ public String toString() {
+ return "Notification{"
+ + "mTitle="
+ + mTitle
+ + ", mText="
+ + mText
+ + ", mActions="
+ + mActions
+ + '}';
+ }
+
+ /** Builder for {@link SafetySourceIssue.Notification}. */
+ public static final class Builder {
+
+ @NonNull private final CharSequence mTitle;
+ @NonNull private final CharSequence mText;
+ @NonNull private final List<Action> mActions = new ArrayList<>();
+
+ public Builder(@NonNull CharSequence title, @NonNull CharSequence text) {
+ mTitle = requireNonNull(title);
+ mText = requireNonNull(text);
+ }
+
+ /** Creates a {@link Builder} with the values from the given {@link Notification}. */
+ public Builder(@NonNull Notification notification) {
+ requireNonNull(notification);
+ mTitle = notification.mTitle;
+ mText = notification.mText;
+ mActions.addAll(notification.mActions);
+ }
+
+ /** Adds an {@link Action} to the custom {@link Notification}. */
+ @NonNull
+ public Builder addAction(@NonNull Action action) {
+ mActions.add(requireNonNull(action));
+ return this;
+ }
+
+ /** Adds several {@link Action}s to the custom {@link Notification}. */
+ @NonNull
+ public Builder addActions(@NonNull List<Action> actions) {
+ mActions.addAll(requireNonNull(actions));
+ return this;
+ }
+
+ /** Clears all the {@link Action}s that were added so far. */
+ @NonNull
+ public Builder clearActions() {
+ mActions.clear();
+ return this;
+ }
+
+ /** Builds a {@link Notification} instance. */
+ @NonNull
+ public Notification build() {
+ List<Action> actions = unmodifiableList(new ArrayList<>(mActions));
+ Action.enforceUniqueActionIds(
+ actions, "Custom notification cannot have duplicate action ids");
+ checkArgument(
+ actions.size() <= 2,
+ "Custom notification must not contain more than 2 actions");
+ return new Notification(mTitle, mText, actions);
}
}
}
@@ -524,6 +1198,18 @@ public final class SafetySourceIssue implements Parcelable {
@Nullable private CharSequence mSubtitle;
@IssueCategory private int mIssueCategory = ISSUE_CATEGORY_GENERAL;
@Nullable private PendingIntent mOnDismissPendingIntent;
+ @Nullable private CharSequence mAttributionTitle;
+ @Nullable private String mDeduplicationId;
+
+ @Nullable private Notification mCustomNotification = null;
+
+ @SuppressLint("NewApi")
+ @NotificationBehavior
+ private int mNotificationBehavior = NOTIFICATION_BEHAVIOR_UNSPECIFIED;
+
+ @SuppressLint("NewApi")
+ @IssueActionability
+ private int mIssueActionability = ISSUE_ACTIONABILITY_MANUAL;
/** Creates a {@link Builder} for a {@link SafetySourceIssue}. */
public Builder(
@@ -539,6 +1225,29 @@ public final class SafetySourceIssue implements Parcelable {
this.mIssueTypeId = requireNonNull(issueTypeId);
}
+ /** Creates a {@link Builder} with the values from the given {@link SafetySourceIssue}. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder(@NonNull SafetySourceIssue safetySourceIssue) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ requireNonNull(safetySourceIssue);
+ mId = safetySourceIssue.mId;
+ mTitle = safetySourceIssue.mTitle;
+ mSummary = safetySourceIssue.mSummary;
+ mSeverityLevel = safetySourceIssue.mSeverityLevel;
+ mIssueTypeId = safetySourceIssue.mIssueTypeId;
+ mActions.addAll(safetySourceIssue.mActions);
+ mSubtitle = safetySourceIssue.mSubtitle;
+ mIssueCategory = safetySourceIssue.mIssueCategory;
+ mOnDismissPendingIntent = safetySourceIssue.mOnDismissPendingIntent;
+ mAttributionTitle = safetySourceIssue.mAttributionTitle;
+ mDeduplicationId = safetySourceIssue.mDeduplicationId;
+ mCustomNotification = safetySourceIssue.mCustomNotification;
+ mNotificationBehavior = safetySourceIssue.mNotificationBehavior;
+ mIssueActionability = safetySourceIssue.mIssueActionability;
+ }
+
/** Sets the localized subtitle. */
@NonNull
public Builder setSubtitle(@Nullable CharSequence subtitle) {
@@ -547,6 +1256,23 @@ public final class SafetySourceIssue implements Parcelable {
}
/**
+ * Sets or clears the optional attribution title for this issue.
+ *
+ * <p>This is displayed in the UI and helps to attribute an issue to a particular source. If
+ * this value is {@code null}, the title of the group that contains the Safety Source will
+ * be used.
+ */
+ @NonNull
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public Builder setAttributionTitle(@Nullable CharSequence attributionTitle) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mAttributionTitle = attributionTitle;
+ return this;
+ }
+
+ /**
* Sets the category of the risk associated with the issue.
*
* <p>The default category will be {@link #ISSUE_CATEGORY_GENERAL}.
@@ -590,12 +1316,98 @@ public final class SafetySourceIssue implements Parcelable {
return this;
}
+ /**
+ * Sets a custom {@link Notification} for this issue.
+ *
+ * <p>Using a custom {@link Notification} a source may specify a different {@link
+ * Notification#getTitle()}, {@link Notification#getText()} and {@link
+ * Notification#getActions()} for Safety Center to use when constructing a notification for
+ * this issue.
+ *
+ * <p>Safety Center may still generate a default notification from the other details of this
+ * issue when no custom notification has been set, depending on the issue's {@link
+ * #getNotificationBehavior()}.
+ *
+ * @see #getCustomNotification()
+ * @see #setNotificationBehavior(int)
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setCustomNotification(@Nullable Notification customNotification) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mCustomNotification = customNotification;
+ return this;
+ }
+
+ /**
+ * Sets the notification behavior of the issue.
+ *
+ * <p>Must be one of {@link #NOTIFICATION_BEHAVIOR_UNSPECIFIED}, {@link
+ * #NOTIFICATION_BEHAVIOR_NEVER}, {@link #NOTIFICATION_BEHAVIOR_DELAYED} or {@link
+ * #NOTIFICATION_BEHAVIOR_IMMEDIATELY}. See {@link #getNotificationBehavior()} for details
+ * of how Safety Center will interpret each of these.
+ *
+ * @see #getNotificationBehavior()
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setNotificationBehavior(@NotificationBehavior int notificationBehavior) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mNotificationBehavior = validateNotificationBehavior(notificationBehavior);
+ return this;
+ }
+
+ /**
+ * Sets the deduplication identifier for the issue.
+ *
+ * @see #getDeduplicationId()
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setDeduplicationId(@Nullable String deduplicationId) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mDeduplicationId = deduplicationId;
+ return this;
+ }
+
+ /**
+ * Sets the issue actionability of the issue.
+ *
+ * <p>Must be one of {@link #ISSUE_ACTIONABILITY_MANUAL} (default), {@link
+ * #ISSUE_ACTIONABILITY_TIP}, {@link #ISSUE_ACTIONABILITY_AUTOMATIC}.
+ *
+ * @see #getIssueActionability()
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setIssueActionability(@IssueActionability int issueActionability) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mIssueActionability = validateIssueActionability(issueActionability);
+ return this;
+ }
+
/** Creates the {@link SafetySourceIssue} defined by this {@link Builder}. */
@NonNull
public SafetySourceIssue build() {
List<SafetySourceIssue.Action> actions = unmodifiableList(new ArrayList<>(mActions));
- enforceUniqueActionIds(actions);
- checkArgument(!actions.isEmpty(), "Safety source issue must contain at least 1 action");
+ Action.enforceUniqueActionIds(
+ actions, "Safety source issue cannot have duplicate action ids");
+ if (SdkLevel.isAtLeastU()) {
+ checkArgument(
+ mIssueActionability != ISSUE_ACTIONABILITY_MANUAL || !actions.isEmpty(),
+ "Actionable safety source issue must contain at least 1 action");
+ } else {
+ checkArgument(
+ !actions.isEmpty(), "Safety source issue must contain at least 1 action");
+ }
checkArgument(
actions.size() <= 2,
"Safety source issue must not contain more than 2 actions");
@@ -608,21 +1420,12 @@ public final class SafetySourceIssue implements Parcelable {
mIssueCategory,
actions,
mOnDismissPendingIntent,
- mIssueTypeId);
- }
-
- private static void enforceUniqueActionIds(
- @NonNull List<SafetySourceIssue.Action> actions) {
- Set<String> actionIds = new HashSet<>();
- for (int i = 0; i < actions.size(); i++) {
- SafetySourceIssue.Action action = actions.get(i);
-
- String actionId = action.getId();
- checkArgument(
- !actionIds.contains(actionId),
- "Safety source issue cannot have duplicate action ids");
- actionIds.add(actionId);
- }
+ mIssueTypeId,
+ mCustomNotification,
+ mNotificationBehavior,
+ mAttributionTitle,
+ mDeduplicationId,
+ mIssueActionability);
}
}
@@ -652,7 +1455,43 @@ public final class SafetySourceIssue implements Parcelable {
return value;
default:
}
+ if (SdkLevel.isAtLeastU()) {
+ switch (value) {
+ case ISSUE_CATEGORY_DATA:
+ case ISSUE_CATEGORY_PASSWORDS:
+ case ISSUE_CATEGORY_PERSONAL_SAFETY:
+ return value;
+ default:
+ }
+ }
throw new IllegalArgumentException(
"Unexpected IssueCategory for SafetySourceIssue: " + value);
}
+
+ @NotificationBehavior
+ private static int validateNotificationBehavior(int value) {
+ switch (value) {
+ case NOTIFICATION_BEHAVIOR_UNSPECIFIED:
+ case NOTIFICATION_BEHAVIOR_NEVER:
+ case NOTIFICATION_BEHAVIOR_DELAYED:
+ case NOTIFICATION_BEHAVIOR_IMMEDIATELY:
+ return value;
+ default:
+ }
+ throw new IllegalArgumentException(
+ "Unexpected NotificationBehavior for SafetySourceIssue: " + value);
+ }
+
+ @IssueActionability
+ private static int validateIssueActionability(int value) {
+ switch (value) {
+ case ISSUE_ACTIONABILITY_MANUAL:
+ case ISSUE_ACTIONABILITY_TIP:
+ case ISSUE_ACTIONABILITY_AUTOMATIC:
+ return value;
+ default:
+ }
+ throw new IllegalArgumentException(
+ "Unexpected IssueActionability for SafetySourceIssue: " + value);
+ }
}
diff --git a/framework-s/java/android/safetycenter/SafetySourceStatus.java b/framework-s/java/android/safetycenter/SafetySourceStatus.java
index b80fd39b5..37095eb59 100644
--- a/framework-s/java/android/safetycenter/SafetySourceStatus.java
+++ b/framework-s/java/android/safetycenter/SafetySourceStatus.java
@@ -17,6 +17,7 @@
package android.safetycenter;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static com.android.internal.util.Preconditions.checkArgument;
@@ -33,6 +34,8 @@ import android.text.TextUtils;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
@@ -341,6 +344,21 @@ public final class SafetySourceStatus implements Parcelable {
this.mSeverityLevel = validateSeverityLevel(severityLevel);
}
+ /** Creates a {@link Builder} with the values of the given {@link SafetySourceStatus}. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder(@NonNull SafetySourceStatus safetySourceStatus) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ requireNonNull(safetySourceStatus);
+ mTitle = safetySourceStatus.mTitle;
+ mSummary = safetySourceStatus.mSummary;
+ mSeverityLevel = safetySourceStatus.mSeverityLevel;
+ mPendingIntent = safetySourceStatus.mPendingIntent;
+ mIconAction = safetySourceStatus.mIconAction;
+ mEnabled = safetySourceStatus.mEnabled;
+ }
+
/**
* Sets an optional {@link PendingIntent} for the safety source status.
*
diff --git a/framework-s/java/android/safetycenter/TEST_MAPPING b/framework-s/java/android/safetycenter/TEST_MAPPING
index e2eb5ef5d..c702ee852 100644
--- a/framework-s/java/android/safetycenter/TEST_MAPPING
+++ b/framework-s/java/android/safetycenter/TEST_MAPPING
@@ -24,5 +24,15 @@
{
"name": "SafetyCenterFunctionalTestCases"
}
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsSafetyCenterTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
]
}
diff --git a/framework-s/java/android/safetycenter/config/BuilderUtils.java b/framework-s/java/android/safetycenter/config/BuilderUtils.java
index 9e3038245..b6ccced62 100644
--- a/framework-s/java/android/safetycenter/config/BuilderUtils.java
+++ b/framework-s/java/android/safetycenter/config/BuilderUtils.java
@@ -25,6 +25,7 @@ import android.content.res.Resources;
import androidx.annotation.RequiresApi;
+import java.util.Collection;
import java.util.Objects;
import java.util.regex.Pattern;
@@ -40,12 +41,12 @@ final class BuilderUtils {
boolean prohibited,
@Nullable Object defaultValue) {
if (attribute == null && required) {
- throw new IllegalStateException("Required attribute " + name + " missing");
+ throwRequiredAttributeMissing(name);
}
boolean nonDefaultValueProvided = !Objects.equals(attribute, defaultValue);
boolean checkProhibited = prohibited && nonDefaultValueProvided;
if (attribute != null && checkProhibited) {
- throw new IllegalStateException("Prohibited attribute " + name + " present");
+ throwProhibitedAttributePresent(name);
}
}
@@ -79,7 +80,7 @@ final class BuilderUtils {
return Resources.ID_NULL;
}
if (required && value == Resources.ID_NULL) {
- throw new IllegalStateException("Required attribute " + name + " invalid");
+ throwRequiredAttributeInvalid(name);
}
return value;
}
@@ -131,4 +132,37 @@ final class BuilderUtils {
}
return value;
}
+
+ /**
+ * Validates a collection argument from a builder.
+ *
+ * <ul>
+ * <li>If {@code required}, a non-empty collection must be supplied.
+ * <li>If {@code prohibited}, an empty collection must be supplied.
+ * </ul>
+ */
+ static <T> void validateCollection(
+ @NonNull Collection<T> value,
+ @NonNull String name,
+ boolean required,
+ boolean prohibited) {
+ if (value.isEmpty() && required) {
+ throwRequiredAttributeMissing(name);
+ }
+ if (!value.isEmpty() && prohibited) {
+ throwProhibitedAttributePresent(name);
+ }
+ }
+
+ static void throwRequiredAttributeMissing(@NonNull String attribute) {
+ throw new IllegalStateException("Required attribute " + attribute + " missing");
+ }
+
+ static void throwProhibitedAttributePresent(@NonNull String attribute) {
+ throw new IllegalStateException("Prohibited attribute " + attribute + " present");
+ }
+
+ static void throwRequiredAttributeInvalid(@NonNull String attribute) {
+ throw new IllegalStateException("Required attribute " + attribute + " invalid");
+ }
}
diff --git a/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java b/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java
index 9c8691e44..c94cedbd6 100644
--- a/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java
+++ b/framework-s/java/android/safetycenter/config/SafetyCenterConfig.java
@@ -17,6 +17,7 @@
package android.safetycenter.config;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
@@ -28,6 +29,8 @@ import android.os.Parcelable;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -115,6 +118,16 @@ public final class SafetyCenterConfig implements Parcelable {
/** Creates a {@link Builder} for a {@link SafetyCenterConfig}. */
public Builder() {}
+ /** Creates a {@link Builder} with the values from the given {@link SafetyCenterConfig}. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder(@NonNull SafetyCenterConfig safetyCenterConfig) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ requireNonNull(safetyCenterConfig);
+ mSafetySourcesGroups.addAll(safetyCenterConfig.mSafetySourcesGroups);
+ }
+
/**
* Adds a {@link SafetySourcesGroup} to the Safety Center configuration.
*
@@ -129,8 +142,8 @@ public final class SafetyCenterConfig implements Parcelable {
/**
* Creates the {@link SafetyCenterConfig} defined by this {@link Builder}.
*
- * <p>Throws an {@link IllegalStateException} if any constraint on the Safety Center
- * configuration is violated.
+ * @throws IllegalStateException if any constraint on the Safety Center configuration is
+ * violated
*/
@NonNull
public SafetyCenterConfig build() {
diff --git a/framework-s/java/android/safetycenter/config/SafetySource.java b/framework-s/java/android/safetycenter/config/SafetySource.java
index 90f348ee2..fddb8b622 100644
--- a/framework-s/java/android/safetycenter/config/SafetySource.java
+++ b/framework-s/java/android/safetycenter/config/SafetySource.java
@@ -17,6 +17,9 @@
package android.safetycenter.config;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -26,12 +29,17 @@ import android.annotation.SystemApi;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArraySet;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Data class used to represent the initial configuration of a safety source.
@@ -152,20 +160,29 @@ public final class SafetySource implements Parcelable {
@Override
public SafetySource createFromParcel(Parcel in) {
int type = in.readInt();
- return new Builder(type)
- .setId(in.readString())
- .setPackageName(in.readString())
- .setTitleResId(in.readInt())
- .setTitleForWorkResId(in.readInt())
- .setSummaryResId(in.readInt())
- .setIntentAction(in.readString())
- .setProfile(in.readInt())
- .setInitialDisplayState(in.readInt())
- .setMaxSeverityLevel(in.readInt())
- .setSearchTermsResId(in.readInt())
- .setLoggingAllowed(in.readBoolean())
- .setRefreshOnPageOpenAllowed(in.readBoolean())
- .build();
+ Builder builder =
+ new Builder(type)
+ .setId(in.readString())
+ .setPackageName(in.readString())
+ .setTitleResId(in.readInt())
+ .setTitleForWorkResId(in.readInt())
+ .setSummaryResId(in.readInt())
+ .setIntentAction(in.readString())
+ .setProfile(in.readInt())
+ .setInitialDisplayState(in.readInt())
+ .setMaxSeverityLevel(in.readInt())
+ .setSearchTermsResId(in.readInt())
+ .setLoggingAllowed(in.readBoolean())
+ .setRefreshOnPageOpenAllowed(in.readBoolean());
+ if (SdkLevel.isAtLeastU()) {
+ builder.setNotificationsAllowed(in.readBoolean());
+ builder.setDeduplicationGroup(in.readString());
+ List<String> certs = in.createStringArrayList();
+ for (int i = 0; i < certs.size(); i++) {
+ builder.addPackageCertificateHash(certs.get(i));
+ }
+ }
+ return builder.build();
}
@Override
@@ -187,6 +204,9 @@ public final class SafetySource implements Parcelable {
@StringRes private final int mSearchTermsResId;
private final boolean mLoggingAllowed;
private final boolean mRefreshOnPageOpenAllowed;
+ private final boolean mNotificationsAllowed;
+ @Nullable final String mDeduplicationGroup;
+ @NonNull private final Set<String> mPackageCertificateHashes;
private SafetySource(
@SafetySourceType int type,
@@ -201,7 +221,10 @@ public final class SafetySource implements Parcelable {
int maxSeverityLevel,
@StringRes int searchTermsResId,
boolean loggingAllowed,
- boolean refreshOnPageOpenAllowed) {
+ boolean refreshOnPageOpenAllowed,
+ boolean notificationsAllowed,
+ @Nullable String deduplicationGroup,
+ @NonNull Set<String> packageCertificateHashes) {
mType = type;
mId = id;
mPackageName = packageName;
@@ -215,6 +238,9 @@ public final class SafetySource implements Parcelable {
mSearchTermsResId = searchTermsResId;
mLoggingAllowed = loggingAllowed;
mRefreshOnPageOpenAllowed = refreshOnPageOpenAllowed;
+ mNotificationsAllowed = notificationsAllowed;
+ mDeduplicationGroup = deduplicationGroup;
+ mPackageCertificateHashes = Set.copyOf(packageCertificateHashes);
}
/** Returns the type of this safety source. */
@@ -236,10 +262,14 @@ public final class SafetySource implements Parcelable {
/**
* Returns the package name of this safety source.
*
- * <p>This is the package that owns the source. The package will receive refresh requests and it
- * can send set requests for the source.
+ * <p>This is the package that owns the source. The package will receive refresh requests, and
+ * it can send set requests for the source. The package is also used to create an explicit
+ * pending intent from the intent action in the package context.
*
- * <p>Throws an {@link UnsupportedOperationException} if the source is of type static.
+ * @throws UnsupportedOperationException if the source is of type {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_STATIC} even if the optional package name field for the
+ * source is set, for sources of type {@link SafetySource#SAFETY_SOURCE_TYPE_STATIC} use
+ * {@link SafetySource#getOptionalPackageName()}
*/
@NonNull
public String getPackageName() {
@@ -251,13 +281,36 @@ public final class SafetySource implements Parcelable {
}
/**
+ * Returns the package name of this safety source or null if undefined.
+ *
+ * <p>This is the package that owns the source.
+ *
+ * <p>The package is always defined for sources of type dynamic and issue-only. The package will
+ * receive refresh requests, and it can send set requests for sources of type dynamic and
+ * issue-only. The package is also used to create an explicit pending intent in the package
+ * context from the intent action if defined.
+ *
+ * <p>The package is optional for sources of type static. If present, the package is used to
+ * create an explicit pending intent in the package context from the intent action.
+ */
+ @Nullable
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public String getOptionalPackageName() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mPackageName;
+ }
+
+ /**
* Returns the resource id of the title of this safety source.
*
* <p>The id refers to a string resource that is either accessible from any resource context or
* that is accessible from the same resource context that was used to load the Safety Center
* configuration. The id is {@link Resources#ID_NULL} when a title is not provided.
*
- * <p>Throws an {@link UnsupportedOperationException} if the source is of type issue-only.
+ * @throws UnsupportedOperationException if the source is of type {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_ISSUE_ONLY}
*/
@StringRes
public int getTitleResId() {
@@ -275,8 +328,9 @@ public final class SafetySource implements Parcelable {
* that is accessible from the same resource context that was used to load the Safety Center
* configuration. The id is {@link Resources#ID_NULL} when a title for work is not provided.
*
- * <p>Throws an {@link UnsupportedOperationException} if the source is of type issue-only or if
- * the profile property of the source is set to {@link SafetySource#PROFILE_PRIMARY}.
+ * @throws UnsupportedOperationException if the source is of type {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_ISSUE_ONLY} or if the profile property of the source is
+ * set to {@link SafetySource#PROFILE_PRIMARY}
*/
@StringRes
public int getTitleForWorkResId() {
@@ -298,7 +352,8 @@ public final class SafetySource implements Parcelable {
* that is accessible from the same resource context that was used to load the Safety Center
* configuration. The id is {@link Resources#ID_NULL} when a summary is not provided.
*
- * <p>Throws an {@link UnsupportedOperationException} if the source is of type issue-only.
+ * @throws UnsupportedOperationException if the source is of type {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_ISSUE_ONLY}
*/
@StringRes
public int getSummaryResId() {
@@ -316,7 +371,8 @@ public final class SafetySource implements Parcelable {
* source is displayed as an entry in the Safety Center page, and if the action is set to {@code
* null} or if it does not resolve to an activity the source will be marked as disabled.
*
- * <p>Throws an {@link UnsupportedOperationException} if the source is of type issue-only.
+ * @throws UnsupportedOperationException if the source is of type {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_ISSUE_ONLY}
*/
@Nullable
public String getIntentAction() {
@@ -336,8 +392,9 @@ public final class SafetySource implements Parcelable {
/**
* Returns the initial display state of this safety source.
*
- * <p>Throws an {@link UnsupportedOperationException} if the source is of type static or
- * issue-only.
+ * @throws UnsupportedOperationException if the source is of type {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_STATIC} or {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_ISSUE_ONLY}
*/
@InitialDisplayState
public int getInitialDisplayState() {
@@ -361,7 +418,8 @@ public final class SafetySource implements Parcelable {
* android.safetycenter.SafetySourceData#SEVERITY_LEVEL_INFORMATION} even if the maximum
* severity level is set to a lower value.
*
- * <p>Throws an {@link UnsupportedOperationException} if the source is of type static.
+ * @throws UnsupportedOperationException if the source is of type {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_STATIC}
*/
public int getMaxSeverityLevel() {
if (mType == SAFETY_SOURCE_TYPE_STATIC) {
@@ -378,7 +436,8 @@ public final class SafetySource implements Parcelable {
* that is accessible from the same resource context that was used to load the Safety Center
* configuration. The id is {@link Resources#ID_NULL} when search terms are not provided.
*
- * <p>Throws an {@link UnsupportedOperationException} if the source is of type issue-only.
+ * @throws UnsupportedOperationException if the source is of type {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_ISSUE_ONLY}
*/
@StringRes
public int getSearchTermsResId() {
@@ -392,7 +451,8 @@ public final class SafetySource implements Parcelable {
/**
* Returns the logging allowed property of this safety source.
*
- * <p>Throws an {@link UnsupportedOperationException} if the source is of type static.
+ * @throws UnsupportedOperationException if the source is of type {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_STATIC}
*/
public boolean isLoggingAllowed() {
if (mType == SAFETY_SOURCE_TYPE_STATIC) {
@@ -408,7 +468,8 @@ public final class SafetySource implements Parcelable {
* <p>If set to {@code true}, a refresh request will be sent to the source when the Safety
* Center page is opened.
*
- * <p>Throws an {@link UnsupportedOperationException} if the source is of type static.
+ * @throws UnsupportedOperationException if the source is of type {@link
+ * SafetySource#SAFETY_SOURCE_TYPE_STATIC}
*/
public boolean isRefreshOnPageOpenAllowed() {
if (mType == SAFETY_SOURCE_TYPE_STATIC) {
@@ -418,6 +479,57 @@ public final class SafetySource implements Parcelable {
return mRefreshOnPageOpenAllowed;
}
+ /**
+ * Returns whether Safety Center may post Notifications about issues reported by this {@link
+ * SafetySource}.
+ *
+ * @see Builder#setNotificationsAllowed(boolean)
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public boolean areNotificationsAllowed() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mNotificationsAllowed;
+ }
+
+ /**
+ * Returns the deduplication group this source belongs to.
+ *
+ * <p>Sources which are part of the same deduplication group can coordinate to deduplicate their
+ * issues.
+ */
+ @Nullable
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public String getDeduplicationGroup() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mDeduplicationGroup;
+ }
+
+ /**
+ * Returns a set of package certificate hashes representing valid signed packages that represent
+ * this {@link SafetySource}.
+ *
+ * <p>If one or more certificate hashes are set, Safety Center will validate that a package
+ * calling {@link android.safetycenter.SafetyCenterManager#setSafetySourceData} is signed with
+ * one of the certificates provided.
+ *
+ * <p>The default value is an empty {@code Set}, in which case only the package name is
+ * validated.
+ *
+ * @see Builder#addPackageCertificateHash(String)
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Set<String> getPackageCertificateHashes() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mPackageCertificateHashes;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -435,7 +547,10 @@ public final class SafetySource implements Parcelable {
&& mMaxSeverityLevel == that.mMaxSeverityLevel
&& mSearchTermsResId == that.mSearchTermsResId
&& mLoggingAllowed == that.mLoggingAllowed
- && mRefreshOnPageOpenAllowed == that.mRefreshOnPageOpenAllowed;
+ && mRefreshOnPageOpenAllowed == that.mRefreshOnPageOpenAllowed
+ && mNotificationsAllowed == that.mNotificationsAllowed
+ && Objects.equals(mDeduplicationGroup, that.mDeduplicationGroup)
+ && Objects.equals(mPackageCertificateHashes, that.mPackageCertificateHashes);
}
@Override
@@ -453,7 +568,10 @@ public final class SafetySource implements Parcelable {
mMaxSeverityLevel,
mSearchTermsResId,
mLoggingAllowed,
- mRefreshOnPageOpenAllowed);
+ mRefreshOnPageOpenAllowed,
+ mNotificationsAllowed,
+ mDeduplicationGroup,
+ mPackageCertificateHashes);
}
@Override
@@ -485,6 +603,12 @@ public final class SafetySource implements Parcelable {
+ mLoggingAllowed
+ ", mRefreshOnPageOpenAllowed="
+ mRefreshOnPageOpenAllowed
+ + ", mNotificationsAllowed="
+ + mNotificationsAllowed
+ + ", mDeduplicationGroup="
+ + mDeduplicationGroup
+ + ", mPackageCertificateHashes="
+ + mPackageCertificateHashes
+ '}';
}
@@ -508,6 +632,11 @@ public final class SafetySource implements Parcelable {
dest.writeInt(mSearchTermsResId);
dest.writeBoolean(mLoggingAllowed);
dest.writeBoolean(mRefreshOnPageOpenAllowed);
+ if (SdkLevel.isAtLeastU()) {
+ dest.writeBoolean(mNotificationsAllowed);
+ dest.writeString(mDeduplicationGroup);
+ dest.writeStringList(List.copyOf(mPackageCertificateHashes));
+ }
}
/** Builder class for {@link SafetySource}. */
@@ -526,12 +655,40 @@ public final class SafetySource implements Parcelable {
@Nullable @StringRes private Integer mSearchTermsResId;
@Nullable private Boolean mLoggingAllowed;
@Nullable private Boolean mRefreshOnPageOpenAllowed;
+ @Nullable private Boolean mNotificationsAllowed;
+ @Nullable private String mDeduplicationGroup;
+ @NonNull private final ArraySet<String> mPackageCertificateHashes = new ArraySet<>();
/** Creates a {@link Builder} for a {@link SafetySource}. */
public Builder(@SafetySourceType int type) {
mType = type;
}
+ /** Creates a {@link Builder} with the values from the given {@link SafetySource}. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder(@NonNull SafetySource safetySource) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ requireNonNull(safetySource);
+ mType = safetySource.mType;
+ mId = safetySource.mId;
+ mPackageName = safetySource.mPackageName;
+ mTitleResId = safetySource.mTitleResId;
+ mTitleForWorkResId = safetySource.mTitleForWorkResId;
+ mSummaryResId = safetySource.mSummaryResId;
+ mIntentAction = safetySource.mIntentAction;
+ mProfile = safetySource.mProfile;
+ mInitialDisplayState = safetySource.mInitialDisplayState;
+ mMaxSeverityLevel = safetySource.mMaxSeverityLevel;
+ mSearchTermsResId = safetySource.mSearchTermsResId;
+ mLoggingAllowed = safetySource.mLoggingAllowed;
+ mRefreshOnPageOpenAllowed = safetySource.mRefreshOnPageOpenAllowed;
+ mNotificationsAllowed = safetySource.mNotificationsAllowed;
+ mDeduplicationGroup = safetySource.mDeduplicationGroup;
+ mPackageCertificateHashes.addAll(safetySource.mPackageCertificateHashes);
+ }
+
/**
* Sets the id of this safety source.
*
@@ -714,6 +871,61 @@ public final class SafetySource implements Parcelable {
}
/**
+ * Sets the {@link #areNotificationsAllowed()} property of this {@link SafetySource}.
+ *
+ * <p>If set to {@code true} Safety Center may post Notifications about issues reported by
+ * this source.
+ *
+ * <p>The default value is {@code false}.
+ *
+ * @see #areNotificationsAllowed()
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setNotificationsAllowed(boolean notificationsAllowed) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mNotificationsAllowed = notificationsAllowed;
+ return this;
+ }
+
+ /**
+ * Sets the deduplication group for this source.
+ *
+ * <p>Sources which are part of the same deduplication group can coordinate to deduplicate
+ * issues that they're sending to SafetyCenter by providing the same deduplication
+ * identifier with those issues.
+ *
+ * <p>The deduplication group property is prohibited for sources of type static.
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setDeduplicationGroup(@Nullable String deduplicationGroup) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mDeduplicationGroup = deduplicationGroup;
+ return this;
+ }
+
+ /**
+ * Adds a package certificate hash to the {@link #getPackageCertificateHashes()} property of
+ * this {@link SafetySource}.
+ *
+ * @see #getPackageCertificateHashes()
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder addPackageCertificateHash(@NonNull String packageCertificateHash) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mPackageCertificateHashes.add(packageCertificateHash);
+ return this;
+ }
+
+ /**
* Creates the {@link SafetySource} defined by this {@link Builder}.
*
* <p>Throws an {@link IllegalStateException} if any constraint on the safety source is
@@ -736,7 +948,10 @@ public final class SafetySource implements Parcelable {
String packageName = mPackageName;
BuilderUtils.validateAttribute(
- packageName, "packageName", isDynamic || isIssueOnly, isStatic);
+ packageName,
+ "packageName",
+ isDynamic || isIssueOnly,
+ isStatic && !SdkLevel.isAtLeastU());
int initialDisplayState =
BuilderUtils.validateIntDef(
@@ -786,7 +1001,7 @@ public final class SafetySource implements Parcelable {
String intentAction = mIntentAction;
BuilderUtils.validateAttribute(
- mIntentAction,
+ intentAction,
"intentAction",
(isDynamic && isEnabled) || isStatic,
isIssueOnly);
@@ -811,6 +1026,24 @@ public final class SafetySource implements Parcelable {
isStatic,
false);
+ String deduplicationGroup = mDeduplicationGroup;
+ boolean notificationsAllowed = false;
+ Set<String> packageCertificateHashes = Set.copyOf(mPackageCertificateHashes);
+ if (SdkLevel.isAtLeastU()) {
+ notificationsAllowed =
+ BuilderUtils.validateBoolean(
+ mNotificationsAllowed,
+ "notificationsAllowed",
+ false,
+ isStatic,
+ false);
+
+ BuilderUtils.validateAttribute(
+ deduplicationGroup, "deduplicationGroup", false, isStatic);
+ BuilderUtils.validateCollection(
+ packageCertificateHashes, "packageCertificateHashes", false, isStatic);
+ }
+
return new SafetySource(
type,
id,
@@ -824,7 +1057,10 @@ public final class SafetySource implements Parcelable {
maxSeverityLevel,
searchTermsResId,
loggingAllowed,
- refreshOnPageOpenAllowed);
+ refreshOnPageOpenAllowed,
+ notificationsAllowed,
+ deduplicationGroup,
+ packageCertificateHashes);
}
}
}
diff --git a/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java b/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java
index 5e0032900..2ff44fad9 100644
--- a/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java
+++ b/framework-s/java/android/safetycenter/config/SafetySourcesGroup.java
@@ -17,6 +17,7 @@
package android.safetycenter.config;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
@@ -25,6 +26,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.res.Resources;
import android.os.Parcel;
@@ -32,6 +34,8 @@ import android.os.Parcelable;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -50,16 +54,39 @@ public final class SafetySourcesGroup implements Parcelable {
/**
* Indicates that the safety sources group should be displayed as a collapsible group with an
* icon (stateless or stateful) and an optional default summary.
+ *
+ * @deprecated use {@link #SAFETY_SOURCES_GROUP_TYPE_STATEFUL} instead.
*/
public static final int SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE = 0;
/**
+ * Indicates that the safety sources group should be displayed as a group that may contribute to
+ * the overall Safety Center status. This is indicated by a group stateful icon. If all sources
+ * in the group have an unspecified status then a stateless group icon might be applied.
+ */
+ public static final int SAFETY_SOURCES_GROUP_TYPE_STATEFUL =
+ SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE;
+
+ /**
* Indicates that the safety sources group should be displayed as a rigid group with no icon and
* no summary.
+ *
+ * @deprecated use {@link #SAFETY_SOURCES_GROUP_TYPE_STATELESS} instead.
*/
public static final int SAFETY_SOURCES_GROUP_TYPE_RIGID = 1;
- /** Indicates that the safety sources group should not be displayed. */
+ /**
+ * Indicates that the safety sources group should be displayed as a group that does not
+ * contribute to the overall Safety Center status. All sources of type dynamic in the group can
+ * only report an unspecified status. The stateless icon and summary may be ignored and not be
+ * displayed.
+ */
+ public static final int SAFETY_SOURCES_GROUP_TYPE_STATELESS = SAFETY_SOURCES_GROUP_TYPE_RIGID;
+
+ /**
+ * Indicates that the safety sources group should not be displayed. All sources in the group
+ * must be of type issue-only.
+ */
public static final int SAFETY_SOURCES_GROUP_TYPE_HIDDEN = 2;
/**
@@ -67,24 +94,27 @@ public final class SafetySourcesGroup implements Parcelable {
*
* @hide
*/
+ @SuppressLint("UniqueConstants") // Intentionally renaming the COLLAPSIBLE and RIGID constants.
@Retention(RetentionPolicy.SOURCE)
@IntDef(
prefix = "SAFETY_SOURCES_GROUP_TYPE_",
value = {
SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE,
+ SAFETY_SOURCES_GROUP_TYPE_STATEFUL,
SAFETY_SOURCES_GROUP_TYPE_RIGID,
+ SAFETY_SOURCES_GROUP_TYPE_STATELESS,
SAFETY_SOURCES_GROUP_TYPE_HIDDEN
})
public @interface SafetySourceGroupType {}
/**
- * Indicates that the safety sources group will not be displayed with any special icon when all
- * the sources contained in it are stateless.
+ * Indicates that no special icon will be displayed by a safety sources group when all the
+ * sources contained in it are stateless.
*/
public static final int STATELESS_ICON_TYPE_NONE = 0;
/**
- * Indicates that the safety sources group will be displayed with the privacy icon when all the
+ * Indicates that the privacy icon will be displayed by a safety sources group when all the
* sources contained in it are stateless.
*/
public static final int STATELESS_ICON_TYPE_PRIVACY = 1;
@@ -116,6 +146,9 @@ public final class SafetySourcesGroup implements Parcelable {
for (int i = 0; i < safetySources.size(); i++) {
builder.addSafetySource(safetySources.get(i));
}
+ if (SdkLevel.isAtLeastU()) {
+ builder.setType(in.readInt());
+ }
return builder.build();
}
@@ -125,6 +158,7 @@ public final class SafetySourcesGroup implements Parcelable {
}
};
+ @SafetySourceGroupType private final int mType;
@NonNull private final String mId;
@StringRes private final int mTitleResId;
@StringRes private final int mSummaryResId;
@@ -132,11 +166,13 @@ public final class SafetySourcesGroup implements Parcelable {
@NonNull private final List<SafetySource> mSafetySources;
private SafetySourcesGroup(
+ @SafetySourceGroupType int type,
@NonNull String id,
@StringRes int titleResId,
@StringRes int summaryResId,
@StatelessIconType int statelessIconType,
@NonNull List<SafetySource> safetySources) {
+ mType = type;
mId = id;
mTitleResId = titleResId;
mSummaryResId = summaryResId;
@@ -144,23 +180,10 @@ public final class SafetySourcesGroup implements Parcelable {
mSafetySources = safetySources;
}
- /**
- * Returns the type of this safety sources group.
- *
- * <p>The type is inferred according to the state of certain fields. If no title is provided
- * when building the group, the group is of type hidden. If a title is provided but no summary
- * or stateless icon are provided when building the group, the group is of type rigid.
- * Otherwise, the group is of type collapsible.
- */
+ /** Returns the type of this safety sources group. */
@SafetySourceGroupType
public int getType() {
- if (mTitleResId == Resources.ID_NULL) {
- return SAFETY_SOURCES_GROUP_TYPE_HIDDEN;
- }
- if (mSummaryResId != Resources.ID_NULL || mStatelessIconType != STATELESS_ICON_TYPE_NONE) {
- return SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE;
- }
- return SAFETY_SOURCES_GROUP_TYPE_RIGID;
+ return mType;
}
/**
@@ -224,7 +247,8 @@ public final class SafetySourcesGroup implements Parcelable {
if (this == o) return true;
if (!(o instanceof SafetySourcesGroup)) return false;
SafetySourcesGroup that = (SafetySourcesGroup) o;
- return Objects.equals(mId, that.mId)
+ return mType == that.mType
+ && Objects.equals(mId, that.mId)
&& mTitleResId == that.mTitleResId
&& mSummaryResId == that.mSummaryResId
&& mStatelessIconType == that.mStatelessIconType
@@ -233,13 +257,16 @@ public final class SafetySourcesGroup implements Parcelable {
@Override
public int hashCode() {
- return Objects.hash(mId, mTitleResId, mSummaryResId, mStatelessIconType, mSafetySources);
+ return Objects.hash(
+ mType, mId, mTitleResId, mSummaryResId, mStatelessIconType, mSafetySources);
}
@Override
public String toString() {
return "SafetySourcesGroup{"
- + "mId="
+ + "mType="
+ + mType
+ + ", mId="
+ mId
+ ", mTitleResId="
+ mTitleResId
@@ -264,6 +291,9 @@ public final class SafetySourcesGroup implements Parcelable {
dest.writeInt(mSummaryResId);
dest.writeInt(mStatelessIconType);
dest.writeTypedList(mSafetySources);
+ if (SdkLevel.isAtLeastU()) {
+ dest.writeInt(mType);
+ }
}
/** Builder class for {@link SafetySourcesGroup}. */
@@ -271,6 +301,7 @@ public final class SafetySourcesGroup implements Parcelable {
private final List<SafetySource> mSafetySources = new ArrayList<>();
+ @Nullable @SafetySourceGroupType private Integer mType;
@Nullable private String mId;
@Nullable @StringRes private Integer mTitleResId;
@Nullable @StringRes private Integer mSummaryResId;
@@ -279,6 +310,37 @@ public final class SafetySourcesGroup implements Parcelable {
/** Creates a {@link Builder} for a {@link SafetySourcesGroup}. */
public Builder() {}
+ /** Creates a {@link Builder} with the values from the given {@link SafetySourcesGroup}. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder(@NonNull SafetySourcesGroup original) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ requireNonNull(original);
+ mSafetySources.addAll(original.mSafetySources);
+ mType = original.mType;
+ mId = original.mId;
+ mTitleResId = original.mTitleResId;
+ mSummaryResId = original.mSummaryResId;
+ mStatelessIconType = original.mStatelessIconType;
+ }
+
+ /**
+ * Sets the type of this safety sources group.
+ *
+ * <p>If the type is not explicitly set, the type is inferred according to the state of
+ * certain fields. If no title is provided when building the group, the group is of type
+ * hidden. If a title is provided but no summary or stateless icon are provided when
+ * building the group, the group is of type stateless. Otherwise, the group is of type
+ * stateful.
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder setType(@SafetySourceGroupType int type) {
+ mType = type;
+ return this;
+ }
+
/**
* Sets the id of this safety sources group.
*
@@ -346,28 +408,20 @@ public final class SafetySourcesGroup implements Parcelable {
/**
* Creates the {@link SafetySourcesGroup} defined by this {@link Builder}.
*
- * <p>Throws an {@link IllegalStateException} if any constraint on the safety sources group
- * is violated.
+ * @throws IllegalStateException if any constraint on the safety sources group is violated
*/
@NonNull
public SafetySourcesGroup build() {
String id = mId;
BuilderUtils.validateId(id, "id", true, false);
+
List<SafetySource> safetySources = unmodifiableList(new ArrayList<>(mSafetySources));
if (safetySources.isEmpty()) {
throw new IllegalStateException("Safety sources group empty");
}
- boolean titleRequired = false;
- int safetySourcesSize = safetySources.size();
- for (int i = 0; i < safetySourcesSize; i++) {
- int type = safetySources.get(i).getType();
- if (type != SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY) {
- titleRequired = true;
- break;
- }
- }
- int titleResId = BuilderUtils.validateResId(mTitleResId, "title", titleRequired, false);
+
int summaryResId = BuilderUtils.validateResId(mSummaryResId, "summary", false, false);
+
int statelessIconType =
BuilderUtils.validateIntDef(
mStatelessIconType,
@@ -377,8 +431,53 @@ public final class SafetySourcesGroup implements Parcelable {
STATELESS_ICON_TYPE_NONE,
STATELESS_ICON_TYPE_NONE,
STATELESS_ICON_TYPE_PRIVACY);
+
+ boolean hasOnlyIssueOnlySources = true;
+ int safetySourcesSize = safetySources.size();
+ for (int i = 0; i < safetySourcesSize; i++) {
+ int type = safetySources.get(i).getType();
+ if (type != SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY) {
+ hasOnlyIssueOnlySources = false;
+ break;
+ }
+ }
+
+ int inferredGroupType = SAFETY_SOURCES_GROUP_TYPE_STATELESS;
+ if (hasOnlyIssueOnlySources) {
+ inferredGroupType = SAFETY_SOURCES_GROUP_TYPE_HIDDEN;
+ } else if (summaryResId != Resources.ID_NULL
+ || statelessIconType != Resources.ID_NULL) {
+ inferredGroupType = SAFETY_SOURCES_GROUP_TYPE_STATEFUL;
+ }
+ int type =
+ BuilderUtils.validateIntDef(
+ mType,
+ "type",
+ false,
+ false,
+ inferredGroupType,
+ SAFETY_SOURCES_GROUP_TYPE_STATEFUL,
+ SAFETY_SOURCES_GROUP_TYPE_STATELESS,
+ SAFETY_SOURCES_GROUP_TYPE_HIDDEN);
+ if (type == SAFETY_SOURCES_GROUP_TYPE_HIDDEN && !hasOnlyIssueOnlySources) {
+ throw new IllegalStateException(
+ "Safety sources groups of type hidden can only contain sources of type "
+ + "issue-only");
+ }
+ if (type != SAFETY_SOURCES_GROUP_TYPE_HIDDEN && hasOnlyIssueOnlySources) {
+ throw new IllegalStateException(
+ "Safety sources groups containing only sources of type issue-only must be "
+ + "of type hidden");
+ }
+
+ boolean isStateful = type == SAFETY_SOURCES_GROUP_TYPE_STATEFUL;
+ boolean isStateless = type == SAFETY_SOURCES_GROUP_TYPE_STATELESS;
+ int titleResId =
+ BuilderUtils.validateResId(
+ mTitleResId, "title", isStateful || isStateless, false);
+
return new SafetySourcesGroup(
- id, titleResId, summaryResId, statelessIconType, safetySources);
+ type, id, titleResId, summaryResId, statelessIconType, safetySources);
}
}
}
diff --git a/framework-s/java/android/safetycenter/config/safety_center_config-v34.xsd b/framework-s/java/android/safetycenter/config/safety_center_config-v34.xsd
new file mode 100644
index 000000000..01497799a
--- /dev/null
+++ b/framework-s/java/android/safetycenter/config/safety_center_config-v34.xsd
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.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.
+ -->
+<!-- This file contains comments that define constraints that cannot be covered by the XSD language -->
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ version="1.0">
+
+ <xsd:element name="safety-center-config" type="safety-center-config"/>
+
+ <xsd:complexType name="safety-center-config">
+ <xsd:sequence>
+ <xsd:element name="safety-sources-config" type="safety-sources-config"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="safety-sources-config">
+ <xsd:sequence>
+ <xsd:element
+ name="safety-sources-group" type="safety-sources-group"
+ minOccurs="1" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="safety-sources-group">
+ <xsd:choice minOccurs="1" maxOccurs="unbounded">
+ <xsd:element name="dynamic-safety-source" type="dynamic-safety-source"/>
+ <xsd:element name="static-safety-source" type="static-safety-source"/>
+ <xsd:element name="issue-only-safety-source" type="issue-only-safety-source"/>
+ </xsd:choice>
+ <!-- id must be unique among safety sources groups -->
+ <xsd:attribute name="id" type="idOrStringResourceName" use="required"/>
+ <!-- title is required unless the group contains issue only and/or internal sources -->
+ <xsd:attribute name="title" type="runtimeStringResourceName"/>
+ <xsd:attribute name="summary" type="runtimeStringResourceName"/>
+ <xsd:attribute name="statelessIconType" type="statelessIconTypeOrStringResourceName"
+ default="none"/>
+ <!-- type is inferred from other attributes and the group content if omitted -->
+ <xsd:attribute name="type" type="groupTypeOrStringResourceName"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="dynamic-safety-source">
+ <!-- id must be unique among safety sources -->
+ <xsd:attribute name="id" type="idOrStringResourceName" use="required"/>
+ <xsd:attribute name="packageName" type="stringOrStringResourceName" use="required"/>
+ <!-- optional comma-separated set of certficate hashes, if provided will be used for validation. -->
+ <xsd:attribute name="packageCertificateHashes" type="stringOrStringResourceName"/>
+ <!-- title is required if initialDisplayState is not set to hidden or if searchTerms are provided -->
+ <xsd:attribute name="title" type="runtimeStringResourceName"/>
+ <!-- titleForWork is required if profile is set to all_profiles, and initialDisplayState is not set to hidden or if searchTerms are provided -->
+ <!-- titleForWork is prohibited if profile is set to primary_profile_only -->
+ <xsd:attribute name="titleForWork" type="runtimeStringResourceName"/>
+ <!-- summary is required if initialDisplayState is not set to hidden -->
+ <xsd:attribute name="summary" type="runtimeStringResourceName"/>
+ <!-- intentAction is required if initialDisplayState is set to enabled -->
+ <xsd:attribute name="intentAction" type="stringOrStringResourceName"/>
+ <xsd:attribute name="profile" type="profile" use="required"/>
+ <xsd:attribute name="initialDisplayState" type="initialDisplayStateOrStringResourceName"
+ default="enabled"/>
+ <xsd:attribute name="maxSeverityLevel" type="intOrStringResourceName" default="2147483647"/>
+ <xsd:attribute name="searchTerms" type="runtimeStringResourceName"/>
+ <xsd:attribute name="loggingAllowed" type="booleanOrStringResourceName" default="true"/>
+ <xsd:attribute name="refreshOnPageOpenAllowed" type="booleanOrStringResourceName"
+ default="false"/>
+ <xsd:attribute name="notificationsAllowed" type="booleanOrStringResourceName"
+ default="false"/>
+ <xsd:attribute name="deduplicationGroup" type="stringOrStringResourceName"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="issue-only-safety-source">
+ <!-- id must be unique among safety sources -->
+ <xsd:attribute name="id" type="idOrStringResourceName" use="required"/>
+ <xsd:attribute name="packageName" type="stringOrStringResourceName" use="required"/>
+ <!-- optional comma-separated set of certficate hashes, if provided will be used for validation. -->
+ <xsd:attribute name="packageCertificateHashes" type="stringOrStringResourceName"/>
+ <xsd:attribute name="profile" type="profileOrStringResourceName" use="required"/>
+ <xsd:attribute name="maxSeverityLevel" type="intOrStringResourceName" default="2147483647"/>
+ <xsd:attribute name="loggingAllowed" type="booleanOrStringResourceName" default="true"/>
+ <xsd:attribute name="refreshOnPageOpenAllowed" type="booleanOrStringResourceName"
+ default="false"/>
+ <xsd:attribute name="notificationsAllowed" type="booleanOrStringResourceName"
+ default="false"/>
+ <xsd:attribute name="deduplicationGroup" type="stringOrStringResourceName"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="static-safety-source">
+ <!-- id must be unique among safety sources -->
+ <xsd:attribute name="id" type="idOrStringResourceName" use="required"/>
+ <xsd:attribute name="packageName" type="stringOrStringResourceName"/>
+ <xsd:attribute name="title" type="runtimeStringResourceName" use="required"/>
+ <!-- titleForWork is required if profile is set to all_profiles -->
+ <!-- titleForWork is prohibited if profile is set to primary_profile_only -->
+ <xsd:attribute name="titleForWork" type="runtimeStringResourceName"/>
+ <xsd:attribute name="summary" type="runtimeStringResourceName"/>
+ <xsd:attribute name="intentAction" type="stringOrStringResourceName" use="required"/>
+ <xsd:attribute name="profile" type="profileOrStringResourceName" use="required"/>
+ <xsd:attribute name="searchTerms" type="runtimeStringResourceName"/>
+ </xsd:complexType>
+
+ <xsd:simpleType name="intOrStringResourceName">
+ <!-- String resource names will be resolved only once at parse time. -->
+ <!-- Locale changes and device config changes will be ignored. -->
+ <!-- The value of the string resource must be of type xsd:int. -->
+ <xsd:union memberTypes="stringResourceName xsd:int"/>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="booleanOrStringResourceName">
+ <!-- String resource names will be resolved only once at parse time. -->
+ <!-- Locale changes and device config changes will be ignored. -->
+ <!-- The value of the string resource must be of type xsd:boolean. -->
+ <xsd:union memberTypes="stringResourceName xsd:boolean"/>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="stringOrStringResourceName">
+ <!-- String resource names will be resolved only once at parse time. -->
+ <!-- Locale changes and device config changes will be ignored. -->
+ <!-- The value of the string resource must be of type xsd:string. -->
+ <xsd:union memberTypes="stringResourceName xsd:string"/>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="idOrStringResourceName">
+ <!-- String resource names will be resolved only once at parse time. -->
+ <!-- Locale changes and device config changes will be ignored. -->
+ <!-- The value of the string resource must be of type xsd:string. -->
+ <xsd:union memberTypes="stringResourceName id"/>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="id">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="[0-9a-zA-Z_-]+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="statelessIconTypeOrStringResourceName">
+ <!-- String resource names will be resolved only once at parse time. -->
+ <!-- Locale changes and device config changes will be ignored. -->
+ <!-- The value of the string resource must be of type statelessIconType. -->
+ <xsd:union memberTypes="stringResourceName statelessIconType"/>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="statelessIconType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="none"/>
+ <xsd:enumeration value="privacy"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="profileOrStringResourceName">
+ <!-- String resource names will be resolved only once at parse time. -->
+ <!-- Locale changes and device config changes will be ignored. -->
+ <!-- The value of the string resource must be of type profile. -->
+ <xsd:union memberTypes="stringResourceName profile"/>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="profile">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="primary_profile_only"/>
+ <xsd:enumeration value="all_profiles"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="initialDisplayStateOrStringResourceName">
+ <!-- String resource names will be resolved only once at parse time. -->
+ <!-- Locale changes and device config changes will be ignored. -->
+ <!-- The value of the string resource must be of type initialDisplayState. -->
+ <xsd:union memberTypes="stringResourceName initialDisplayState"/>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="initialDisplayState">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="enabled"/>
+ <xsd:enumeration value="disabled"/>
+ <xsd:enumeration value="hidden"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="groupTypeOrStringResourceName">
+ <!-- String resource names will be resolved only once at parse time. -->
+ <!-- Locale changes and device config changes will be ignored. -->
+ <!-- The value of the string resource must be of type groupType. -->
+ <xsd:union memberTypes="stringResourceName groupType"/>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="groupType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="stateless"/>
+ <xsd:enumeration value="stateful"/>
+ <xsd:enumeration value="hidden"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+ <xsd:simpleType name="runtimeStringResourceName">
+ <!-- String resource names will be resolved at runtime whenever the string value is used. -->
+ <xsd:union memberTypes="stringResourceName"/>
+ </xsd:simpleType>
+
+ <!-- String resource names will be ignored for any attribute not directly or indirectly marked as stringResourceName. -->
+ <!-- A stringResourceName is a fully qualified resource name of the form "@package:string/entry". Package is required. -->
+ <xsd:simpleType name="stringResourceName">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="@([a-z]+\.)*[a-z]+:string/.+"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+
+</xsd:schema>
diff --git a/framework-s/java/android/safetylabel/SafetyLabelConstants.java b/framework-s/java/android/safetylabel/SafetyLabelConstants.java
new file mode 100644
index 000000000..8e8cb0ec9
--- /dev/null
+++ b/framework-s/java/android/safetylabel/SafetyLabelConstants.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetylabel;
+
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+import android.annotation.SystemApi;
+
+import androidx.annotation.RequiresApi;
+
+/**
+ * Constants relating to the Safety Label feature.
+ *
+ * @hide
+ */
+@SystemApi
+@RequiresApi(UPSIDE_DOWN_CAKE)
+public final class SafetyLabelConstants {
+
+ /**
+ * Constant to be used as Device Config flag determining whether the Permission Rationale
+ * feature is enabled.
+ *
+ * <p>When this flag is enabled, permission rationale messaging will be displayed in permission
+ * settings and the runtime permissions grant dialog.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final String PERMISSION_RATIONALE_ENABLED = "permission_rationale_enabled";
+
+ /**
+ * Constant to be used as Device Config flag determining whether the Safety Label Change
+ * Notifications feature is enabled.
+ *
+ * <p>When this flag is enabled, a system notification will be sent to users if any apps they
+ * have installed have made recent updates to their data sharing policy in their app safety
+ * labels.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public static final String SAFETY_LABEL_CHANGE_NOTIFICATIONS_ENABLED =
+ "safety_label_change_notifications_enabled";
+
+ private SafetyLabelConstants() {}
+}
diff --git a/framework/Android.bp b/framework/Android.bp
index c379ff71a..0ea2f64fb 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -55,3 +55,12 @@ java_sdk_library {
hostdex: true,
installable: true,
}
+
+java_api_contribution {
+ name: "framework-permission-public-stubs",
+ api_surface: "public",
+ api_file: "api/current.txt",
+ visibility: [
+ "//build/orchestrator/apis",
+ ],
+}
diff --git a/ktfmt_includes.txt b/ktfmt_includes.txt
new file mode 100644
index 000000000..e5ef2ba2a
--- /dev/null
+++ b/ktfmt_includes.txt
@@ -0,0 +1 @@
++tests/cts/safetycenter
diff --git a/service/Android.bp b/service/Android.bp
index 2ae6c55b0..4fa96f235 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -20,30 +20,23 @@ filegroup {
name: "service-permission-java-sources",
srcs: [
"java/**/*.java",
- // Exclude Kotlin sources for T.
- //"java/**/*.kt",
+ "java/**/*.kt",
],
path: "java",
visibility: ["//visibility:private"],
}
-filegroup {
- name: "service-permission-streaming-proto-sources",
- srcs: [
- "proto/role_service.proto",
- ],
- visibility: ["//frameworks/base"],
-}
-
java_library {
- name: "service-permission-streaming-proto-java-gen",
+ name: "service-permission-proto-stream",
proto: {
type: "stream",
include_dirs: [
"external/protobuf/src",
],
},
- srcs: [":service-permission-streaming-proto-sources"],
+ srcs: [
+ "proto/role_service.proto",
+ ],
installable: false,
min_sdk_version: "30",
sdk_version: "system_server_current",
@@ -79,7 +72,6 @@ java_sdk_library {
"//frameworks/base/apex/permission/tests",
"//frameworks/base/services/tests/mockingservicestests",
"//frameworks/base/services/tests/PackageManagerServiceTests/server",
- "//frameworks/base/services/tests/servicestests",
"//packages/modules/Permission/tests/apex",
],
srcs: [
@@ -87,6 +79,7 @@ java_sdk_library {
],
libs: [
"androidx.annotation_annotation",
+ "framework-configinfrastructure",
// TODO(b/177884622): Short term solution to prevent service-permission from seeing hidden
// APIs in framework-permission, as we don't actually have any dependency in it.
//"framework-permission",
@@ -97,13 +90,12 @@ java_sdk_library {
// Soong fails to automatically add this dependency because all the
// *.kt sources are inside a filegroup.
- // Exclude Kotlin sources for T.
- //"kotlin-annotations",
+ "kotlin-annotations",
],
static_libs: [
- // Exclude Kotlin sources for T.
- //"kotlin-stdlib",
+ "kotlin-stdlib",
"modules-utils-backgroundthread",
+ "modules-utils-binary-xml",
"modules-utils-os",
"safety-center-config",
"safety-center-internal-data",
@@ -111,7 +103,7 @@ java_sdk_library {
"safety-center-resources-lib",
"service-permission-shared",
"service-permission-statsd",
- "service-permission-streaming-proto-java-gen",
+ "service-permission-proto-stream",
],
errorprone: {
javacflags: ["-Xep:GuardedBy:ERROR"],
diff --git a/service/java/com/android/permission/compat/UserHandleCompat.java b/service/java/com/android/permission/compat/UserHandleCompat.java
index 7c711d301..1901aa997 100644
--- a/service/java/com/android/permission/compat/UserHandleCompat.java
+++ b/service/java/com/android/permission/compat/UserHandleCompat.java
@@ -45,4 +45,15 @@ public final class UserHandleCompat {
public static int getUserId(int uid) {
return UserHandle.getUserHandleForUid(uid).getIdentifier();
}
+
+ /**
+ * Get the UID from the give user ID and app ID
+ *
+ * @param userId the user ID
+ * @param appId the app ID
+ * @return the UID
+ */
+ public static int getUid(@UserIdInt int userId, int appId) {
+ return UserHandle.of(userId).getUid(appId);
+ }
}
diff --git a/service/java/com/android/role/RoleService.java b/service/java/com/android/role/RoleService.java
index bb787b7cf..bc8d58a8a 100644
--- a/service/java/com/android/role/RoleService.java
+++ b/service/java/com/android/role/RoleService.java
@@ -272,15 +272,9 @@ public class RoleService extends SystemService implements RoleUserState.Callback
RoleControllerManager controller = mControllers.get(userId);
if (controller == null) {
Context systemContext = getContext();
- Context context;
- try {
- context = systemContext.createPackageContextAsUser(
- systemContext.getPackageName(), 0, UserHandle.of(userId));
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException(e);
- }
+ Context userContext = systemContext.createContextAsUser(UserHandle.of(userId), 0);
controller = RoleControllerManager.createWithInitializedRemoteServiceComponentName(
- ForegroundThread.getHandler(), context);
+ ForegroundThread.getHandler(), userContext);
mControllers.put(userId, controller);
}
return controller;
diff --git a/service/java/com/android/role/RoleShellCommand.java b/service/java/com/android/role/RoleShellCommand.java
index 357ce5f76..808a64cb4 100644
--- a/service/java/com/android/role/RoleShellCommand.java
+++ b/service/java/com/android/role/RoleShellCommand.java
@@ -29,11 +29,14 @@ import com.android.modules.utils.BasicShellCommandHandler;
import com.android.permission.compat.UserHandleCompat;
import java.io.PrintWriter;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@RequiresApi(Build.VERSION_CODES.S)
class RoleShellCommand extends BasicShellCommandHandler {
+ private static final String ROLE_HOLDER_SEPARATOR = ";";
+
@NonNull
private final IRoleManager mRoleManager;
@@ -74,6 +77,8 @@ class RoleShellCommand extends BasicShellCommandHandler {
PrintWriter pw = getOutPrintWriter();
try {
switch (cmd) {
+ case "get-role-holders":
+ return runGetRoleHolders();
case "add-role-holder":
return runAddRoleHolder();
case "remove-role-holder":
@@ -108,6 +113,15 @@ class RoleShellCommand extends BasicShellCommandHandler {
return Integer.parseInt(flags);
}
+ private int runGetRoleHolders() throws RemoteException {
+ int userId = getUserIdMaybe();
+ String roleName = getNextArgRequired();
+
+ List<String> roleHolders = mRoleManager.getRoleHoldersAsUser(roleName, userId);
+ getOutPrintWriter().println(String.join(ROLE_HOLDER_SEPARATOR, roleHolders));
+ return 0;
+ }
+
private int runAddRoleHolder() throws RemoteException {
int userId = getUserIdMaybe();
String roleName = getNextArgRequired();
@@ -155,6 +169,7 @@ class RoleShellCommand extends BasicShellCommandHandler {
pw.println(" help or -h");
pw.println(" Print this help text.");
pw.println();
+ pw.println(" get-role-holders [--user USER_ID] ROLE");
pw.println(" add-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]");
pw.println(" remove-role-holder [--user USER_ID] ROLE PACKAGE [FLAGS]");
pw.println(" clear-role-holders [--user USER_ID] ROLE [FLAGS]");
diff --git a/service/java/com/android/role/TEST_MAPPING b/service/java/com/android/role/TEST_MAPPING
index 0d7bc1476..15173a9da 100644
--- a/service/java/com/android/role/TEST_MAPPING
+++ b/service/java/com/android/role/TEST_MAPPING
@@ -1,10 +1,10 @@
{
"presubmit": [
{
- "name": "CtsStatsdHostTestCases",
+ "name": "CtsAppSecurityHostTestCases",
"options": [
{
- "include-filter": "android.cts.statsd.atom.UidAtomTests#testRoleHolder"
+ "include-filter": "android.appsecurity.cts.StatsdAppSecurityAtomTest#testRoleHolder"
}
]
},
@@ -16,5 +16,22 @@
}
]
}
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsRoleTestCases[com.google.android.permission.apex]",
+ "options": [
+ // TODO(b/238677748): These two tests currently fails on R base image
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#openDefaultAppListThenIsNotDefaultAppInList"
+ },
+ {
+ "exclude-filter": "android.app.role.cts.RoleManagerTest#removeSmsRoleHolderThenPermissionIsRevoked"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
]
}
diff --git a/service/java/com/android/safetycenter/PendingIntentFactory.java b/service/java/com/android/safetycenter/PendingIntentFactory.java
index 631f6fc92..2a2b2b5fa 100644
--- a/service/java/com/android/safetycenter/PendingIntentFactory.java
+++ b/service/java/com/android/safetycenter/PendingIntentFactory.java
@@ -26,6 +26,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ResolveInfoFlags;
import android.content.res.Resources;
import android.os.Binder;
import android.os.UserHandle;
@@ -34,7 +35,6 @@ import android.util.Log;
import androidx.annotation.RequiresApi;
-import com.android.permission.util.PackageUtils;
import com.android.safetycenter.resources.SafetyCenterResourcesContext;
import java.util.Arrays;
@@ -92,8 +92,7 @@ final class PendingIntentFactory {
if (packageContext == null) {
return null;
}
- Intent intent =
- createIntent(sourceId, intentAction, packageName, userId, isQuietModeEnabled);
+ Intent intent = createIntent(packageContext, sourceId, intentAction, isQuietModeEnabled);
if (intent == null) {
return null;
}
@@ -208,10 +207,9 @@ final class PendingIntentFactory {
@Nullable
private Intent createIntent(
+ @NonNull Context packageContext,
@NonNull String sourceId,
@NonNull String intentAction,
- @NonNull String packageName,
- @UserIdInt int userId,
boolean isQuietModeEnabled) {
Intent intent = new Intent(intentAction);
@@ -231,11 +229,11 @@ final class PendingIntentFactory {
if (isQuietModeEnabled) {
return intent;
}
- if (intentResolves(intent, userId)) {
+ if (intentResolves(packageContext, intent)) {
return intent;
}
- intent.setPackage(packageName);
- if (intentResolves(intent, userId)) {
+ intent.setPackage(packageContext.getPackageName());
+ if (intentResolves(packageContext, intent)) {
return intent;
}
return null;
@@ -249,9 +247,16 @@ final class PendingIntentFactory {
.contains(sourceId);
}
- private boolean intentResolves(@NonNull Intent intent, @UserIdInt int userId) {
- return !PackageUtils.queryUnfilteredIntentActivitiesAsUser(intent, 0, userId, mContext)
- .isEmpty();
+ private static boolean intentResolves(@NonNull Context packageContext, @NonNull Intent intent) {
+ PackageManager packageManager = packageContext.getPackageManager();
+ // This call requires the INTERACT_ACROSS_USERS permission as the `packageContext` could
+ // belong to another user.
+ final long callingId = Binder.clearCallingIdentity();
+ try {
+ return !packageManager.queryIntentActivities(intent, ResolveInfoFlags.of(0)).isEmpty();
+ } finally {
+ Binder.restoreCallingIdentity(callingId);
+ }
}
@NonNull
diff --git a/service/java/com/android/safetycenter/RefreshReasons.java b/service/java/com/android/safetycenter/RefreshReasons.java
index c5f860a7a..ee318c7fd 100644
--- a/service/java/com/android/safetycenter/RefreshReasons.java
+++ b/service/java/com/android/safetycenter/RefreshReasons.java
@@ -17,21 +17,26 @@
package com.android.safetycenter;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA;
import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_REQUEST_TYPE_GET_DATA;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_LOCALE_CHANGE;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_REBOOT;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN;
+import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PERIODIC;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED;
+import android.annotation.TargetApi;
import android.safetycenter.SafetyCenterManager.RefreshReason;
import android.safetycenter.SafetyCenterManager.RefreshRequestType;
import android.util.Log;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
/** Helpers to do with {@link RefreshReason}. */
@RequiresApi(TIRAMISU)
final class RefreshReasons {
@@ -54,10 +59,14 @@ final class RefreshReasons {
case REFRESH_REASON_OTHER:
return;
}
+ if (SdkLevel.isAtLeastU() && refreshReason == REFRESH_REASON_PERIODIC) {
+ return;
+ }
throw new IllegalArgumentException("Unexpected refresh reason: " + refreshReason);
}
/** Converts the given {@link RefreshReason} to a {@link RefreshRequestType}. */
+ @TargetApi(UPSIDE_DOWN_CAKE)
@RefreshRequestType
static int toRefreshRequestType(@RefreshReason int refreshReason) {
switch (refreshReason) {
@@ -68,6 +77,7 @@ final class RefreshReasons {
case REFRESH_REASON_DEVICE_LOCALE_CHANGE:
case REFRESH_REASON_SAFETY_CENTER_ENABLED:
case REFRESH_REASON_OTHER:
+ case REFRESH_REASON_PERIODIC:
return EXTRA_REFRESH_REQUEST_TYPE_GET_DATA;
}
Log.w(TAG, "Unexpected refresh reason: " + refreshReason);
@@ -77,12 +87,14 @@ final class RefreshReasons {
/**
* Returns {@code true} if the given {@link RefreshReason} corresponds to a background refresh.
*/
+ @TargetApi(UPSIDE_DOWN_CAKE)
static boolean isBackgroundRefresh(@RefreshReason int refreshReason) {
switch (refreshReason) {
case REFRESH_REASON_DEVICE_REBOOT:
case REFRESH_REASON_DEVICE_LOCALE_CHANGE:
case REFRESH_REASON_SAFETY_CENTER_ENABLED:
case REFRESH_REASON_OTHER:
+ case REFRESH_REASON_PERIODIC:
return true;
case REFRESH_REASON_PAGE_OPEN:
case REFRESH_REASON_RESCAN_BUTTON_CLICK:
diff --git a/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java b/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java
index 4deb642f7..9bfbf6025 100644
--- a/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java
+++ b/service/java/com/android/safetycenter/SafetyCenterBroadcastDispatcher.java
@@ -27,10 +27,14 @@ import static android.safetycenter.SafetyCenterManager.ACTION_SAFETY_CENTER_ENAB
import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID;
import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE;
import static android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCE_IDS;
+import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED;
+import static java.util.Collections.unmodifiableList;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.BroadcastOptions;
import android.content.Context;
import android.content.Intent;
@@ -39,6 +43,8 @@ import android.os.UserHandle;
import android.safetycenter.SafetyCenterManager;
import android.safetycenter.SafetyCenterManager.RefreshReason;
import android.safetycenter.SafetyCenterManager.RefreshRequestType;
+import android.safetycenter.SafetySourceData;
+import android.util.ArraySet;
import android.util.Log;
import android.util.SparseArray;
@@ -68,14 +74,17 @@ final class SafetyCenterBroadcastDispatcher {
@NonNull private final Context mContext;
@NonNull private final SafetyCenterConfigReader mSafetyCenterConfigReader;
@NonNull private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker;
+ @NonNull private final SafetyCenterRepository mSafetyCenterRepository;
SafetyCenterBroadcastDispatcher(
@NonNull Context context,
@NonNull SafetyCenterConfigReader safetyCenterConfigReader,
- @NonNull SafetyCenterRefreshTracker safetyCenterRefreshTracker) {
+ @NonNull SafetyCenterRefreshTracker safetyCenterRefreshTracker,
+ @NonNull SafetyCenterRepository safetyCenterRepository) {
mContext = context;
mSafetyCenterConfigReader = safetyCenterConfigReader;
mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
+ mSafetyCenterRepository = safetyCenterRepository;
}
/**
@@ -83,10 +92,15 @@ final class SafetyCenterBroadcastDispatcher {
* SafetyCenterManager#ACTION_REFRESH_SAFETY_SOURCES}, and returns the associated broadcast id.
*
* <p>Returns {@code null} if no broadcast was sent.
+ *
+ * @param safetySourceIds list of IDs to specify the safety sources to be refreshed or a {@code
+ * null} value to refresh all safety sources.
*/
@Nullable
String sendRefreshSafetySources(
- @RefreshReason int refreshReason, @NonNull UserProfileGroup userProfileGroup) {
+ @RefreshReason int refreshReason,
+ @NonNull UserProfileGroup userProfileGroup,
+ @Nullable List<String> safetySourceIds) {
List<Broadcast> broadcasts = mSafetyCenterConfigReader.getBroadcasts();
BroadcastOptions broadcastOptions = createBroadcastOptions();
@@ -104,7 +118,8 @@ final class SafetyCenterBroadcastDispatcher {
broadcastOptions,
refreshReason,
userProfileGroup,
- broadcastId);
+ broadcastId,
+ safetySourceIds);
}
if (!hasSentAtLeastOneBroadcast) {
@@ -120,7 +135,8 @@ final class SafetyCenterBroadcastDispatcher {
@NonNull BroadcastOptions broadcastOptions,
@RefreshReason int refreshReason,
@NonNull UserProfileGroup userProfileGroup,
- @NonNull String broadcastId) {
+ @NonNull String broadcastId,
+ @Nullable List<String> requiredSourceIds) {
boolean hasSentAtLeastOneBroadcast = false;
int requestType = RefreshReasons.toRefreshRequestType(refreshReason);
String packageName = broadcast.getPackageName();
@@ -137,6 +153,11 @@ final class SafetyCenterBroadcastDispatcher {
sourceIds.removeAll(deniedSourceIds);
}
+ if (requiredSourceIds != null) {
+ sourceIds = new ArrayList<>(sourceIds);
+ sourceIds.retainAll(requiredSourceIds);
+ }
+
if (sourceIds.isEmpty()) {
continue;
}
@@ -317,26 +338,74 @@ final class SafetyCenterBroadcastDispatcher {
*
* <p>Every value present is a non-empty list, but the overall result may be empty.
*/
- private static SparseArray<List<String>> getUserIdsToSourceIds(
+ @NonNull
+ private SparseArray<List<String>> getUserIdsToSourceIds(
@NonNull Broadcast broadcast,
@NonNull UserProfileGroup userProfileGroup,
@RefreshReason int refreshReason) {
int[] managedProfileIds = userProfileGroup.getManagedRunningProfilesUserIds();
SparseArray<List<String>> result = new SparseArray<>(managedProfileIds.length + 1);
+ List<String> profileParentSources =
+ getSourceIdsForRefreshReason(
+ refreshReason,
+ broadcast.getSourceIdsForProfileParent(),
+ broadcast.getSourceIdsForProfileParentOnPageOpen(),
+ userProfileGroup.getProfileParentUserId());
- List<String> profileParentSources = broadcast.getSourceIdsForProfileParent(refreshReason);
if (!profileParentSources.isEmpty()) {
result.put(userProfileGroup.getProfileParentUserId(), profileParentSources);
}
- List<String> managedProfileSources =
- broadcast.getSourceIdsForManagedProfiles(refreshReason);
- if (!managedProfileSources.isEmpty()) {
- for (int i = 0; i < managedProfileIds.length; i++) {
+ for (int i = 0; i < managedProfileIds.length; i++) {
+ List<String> managedProfileSources =
+ getSourceIdsForRefreshReason(
+ refreshReason,
+ broadcast.getSourceIdsForManagedProfiles(),
+ broadcast.getSourceIdsForManagedProfilesOnPageOpen(),
+ managedProfileIds[i]);
+
+ if (!managedProfileSources.isEmpty()) {
result.put(managedProfileIds[i], managedProfileSources);
}
}
return result;
}
+
+ /**
+ * Returns the sources to refresh for the given {@code refreshReason}.
+ *
+ * <p>For {@link SafetyCenterManager#REFRESH_REASON_PAGE_OPEN}, returns a copy of {@code
+ * allSourceIds} filtered to contain only sources that have refreshOnPageOpenAllowed in the XML
+ * config, or are in the safety_center_override_refresh_on_page_open_sources flag, or don't have
+ * any {@link SafetySourceData} provided.
+ */
+ @NonNull
+ private List<String> getSourceIdsForRefreshReason(
+ @RefreshReason int refreshReason,
+ @NonNull List<String> allSourceIds,
+ @NonNull List<String> pageOpenSourceIds,
+ @UserIdInt int userId) {
+ if (refreshReason != REFRESH_REASON_PAGE_OPEN) {
+ return allSourceIds;
+ }
+
+ List<String> sourceIds = new ArrayList<>();
+
+ ArraySet<String> flagAllowListedSourceIds =
+ SafetyCenterFlags.getOverrideRefreshOnPageOpenSourceIds();
+
+ for (int i = 0; i < allSourceIds.size(); i++) {
+ String sourceId = allSourceIds.get(i);
+ if (pageOpenSourceIds.contains(sourceId)
+ || flagAllowListedSourceIds.contains(sourceId)
+ || mSafetyCenterRepository.getSafetySourceData(
+ SafetySourceKey.of(sourceId, userId))
+ == null) {
+ sourceIds.add(sourceId);
+ }
+ }
+
+ return unmodifiableList(sourceIds);
+ }
}
diff --git a/service/java/com/android/safetycenter/SafetyCenterConfigReader.java b/service/java/com/android/safetycenter/SafetyCenterConfigReader.java
index d09807abb..d3c95978d 100644
--- a/service/java/com/android/safetycenter/SafetyCenterConfigReader.java
+++ b/service/java/com/android/safetycenter/SafetyCenterConfigReader.java
@@ -24,8 +24,6 @@ import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Resources;
-import android.safetycenter.SafetyCenterManager;
-import android.safetycenter.SafetyCenterManager.RefreshReason;
import android.safetycenter.config.SafetyCenterConfig;
import android.safetycenter.config.SafetySource;
import android.safetycenter.config.SafetySourcesGroup;
@@ -191,13 +189,13 @@ final class SafetyCenterConfigReader {
private SafetyCenterConfig readSafetyCenterConfig() {
InputStream in = mSafetyCenterResourcesContext.getSafetyCenterConfig();
if (in == null) {
- Log.e(TAG, "Cannot get safety center config file");
+ Log.e(TAG, "Cannot get safety center config file, safety center will be disabled.");
return null;
}
Resources resources = mSafetyCenterResourcesContext.getResources();
if (resources == null) {
- Log.e(TAG, "Cannot get safety center resources");
+ Log.e(TAG, "Cannot get safety center resources, safety center will be disabled.");
return null;
}
@@ -207,7 +205,7 @@ final class SafetyCenterConfigReader {
Log.i(TAG, "SafetyCenterConfig read successfully");
return safetyCenterConfig;
} catch (ParseException e) {
- Log.e(TAG, "Cannot read SafetyCenterConfig", e);
+ Log.e(TAG, "Cannot read SafetyCenterConfig, safety center will be disabled.", e);
return null;
}
}
@@ -315,14 +313,15 @@ final class SafetyCenterConfigReader {
continue;
}
- boolean hasEntryInRigidGroup =
+ boolean hasEntryInStatelessGroup =
safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC
&& safetySourcesGroup.getType()
- == SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_RIGID;
+ == SafetySourcesGroup
+ .SAFETY_SOURCES_GROUP_TYPE_STATELESS;
externalSafetySources.put(
safetySource.getId(),
- new ExternalSafetySource(safetySource, hasEntryInRigidGroup));
+ new ExternalSafetySource(safetySource, hasEntryInStatelessGroup));
}
}
@@ -405,12 +404,12 @@ final class SafetyCenterConfigReader {
/** A wrapper class around a {@link SafetySource} that is providing data externally. */
static final class ExternalSafetySource {
@NonNull private final SafetySource mSafetySource;
- @NonNull private final boolean mHasEntryInRigidGroup;
+ @NonNull private final boolean mHasEntryInStatelessGroup;
private ExternalSafetySource(
- @NonNull SafetySource safetySource, boolean hasEntryInRigidGroup) {
+ @NonNull SafetySource safetySource, boolean hasEntryInStatelessGroup) {
mSafetySource = safetySource;
- mHasEntryInRigidGroup = hasEntryInRigidGroup;
+ mHasEntryInStatelessGroup = hasEntryInStatelessGroup;
}
/** Returns the external {@link SafetySource}. */
@@ -420,11 +419,11 @@ final class SafetyCenterConfigReader {
}
/**
- * Returns whether the external {@link SafetySource} has an entry in a rigid {@link
+ * Returns whether the external {@link SafetySource} has an entry in a stateless {@link
* SafetySourcesGroup}.
*/
- boolean hasEntryInRigidGroup() {
- return mHasEntryInRigidGroup;
+ boolean hasEntryInStatelessGroup() {
+ return mHasEntryInStatelessGroup;
}
@Override
@@ -432,13 +431,13 @@ final class SafetyCenterConfigReader {
if (this == o) return true;
if (!(o instanceof ExternalSafetySource)) return false;
ExternalSafetySource that = (ExternalSafetySource) o;
- return mHasEntryInRigidGroup == that.mHasEntryInRigidGroup
+ return mHasEntryInStatelessGroup == that.mHasEntryInStatelessGroup
&& mSafetySource.equals(that.mSafetySource);
}
@Override
public int hashCode() {
- return Objects.hash(mSafetySource, mHasEntryInRigidGroup);
+ return Objects.hash(mSafetySource, mHasEntryInStatelessGroup);
}
@Override
@@ -446,8 +445,8 @@ final class SafetyCenterConfigReader {
return "ExternalSafetySource{"
+ "mSafetySource="
+ mSafetySource
- + ", mHasEntryInRigidGroup="
- + mHasEntryInRigidGroup
+ + ", mHasEntryInStatelessGroup="
+ + mHasEntryInStatelessGroup
+ '}';
}
}
@@ -476,32 +475,44 @@ final class SafetyCenterConfigReader {
* Returns the safety source ids associated with this broadcast in the profile owner.
*
* <p>If this list is empty, there are no sources to dispatch to in the profile owner.
- *
- * @param refreshReason the {@link RefreshReason} for the broadcast
*/
@NonNull
- List<String> getSourceIdsForProfileParent(@RefreshReason int refreshReason) {
- if (refreshReason == SafetyCenterManager.REFRESH_REASON_PAGE_OPEN) {
- return unmodifiableList(mSourceIdsForProfileParentOnPageOpen);
- }
+ List<String> getSourceIdsForProfileParent() {
return unmodifiableList(mSourceIdsForProfileParent);
}
/**
+ * Returns the safety source ids associated with this broadcast in the profile owner that
+ * have refreshOnPageOpenAllowed set to true in the XML config.
+ *
+ * <p>If this list is empty, there are no sources to dispatch to in the profile owner.
+ */
+ @NonNull
+ List<String> getSourceIdsForProfileParentOnPageOpen() {
+ return unmodifiableList(mSourceIdsForProfileParentOnPageOpen);
+ }
+
+ /**
* Returns the safety source ids associated with this broadcast in the managed profile(s).
*
* <p>If this list is empty, there are no sources to dispatch to in the managed profile(s).
- *
- * @param refreshReason the {@link RefreshReason} for the broadcast
*/
@NonNull
- List<String> getSourceIdsForManagedProfiles(@RefreshReason int refreshReason) {
- if (refreshReason == SafetyCenterManager.REFRESH_REASON_PAGE_OPEN) {
- return unmodifiableList(mSourceIdsForManagedProfilesOnPageOpen);
- }
+ List<String> getSourceIdsForManagedProfiles() {
return unmodifiableList(mSourceIdsForManagedProfiles);
}
+ /**
+ * Returns the safety source ids associated with this broadcast in the managed profile(s)
+ * that have refreshOnPageOpenAllowed set to true in the XML config.
+ *
+ * <p>If this list is empty, there are no sources to dispatch to in the managed profile(s).
+ */
+ @NonNull
+ List<String> getSourceIdsForManagedProfilesOnPageOpen() {
+ return unmodifiableList(mSourceIdsForManagedProfilesOnPageOpen);
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java
index 448321d99..85948d4fc 100644
--- a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java
+++ b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java
@@ -48,6 +48,7 @@ import android.util.Log;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
import com.android.safetycenter.internaldata.SafetyCenterEntryId;
import com.android.safetycenter.internaldata.SafetyCenterIds;
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
@@ -87,19 +88,24 @@ final class SafetyCenterDataFactory {
@NonNull private final SafetyCenterIssueCache mSafetyCenterIssueCache;
@NonNull private final SafetyCenterRepository mSafetyCenterRepository;
+ /** Only available on Android U+. */
+ @Nullable private final SafetyCenterIssueDeduplicator mSafetyCenterIssueDeduplicator;
+
SafetyCenterDataFactory(
@NonNull SafetyCenterResourcesContext safetyCenterResourcesContext,
@NonNull SafetyCenterConfigReader safetyCenterConfigReader,
@NonNull SafetyCenterRefreshTracker safetyCenterRefreshTracker,
@NonNull PendingIntentFactory pendingIntentFactory,
@NonNull SafetyCenterIssueCache safetyCenterIssueCache,
- @NonNull SafetyCenterRepository safetyCenterRepository) {
+ @NonNull SafetyCenterRepository safetyCenterRepository,
+ @Nullable SafetyCenterIssueDeduplicator safetyCenterIssueDeduplicator) {
mSafetyCenterResourcesContext = safetyCenterResourcesContext;
mSafetyCenterConfigReader = safetyCenterConfigReader;
mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
mPendingIntentFactory = pendingIntentFactory;
mSafetyCenterIssueCache = safetyCenterIssueCache;
mSafetyCenterRepository = safetyCenterRepository;
+ mSafetyCenterIssueDeduplicator = safetyCenterIssueDeduplicator;
}
/**
@@ -107,13 +113,16 @@ final class SafetyCenterDataFactory {
*/
@NonNull
static SafetyCenterData getDefaultSafetyCenterData() {
- return new SafetyCenterData(
+ SafetyCenterStatus defaultSafetyCenterStatus =
new SafetyCenterStatus.Builder("", "")
.setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN)
- .build(),
- emptyList(),
- emptyList(),
- emptyList());
+ .build();
+ if (SdkLevel.isAtLeastU()) {
+ return new SafetyCenterData.Builder(defaultSafetyCenterStatus).build();
+ } else {
+ return new SafetyCenterData(
+ defaultSafetyCenterStatus, emptyList(), emptyList(), emptyList());
+ }
}
/**
@@ -142,7 +151,7 @@ final class SafetyCenterDataFactory {
@NonNull String packageName,
@NonNull UserProfileGroup userProfileGroup,
@NonNull List<SafetySourcesGroup> safetySourcesGroups) {
- List<SafetyCenterIssueWithCategory> safetyCenterIssuesWithCategories = new ArrayList<>();
+ List<SafetyCenterIssueExtended> safetyCenterIssuesExtended = new ArrayList<>();
List<SafetyCenterEntryOrGroup> safetyCenterEntryOrGroups = new ArrayList<>();
List<SafetyCenterStaticEntryGroup> safetyCenterStaticEntryGroups = new ArrayList<>();
SafetyCenterOverallState safetyCenterOverallState = new SafetyCenterOverallState();
@@ -150,14 +159,10 @@ final class SafetyCenterDataFactory {
for (int i = 0; i < safetySourcesGroups.size(); i++) {
SafetySourcesGroup safetySourcesGroup = safetySourcesGroups.get(i);
- addSafetyCenterIssues(
- safetyCenterOverallState,
- safetyCenterIssuesWithCategories,
- safetySourcesGroup,
- userProfileGroup);
+ addSafetyCenterIssues(safetyCenterIssuesExtended, safetySourcesGroup, userProfileGroup);
int safetySourcesGroupType = safetySourcesGroup.getType();
switch (safetySourcesGroupType) {
- case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE:
+ case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL:
addSafetyCenterEntryGroup(
safetyCenterOverallState,
safetyCenterEntryOrGroups,
@@ -165,7 +170,7 @@ final class SafetyCenterDataFactory {
packageName,
userProfileGroup);
break;
- case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_RIGID:
+ case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS:
addSafetyCenterStaticEntryGroup(
safetyCenterOverallState,
safetyCenterStaticEntryGroups,
@@ -181,20 +186,37 @@ final class SafetyCenterDataFactory {
}
}
- safetyCenterIssuesWithCategories.sort(SAFETY_CENTER_ISSUES_BY_SEVERITY_DESCENDING);
+ safetyCenterIssuesExtended.sort(SAFETY_CENTER_ISSUES_BY_SEVERITY_DESCENDING);
- List<SafetyCenterIssue> safetyCenterIssues =
- new ArrayList<>(safetyCenterIssuesWithCategories.size());
- for (int i = 0; i < safetyCenterIssuesWithCategories.size(); i++) {
- safetyCenterIssues.add(safetyCenterIssuesWithCategories.get(i).getSafetyCenterIssue());
+ if (SdkLevel.isAtLeastU() && mSafetyCenterIssueDeduplicator != null) {
+ mSafetyCenterIssueDeduplicator.deduplicateIssues(safetyCenterIssuesExtended);
+ }
+
+ List<SafetyCenterIssue> safetyCenterIssues = new ArrayList<>();
+ List<SafetyCenterIssue> safetyCenterDismissedIssues = new ArrayList<>();
+ SafetyCenterIssueExtended topNonDismissedIssueExtended = null;
+
+ for (int i = 0; i < safetyCenterIssuesExtended.size(); i++) {
+ SafetyCenterIssueExtended issueExtended = safetyCenterIssuesExtended.get(i);
+ if (mSafetyCenterIssueCache.isIssueDismissed(issueExtended)) {
+ safetyCenterDismissedIssues.add(issueExtended.getSafetyCenterIssue());
+ } else {
+ safetyCenterIssues.add(issueExtended.getSafetyCenterIssue());
+ safetyCenterOverallState.addIssueOverallSeverityLevel(
+ toSafetyCenterStatusOverallSeverityLevel(
+ issueExtended.getSafetySourceIssueSeverityLevel()));
+ if (topNonDismissedIssueExtended == null) {
+ topNonDismissedIssueExtended = issueExtended;
+ }
+ }
}
int refreshStatus = mSafetyCenterRefreshTracker.getRefreshStatus();
- return new SafetyCenterData(
+ SafetyCenterStatus safetyCenterStatus =
new SafetyCenterStatus.Builder(
getSafetyCenterStatusTitle(
safetyCenterOverallState.getOverallSeverityLevel(),
- safetyCenterIssuesWithCategories,
+ topNonDismissedIssueExtended,
refreshStatus,
safetyCenterOverallState.hasSettingsToReview()),
getSafetyCenterStatusSummary(
@@ -204,10 +226,30 @@ final class SafetyCenterDataFactory {
safetyCenterOverallState.hasSettingsToReview()))
.setSeverityLevel(safetyCenterOverallState.getOverallSeverityLevel())
.setRefreshStatus(refreshStatus)
- .build(),
- safetyCenterIssues,
- safetyCenterEntryOrGroups,
- safetyCenterStaticEntryGroups);
+ .build();
+
+ if (SdkLevel.isAtLeastU()) {
+ SafetyCenterData.Builder builder = new SafetyCenterData.Builder(safetyCenterStatus);
+ for (int i = 0; i < safetyCenterIssues.size(); i++) {
+ builder.addIssue(safetyCenterIssues.get(i));
+ }
+ for (int i = 0; i < safetyCenterEntryOrGroups.size(); i++) {
+ builder.addEntryOrGroup(safetyCenterEntryOrGroups.get(i));
+ }
+ for (int i = 0; i < safetyCenterStaticEntryGroups.size(); i++) {
+ builder.addStaticEntryGroup(safetyCenterStaticEntryGroups.get(i));
+ }
+ for (int i = 0; i < safetyCenterDismissedIssues.size(); i++) {
+ builder.addDismissedIssue(safetyCenterDismissedIssues.get(i));
+ }
+ return builder.build();
+ } else {
+ return new SafetyCenterData(
+ safetyCenterStatus,
+ safetyCenterIssues,
+ safetyCenterEntryOrGroups,
+ safetyCenterStaticEntryGroups);
+ }
}
@NonNull
@@ -216,8 +258,7 @@ final class SafetyCenterDataFactory {
}
private void addSafetyCenterIssues(
- @NonNull SafetyCenterOverallState safetyCenterOverallState,
- @NonNull List<SafetyCenterIssueWithCategory> safetyCenterIssuesWithCategories,
+ @NonNull List<SafetyCenterIssueExtended> safetyCenterIssues,
@NonNull SafetySourcesGroup safetySourcesGroup,
@NonNull UserProfileGroup userProfileGroup) {
List<SafetySource> safetySources = safetySourcesGroup.getSafetySources();
@@ -229,9 +270,9 @@ final class SafetyCenterDataFactory {
}
addSafetyCenterIssues(
- safetyCenterOverallState,
- safetyCenterIssuesWithCategories,
+ safetyCenterIssues,
safetySource,
+ safetySourcesGroup,
userProfileGroup.getProfileParentUserId());
if (!SafetySources.supportsManagedProfiles(safetySource)) {
@@ -244,18 +285,18 @@ final class SafetyCenterDataFactory {
int managedRunningProfileUserId = managedRunningProfilesUserIds[j];
addSafetyCenterIssues(
- safetyCenterOverallState,
- safetyCenterIssuesWithCategories,
+ safetyCenterIssues,
safetySource,
+ safetySourcesGroup,
managedRunningProfileUserId);
}
}
}
private void addSafetyCenterIssues(
- @NonNull SafetyCenterOverallState safetyCenterOverallState,
- @NonNull List<SafetyCenterIssueWithCategory> safetyCenterIssuesWithCategories,
+ @NonNull List<SafetyCenterIssueExtended> safetyCenterIssues,
@NonNull SafetySource safetySource,
+ @NonNull SafetySourcesGroup safetySourcesGroup,
@UserIdInt int userId) {
SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId);
SafetySourceData safetySourceData = mSafetyCenterRepository.getSafetySourceData(key);
@@ -268,17 +309,19 @@ final class SafetyCenterDataFactory {
for (int i = 0; i < safetySourceIssues.size(); i++) {
SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i);
SafetyCenterIssue safetyCenterIssue =
- toSafetyCenterIssue(safetySourceIssue, safetySource, userId);
-
- if (safetyCenterIssue == null) {
- continue;
+ toSafetyCenterIssue(
+ safetySourceIssue, safetySource, safetySourcesGroup, userId);
+
+ SafetyCenterIssueExtended.Builder issueExtendedBuilder =
+ new SafetyCenterIssueExtended.Builder(
+ safetyCenterIssue,
+ safetySourceIssue.getIssueCategory(),
+ safetySourceIssue.getSeverityLevel());
+ if (SdkLevel.isAtLeastU()) {
+ issueExtendedBuilder.setDeduplicationGroup(safetySource.getDeduplicationGroup());
+ issueExtendedBuilder.setDeduplicationId(safetySourceIssue.getDeduplicationId());
}
-
- safetyCenterOverallState.addIssueOverallSeverityLevel(
- toSafetyCenterStatusOverallSeverityLevel(safetySourceIssue.getSeverityLevel()));
- safetyCenterIssuesWithCategories.add(
- SafetyCenterIssueWithCategory.create(
- safetyCenterIssue, safetySourceIssue.getIssueCategory()));
+ safetyCenterIssues.add(issueExtendedBuilder.build());
}
}
@@ -286,6 +329,7 @@ final class SafetyCenterDataFactory {
private SafetyCenterIssue toSafetyCenterIssue(
@NonNull SafetySourceIssue safetySourceIssue,
@NonNull SafetySource safetySource,
+ @NonNull SafetySourcesGroup safetySourcesGroup,
@UserIdInt int userId) {
SafetyCenterIssueId safetyCenterIssueId =
SafetyCenterIssueId.newBuilder()
@@ -298,12 +342,6 @@ final class SafetyCenterDataFactory {
.setIssueTypeId(safetySourceIssue.getIssueTypeId())
.build();
- if (mSafetyCenterIssueCache.isIssueDismissed(
- safetyCenterIssueId.getSafetyCenterIssueKey(),
- safetySourceIssue.getSeverityLevel())) {
- return null;
- }
-
List<SafetySourceIssue.Action> safetySourceIssueActions = safetySourceIssue.getActions();
List<SafetyCenterIssue.Action> safetyCenterIssueActions =
new ArrayList<>(safetySourceIssueActions.size());
@@ -318,16 +356,27 @@ final class SafetyCenterDataFactory {
int safetyCenterIssueSeverityLevel =
toSafetyCenterIssueSeverityLevel(safetySourceIssue.getSeverityLevel());
- return new SafetyCenterIssue.Builder(
- SafetyCenterIds.encodeToString(safetyCenterIssueId),
- safetySourceIssue.getTitle(),
- safetySourceIssue.getSummary())
- .setSeverityLevel(safetyCenterIssueSeverityLevel)
- .setShouldConfirmDismissal(
- safetyCenterIssueSeverityLevel > SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
- .setSubtitle(safetySourceIssue.getSubtitle())
- .setActions(safetyCenterIssueActions)
- .build();
+ SafetyCenterIssue.Builder safetyCenterIssueBuilder =
+ new SafetyCenterIssue.Builder(
+ SafetyCenterIds.encodeToString(safetyCenterIssueId),
+ safetySourceIssue.getTitle(),
+ safetySourceIssue.getSummary())
+ .setSeverityLevel(safetyCenterIssueSeverityLevel)
+ .setShouldConfirmDismissal(
+ safetyCenterIssueSeverityLevel
+ > SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+ .setSubtitle(safetySourceIssue.getSubtitle())
+ .setActions(safetyCenterIssueActions);
+ if (SdkLevel.isAtLeastU()) {
+ CharSequence issueAttributionTitle =
+ TextUtils.isEmpty(safetySourceIssue.getAttributionTitle())
+ ? mSafetyCenterResourcesContext.getOptionalString(
+ safetySourcesGroup.getTitleResId())
+ : safetySourceIssue.getAttributionTitle();
+ safetyCenterIssueBuilder.setAttributionTitle(issueAttributionTitle);
+ safetyCenterIssueBuilder.setGroupId(safetySourcesGroup.getId());
+ }
+ return safetyCenterIssueBuilder.build();
}
@NonNull
@@ -407,7 +456,7 @@ final class SafetyCenterDataFactory {
return;
}
- if (entries.size() == 1) {
+ if (!SafetyCenterFlags.getShowSubpages() && entries.size() == 1) {
safetyCenterEntryOrGroups.add(new SafetyCenterEntryOrGroup(entries.get(0)));
return;
}
@@ -634,7 +683,7 @@ final class SafetyCenterDataFactory {
case SafetySource.SAFETY_SOURCE_TYPE_STATIC:
return toDefaultSafetyCenterEntry(
safetySource,
- defaultPackageName,
+ getStaticSourcePackageNameOrDefault(safetySource, defaultPackageName),
SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED,
SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON,
userId,
@@ -819,7 +868,7 @@ final class SafetyCenterDataFactory {
case SafetySource.SAFETY_SOURCE_TYPE_STATIC:
return toDefaultSafetyCenterStaticEntry(
safetySource,
- defaultPackageName,
+ getStaticSourcePackageNameOrDefault(safetySource, defaultPackageName),
userId,
isUserManaged,
isManagedUserRunning);
@@ -885,6 +934,19 @@ final class SafetyCenterDataFactory {
return safetySourceData.getStatus();
}
+ @NonNull
+ private static String getStaticSourcePackageNameOrDefault(
+ @NonNull SafetySource safetySource, @NonNull String defaultPackageName) {
+ if (!SdkLevel.isAtLeastU()) {
+ return defaultPackageName;
+ }
+ String sourcePackageName = safetySource.getOptionalPackageName();
+ if (sourcePackageName == null) {
+ return defaultPackageName;
+ }
+ return sourcePackageName;
+ }
+
@SafetyCenterStatus.OverallSeverityLevel
private static int toSafetyCenterStatusOverallSeverityLevel(
@SafetySourceData.SeverityLevel int safetySourceSeverityLevel) {
@@ -1004,7 +1066,7 @@ final class SafetyCenterDataFactory {
@NonNull
private String getSafetyCenterStatusTitle(
@SafetyCenterStatus.OverallSeverityLevel int overallSeverityLevel,
- @NonNull List<SafetyCenterIssueWithCategory> safetyCenterIssuesWithCategories,
+ @Nullable SafetyCenterIssueExtended topNonDismissedIssueExtended,
@SafetyCenterStatus.RefreshStatus int refreshStatus,
boolean hasSettingsToReview) {
boolean overallSeverityUnknown =
@@ -1025,16 +1087,22 @@ final class SafetyCenterDataFactory {
"overall_severity_level_ok_title");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
return getStatusTitleFromIssueCategories(
- safetyCenterIssuesWithCategories,
+ topNonDismissedIssueExtended,
"overall_severity_level_device_recommendation_title",
"overall_severity_level_account_recommendation_title",
- "overall_severity_level_safety_recommendation_title");
+ "overall_severity_level_safety_recommendation_title",
+ "overall_severity_level_data_recommendation_title",
+ "overall_severity_level_passwords_recommendation_title",
+ "overall_severity_level_personal_recommendation_title");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
return getStatusTitleFromIssueCategories(
- safetyCenterIssuesWithCategories,
+ topNonDismissedIssueExtended,
"overall_severity_level_critical_device_warning_title",
"overall_severity_level_critical_account_warning_title",
- "overall_severity_level_critical_safety_warning_title");
+ "overall_severity_level_critical_safety_warning_title",
+ "overall_severity_level_critical_data_warning_title",
+ "overall_severity_level_critical_passwords_warning_title",
+ "overall_severity_level_critical_personal_warning_title");
}
Log.w(TAG, "Unexpected SafetyCenterStatus.OverallSeverityLevel: " + overallSeverityLevel);
@@ -1043,16 +1111,19 @@ final class SafetyCenterDataFactory {
@NonNull
private String getStatusTitleFromIssueCategories(
- @NonNull List<SafetyCenterIssueWithCategory> safetyCenterIssuesWithCategories,
+ @Nullable SafetyCenterIssueExtended topNonDismissedIssueExtended,
@NonNull String deviceResourceName,
@NonNull String accountResourceName,
- @NonNull String generalResourceName) {
+ @NonNull String generalResourceName,
+ @NonNull String dataResourceName,
+ @NonNull String passwordsResourceName,
+ @NonNull String personalSafetyResourceName) {
String generalString = mSafetyCenterResourcesContext.getStringByName(generalResourceName);
- if (safetyCenterIssuesWithCategories.isEmpty()) {
+ if (topNonDismissedIssueExtended == null) {
Log.w(TAG, "No safety center issues found in a non-green status");
return generalString;
}
- int issueCategory = safetyCenterIssuesWithCategories.get(0).getSafetyCenterIssueCategory();
+ int issueCategory = topNonDismissedIssueExtended.getSafetySourceIssueCategory();
switch (issueCategory) {
case SafetySourceIssue.ISSUE_CATEGORY_DEVICE:
return mSafetyCenterResourcesContext.getStringByName(deviceResourceName);
@@ -1061,6 +1132,17 @@ final class SafetyCenterDataFactory {
case SafetySourceIssue.ISSUE_CATEGORY_GENERAL:
return generalString;
}
+ if (SdkLevel.isAtLeastU()) {
+ switch (issueCategory) {
+ case SafetySourceIssue.ISSUE_CATEGORY_DATA:
+ return mSafetyCenterResourcesContext.getStringByName(dataResourceName);
+ case SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS:
+ return mSafetyCenterResourcesContext.getStringByName(passwordsResourceName);
+ case SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY:
+ return mSafetyCenterResourcesContext.getStringByName(
+ personalSafetyResourceName);
+ }
+ }
Log.w(TAG, "Unexpected SafetySourceIssue.IssueCategory: " + issueCategory);
return generalString;
@@ -1153,45 +1235,15 @@ final class SafetyCenterDataFactory {
return SafetySourceKey.of(id.getSafetySourceId(), id.getUserId());
}
- /** Wrapper that encapsulates both {@link SafetyCenterIssue} and its category. */
- private static final class SafetyCenterIssueWithCategory {
- @NonNull private final SafetyCenterIssue mSafetyCenterIssue;
- @SafetySourceIssue.IssueCategory private final int mSafetyCenterIssueCategory;
-
- private SafetyCenterIssueWithCategory(
- @NonNull SafetyCenterIssue safetyCenterIssue,
- @SafetySourceIssue.IssueCategory int safetyCenterIssueCategory) {
- this.mSafetyCenterIssue = safetyCenterIssue;
- this.mSafetyCenterIssueCategory = safetyCenterIssueCategory;
- }
-
- @NonNull
- private SafetyCenterIssue getSafetyCenterIssue() {
- return mSafetyCenterIssue;
- }
-
- @SafetySourceIssue.IssueCategory
- private int getSafetyCenterIssueCategory() {
- return mSafetyCenterIssueCategory;
- }
-
- private static SafetyCenterIssueWithCategory create(
- @NonNull SafetyCenterIssue safetyCenterIssue,
- @SafetySourceIssue.IssueCategory int safetyCenterIssueCategory) {
- return new SafetyCenterIssueWithCategory(safetyCenterIssue, safetyCenterIssueCategory);
- }
- }
-
- /** A comparator to order {@link SafetyCenterIssueWithCategory} by severity level descending. */
+ /** A comparator to order {@link SafetyCenterIssueExtended} by severity level descending. */
private static final class SafetyCenterIssuesBySeverityDescending
- implements Comparator<SafetyCenterIssueWithCategory> {
+ implements Comparator<SafetyCenterIssueExtended> {
private SafetyCenterIssuesBySeverityDescending() {}
@Override
public int compare(
- @NonNull SafetyCenterIssueWithCategory left,
- @NonNull SafetyCenterIssueWithCategory right) {
+ @NonNull SafetyCenterIssueExtended left, @NonNull SafetyCenterIssueExtended right) {
return Integer.compare(
right.getSafetyCenterIssue().getSeverityLevel(),
left.getSafetyCenterIssue().getSeverityLevel());
diff --git a/service/java/com/android/safetycenter/SafetyCenterFlags.java b/service/java/com/android/safetycenter/SafetyCenterFlags.java
index 3ce2a89aa..0e508fe5b 100644
--- a/service/java/com/android/safetycenter/SafetyCenterFlags.java
+++ b/service/java/com/android/safetycenter/SafetyCenterFlags.java
@@ -30,6 +30,8 @@ import android.util.Log;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+
import java.io.PrintWriter;
import java.time.Duration;
@@ -80,6 +82,11 @@ final class SafetyCenterFlags {
private static final String PROPERTY_ALLOW_STATSD_LOGGING_IN_TESTS =
"safety_center_allow_statsd_logging_in_tests";
+ private static final String PROPERTY_SHOW_SUBPAGES = "safety_center_show_subpages";
+
+ private static final String PROPERTY_OVERRIDE_REFRESH_ON_PAGE_OPEN_SOURCES =
+ "safety_center_override_refresh_on_page_open_sources";
+
private static final Duration REFRESH_SOURCES_TIMEOUT_DEFAULT_DURATION = Duration.ofSeconds(15);
private static final Duration RESOLVING_ACTION_TIMEOUT_DEFAULT_DURATION =
@@ -112,6 +119,11 @@ final class SafetyCenterFlags {
fout, PROPERTY_REFRESH_SOURCES_TIMEOUTS_MILLIS, getRefreshSourcesTimeoutsMillis());
printFlag(fout, PROPERTY_ISSUE_CATEGORY_ALLOWLISTS, getIssueCategoryAllowlists());
printFlag(fout, PROPERTY_ALLOW_STATSD_LOGGING_IN_TESTS, getAllowStatsdLoggingInTests());
+ printFlag(fout, PROPERTY_SHOW_SUBPAGES, getShowSubpages());
+ printFlag(
+ fout,
+ PROPERTY_OVERRIDE_REFRESH_ON_PAGE_OPEN_SOURCES,
+ getOverrideRefreshOnPageOpenSourceIds());
fout.println();
}
@@ -314,6 +326,23 @@ final class SafetyCenterFlags {
return getBoolean(PROPERTY_ALLOW_STATSD_LOGGING_IN_TESTS, false);
}
+ /**
+ * Returns whether to show subpages in the Safety Center UI for Android-U instead of the
+ * expand-and-collapse list implementation.
+ */
+ static boolean getShowSubpages() {
+ // TODO(b/260822348): Add CTS test to verify that the flag is disabled when turned on for T
+ return SdkLevel.isAtLeastU() && getBoolean(PROPERTY_SHOW_SUBPAGES, true);
+ }
+
+ /**
+ * Returns an array of safety source Ids that will be refreshed on page open, even if
+ * refreshOnPageOpenAllowed is false (the default) in the XML config.
+ */
+ static ArraySet<String> getOverrideRefreshOnPageOpenSourceIds() {
+ return getCommaSeparatedStrings(PROPERTY_OVERRIDE_REFRESH_ON_PAGE_OPEN_SOURCES);
+ }
+
@NonNull
private static Duration getDuration(@NonNull String property, @NonNull Duration defaultValue) {
return Duration.ofMillis(getLong(property, defaultValue.toMillis()));
diff --git a/service/java/com/android/safetycenter/SafetyCenterIssueCache.java b/service/java/com/android/safetycenter/SafetyCenterIssueCache.java
index 388ddf89d..ed3d997bf 100644
--- a/service/java/com/android/safetycenter/SafetyCenterIssueCache.java
+++ b/service/java/com/android/safetycenter/SafetyCenterIssueCache.java
@@ -111,6 +111,13 @@ final class SafetyCenterIssueCache {
return result;
}
+ /** Same as {@link SafetyCenterIssueCache#isIssueDismissed(SafetyCenterIssueKey, int)}. */
+ boolean isIssueDismissed(@NonNull SafetyCenterIssueExtended safetyCenterIssue) {
+ return isIssueDismissed(
+ safetyCenterIssue.getSafetyCenterIssueKey(),
+ safetyCenterIssue.getSafetySourceIssueSeverityLevel());
+ }
+
/**
* Returns {@code true} if the issue with the given key and severity level is currently
* dismissed.
@@ -172,6 +179,25 @@ final class SafetyCenterIssueCache {
}
/**
+ * Copy dismissal data from one issue to the other.
+ *
+ * <p>This will align dismissal state of these issues, unless issues are of different
+ * severities, in which case they can potentially differ in resurface times.
+ */
+ void copyDismissalData(
+ @NonNull SafetyCenterIssueKey keyFrom, @NonNull SafetyCenterIssueKey keyTo) {
+ IssueData dataFrom = getOrWarn(keyFrom, "copying dismissed data");
+ IssueData dataTo = getOrWarn(keyTo, "copying dismissed data");
+ if (dataFrom == null || dataTo == null) {
+ return;
+ }
+
+ dataTo.setDismissedAt(dataFrom.getDismissedAt());
+ dataTo.setDismissCount(dataFrom.getDismissCount());
+ mIsDirty = true;
+ }
+
+ /**
* Marks the notification (if any) of the issue with the given key as dismissed.
*
* <p>The issue itself is <strong>not</strong> marked as dismissed and its warning card can
diff --git a/service/java/com/android/safetycenter/SafetyCenterIssueDeduplicator.java b/service/java/com/android/safetycenter/SafetyCenterIssueDeduplicator.java
new file mode 100644
index 000000000..50a8f0e51
--- /dev/null
+++ b/service/java/com/android/safetycenter/SafetyCenterIssueDeduplicator.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.safetycenter;
+
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+import android.annotation.NonNull;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+/** Deduplicates issues based on deduplication info provided by the source and the issue. */
+@RequiresApi(UPSIDE_DOWN_CAKE)
+@NotThreadSafe
+final class SafetyCenterIssueDeduplicator {
+
+ @NonNull private final SafetyCenterIssueCache mSafetyCenterIssueCache;
+
+ SafetyCenterIssueDeduplicator(@NonNull SafetyCenterIssueCache safetyCenterIssueCache) {
+ this.mSafetyCenterIssueCache = safetyCenterIssueCache;
+ }
+
+ /**
+ * Accepts a list of issues sorted by priority and filters out duplicates.
+ *
+ * <p>Issues are considered duplicate if they have the same deduplication id and were sent by
+ * sources which are part of the same deduplication group. All but the highest priority
+ * duplicate issue will be filtered out.
+ *
+ * <p>In case any issue, in the bucket of duplicate issues, was dismissed, all issues of the
+ * same or lower severity will be dismissed as well.
+ *
+ * <p>This method modifies the given argument.
+ */
+ void deduplicateIssues(@NonNull List<SafetyCenterIssueExtended> sortedIssues) {
+ // (dedup key) -> list(issues)
+ ArrayMap<DeduplicationKey, List<SafetyCenterIssueExtended>> dedupBuckets =
+ createDedupBuckets(sortedIssues);
+
+ dismissDuplicateIssuesOfDismissedIssue(dedupBuckets);
+
+ ArraySet<SafetyCenterIssueKey> duplicatesToFilterOut =
+ getDuplicatesToFilterOut(dedupBuckets);
+
+ Iterator<SafetyCenterIssueExtended> it = sortedIssues.iterator();
+ while (it.hasNext()) {
+ if (duplicatesToFilterOut.contains(it.next().getSafetyCenterIssueKey())) {
+ it.remove();
+ }
+ }
+ }
+
+ /**
+ * Handles dismissals logic: in each bucket, dismissal details of the top (highest priority)
+ * dismissed issue will be copied to all other duplicate issues in that bucket, that are of
+ * equal or lower severity (not priority).
+ */
+ private void dismissDuplicateIssuesOfDismissedIssue(
+ @NonNull ArrayMap<DeduplicationKey, List<SafetyCenterIssueExtended>> dedupBuckets) {
+ for (int i = 0; i < dedupBuckets.size(); i++) {
+ List<SafetyCenterIssueExtended> duplicates = dedupBuckets.valueAt(i);
+ SafetyCenterIssueExtended topDismissed = getHighestPriorityDismissedIssue(duplicates);
+ alignDismissalsDataWithinBucket(topDismissed, duplicates);
+ }
+ }
+
+ /**
+ * Dismisses all issues of lower or equal severity relative to the given top dismissed issue in
+ * the bucket.
+ */
+ private void alignDismissalsDataWithinBucket(
+ @Nullable SafetyCenterIssueExtended topDismissed,
+ @NonNull List<SafetyCenterIssueExtended> duplicates) {
+ if (topDismissed == null) {
+ return;
+ }
+ SafetyCenterIssueKey topDismissedIssueKey = topDismissed.getSafetyCenterIssueKey();
+ int topDismissedSeverityLevel = topDismissed.getSafetyCenterIssue().getSeverityLevel();
+ for (int i = 0; i < duplicates.size(); i++) {
+ SafetyCenterIssueExtended issue = duplicates.get(i);
+ SafetyCenterIssueKey issueKey = issue.getSafetyCenterIssueKey();
+ if (!issueKey.equals(topDismissedIssueKey)
+ && issue.getSafetyCenterIssue().getSeverityLevel()
+ <= topDismissedSeverityLevel) {
+ // all duplicate issues should have same dismissals data as top dismissed issue
+ mSafetyCenterIssueCache.copyDismissalData(topDismissedIssueKey, issueKey);
+ }
+ }
+ }
+
+ @Nullable
+ private SafetyCenterIssueExtended getHighestPriorityDismissedIssue(
+ @NonNull List<SafetyCenterIssueExtended> duplicates) {
+ for (int i = 0; i < duplicates.size(); i++) {
+ SafetyCenterIssueExtended issue = duplicates.get(i);
+ if (mSafetyCenterIssueCache.isIssueDismissed(issue)) {
+ return issue;
+ }
+ }
+
+ return null;
+ }
+
+ /** Returns a set of duplicate issues that need to be filtered out. */
+ @NonNull
+ private static ArraySet<SafetyCenterIssueKey> getDuplicatesToFilterOut(
+ @NonNull ArrayMap<DeduplicationKey, List<SafetyCenterIssueExtended>> dedupBuckets) {
+ ArraySet<SafetyCenterIssueKey> duplicatesToFilterOut = new ArraySet<>();
+
+ for (int i = 0; i < dedupBuckets.size(); i++) {
+ List<SafetyCenterIssueExtended> duplicates = dedupBuckets.valueAt(i);
+ // all but the top one in the bucket
+ for (int j = 1; j < duplicates.size(); j++) {
+ duplicatesToFilterOut.add(duplicates.get(j).getSafetyCenterIssueKey());
+ }
+ }
+
+ return duplicatesToFilterOut;
+ }
+
+ /** Returns a mapping (dedup key) -> list(issues). */
+ @NonNull
+ private static ArrayMap<DeduplicationKey, List<SafetyCenterIssueExtended>> createDedupBuckets(
+ @NonNull List<SafetyCenterIssueExtended> sortedIssues) {
+ ArrayMap<DeduplicationKey, List<SafetyCenterIssueExtended>> dedupBuckets = new ArrayMap<>();
+
+ for (int i = 0; i < sortedIssues.size(); i++) {
+ SafetyCenterIssueExtended issue = sortedIssues.get(i);
+ DeduplicationKey dedupKey = getDedupKey(issue);
+ if (dedupKey == null) {
+ continue;
+ }
+
+ // each bucket will remain sorted
+ List<SafetyCenterIssueExtended> bucket =
+ dedupBuckets.getOrDefault(dedupKey, new ArrayList<>());
+ bucket.add(issue);
+
+ dedupBuckets.put(dedupKey, bucket);
+ }
+
+ return dedupBuckets;
+ }
+
+ /** Returns deduplication key of the given issue. */
+ @Nullable
+ private static DeduplicationKey getDedupKey(@NonNull SafetyCenterIssueExtended issue) {
+ if (issue.getDeduplicationGroup() == null || issue.getDeduplicationId() == null) {
+ return null;
+ }
+ return new DeduplicationKey(issue.getDeduplicationGroup(), issue.getDeduplicationId());
+ }
+
+ private static class DeduplicationKey {
+
+ @NonNull private final String mDeduplicationGroup;
+ @NonNull private final String mDeduplicationId;
+
+ private DeduplicationKey(
+ @NonNull String deduplicationGroup, @NonNull String deduplicationId) {
+ mDeduplicationGroup = deduplicationGroup;
+ mDeduplicationId = deduplicationId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDeduplicationGroup, mDeduplicationId);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DeduplicationKey)) return false;
+ DeduplicationKey dedupKey = (DeduplicationKey) o;
+ return mDeduplicationGroup.equals(dedupKey.mDeduplicationGroup)
+ && mDeduplicationId.equals(dedupKey.mDeduplicationId);
+ }
+ }
+}
diff --git a/service/java/com/android/safetycenter/SafetyCenterIssueExtended.java b/service/java/com/android/safetycenter/SafetyCenterIssueExtended.java
new file mode 100644
index 000000000..60e0b5bd3
--- /dev/null
+++ b/service/java/com/android/safetycenter/SafetyCenterIssueExtended.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.safetycenter;
+
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.safetycenter.SafetyCenterIssue;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceIssue;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.safetycenter.internaldata.SafetyCenterIds;
+import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+/** Wrapper that contains a {@link SafetyCenterIssue} and some extra information about it. */
+@RequiresApi(TIRAMISU)
+final class SafetyCenterIssueExtended {
+ @NonNull private final SafetyCenterIssue mSafetyCenterIssue;
+ @NonNull private final SafetyCenterIssueKey mSafetyCenterIssueKey;
+ @SafetySourceIssue.IssueCategory private final int mSafetySourceIssueCategory;
+ @SafetySourceData.SeverityLevel private final int mSafetySourceIssueSeverityLevel;
+
+ // Deduplication info, only available on Android U+.
+ @Nullable private final String mDeduplicationGroup;
+ @Nullable private final String mDeduplicationId;
+
+ private SafetyCenterIssueExtended(
+ @NonNull SafetyCenterIssue safetyCenterIssue,
+ @NonNull SafetyCenterIssueKey safetyCenterIssueKey,
+ @SafetySourceIssue.IssueCategory int safetySourceIssueCategory,
+ int safetySourceIssueSeverityLevel,
+ @Nullable String deduplicationGroup,
+ @Nullable String deduplicationId) {
+ this.mSafetyCenterIssue = safetyCenterIssue;
+ this.mSafetyCenterIssueKey = safetyCenterIssueKey;
+ this.mSafetySourceIssueCategory = safetySourceIssueCategory;
+ this.mSafetySourceIssueSeverityLevel = safetySourceIssueSeverityLevel;
+ this.mDeduplicationGroup = deduplicationGroup;
+ this.mDeduplicationId = deduplicationId;
+ }
+
+ /** Returns the {@link SafetyCenterIssue} it contains. */
+ @NonNull
+ SafetyCenterIssue getSafetyCenterIssue() {
+ return mSafetyCenterIssue;
+ }
+
+ /** Returns the {@link SafetyCenterIssueKey} related to this issue. */
+ @NonNull
+ SafetyCenterIssueKey getSafetyCenterIssueKey() {
+ return mSafetyCenterIssueKey;
+ }
+
+ /** Returns the {@link SafetySourceIssue.IssueCategory} related to this issue. */
+ @SafetySourceIssue.IssueCategory
+ int getSafetySourceIssueCategory() {
+ return mSafetySourceIssueCategory;
+ }
+
+ /** Returns the {@link SafetySourceData.SeverityLevel} related to this issue. */
+ @SafetySourceData.SeverityLevel
+ int getSafetySourceIssueSeverityLevel() {
+ return mSafetySourceIssueSeverityLevel;
+ }
+
+ /** Returns the deduplication group related to this issue. */
+ @Nullable
+ String getDeduplicationGroup() {
+ return mDeduplicationGroup;
+ }
+
+ /** Returns the deduplication id related to this issue. */
+ @Nullable
+ String getDeduplicationId() {
+ return mDeduplicationId;
+ }
+
+ /** Builder for the {@link SafetyCenterIssueExtended}. */
+ @NotThreadSafe
+ static final class Builder {
+ @NonNull private final SafetyCenterIssue mSafetyCenterIssue;
+ @SafetySourceIssue.IssueCategory private final int mSafetySourceIssueCategory;
+ @SafetySourceData.SeverityLevel private final int mSafetySourceIssueSeverityLevel;
+
+ @Nullable private String mDeduplicationGroup;
+ @Nullable private String mDeduplicationId;
+
+ /** Constructs a new instance of the builder. */
+ Builder(
+ @NonNull SafetyCenterIssue safetyCenterIssue,
+ @SafetySourceIssue.IssueCategory int safetySourceIssueCategory,
+ @SafetySourceData.SeverityLevel int safetySourceIssueSeverityLevel) {
+ this.mSafetyCenterIssue = safetyCenterIssue;
+ this.mSafetySourceIssueCategory = safetySourceIssueCategory;
+ this.mSafetySourceIssueSeverityLevel = safetySourceIssueSeverityLevel;
+ }
+
+ /** Sets the deduplication group for this issue. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ Builder setDeduplicationGroup(@Nullable String deduplicationGroup) {
+ this.mDeduplicationGroup = deduplicationGroup;
+ return this;
+ }
+
+ /** Sets the deduplication id for this issue. */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ Builder setDeduplicationId(@Nullable String deduplicationId) {
+ this.mDeduplicationId = deduplicationId;
+ return this;
+ }
+
+ /** Returns a new {@link SafetyCenterIssueExtended} based on previously given data. */
+ SafetyCenterIssueExtended build() {
+ return new SafetyCenterIssueExtended(
+ mSafetyCenterIssue,
+ SafetyCenterIds.issueIdFromString(mSafetyCenterIssue.getId())
+ .getSafetyCenterIssueKey(),
+ mSafetySourceIssueCategory,
+ mSafetySourceIssueSeverityLevel,
+ mDeduplicationGroup,
+ mDeduplicationId);
+ }
+ }
+}
diff --git a/service/java/com/android/safetycenter/SafetyCenterListeners.java b/service/java/com/android/safetycenter/SafetyCenterListeners.java
index b5a9fb8d3..95518bf96 100644
--- a/service/java/com/android/safetycenter/SafetyCenterListeners.java
+++ b/service/java/com/android/safetycenter/SafetyCenterListeners.java
@@ -224,31 +224,51 @@ final class SafetyCenterListeners {
try {
while (i > 0) {
i--;
- OnSafetyCenterDataChangedListenerWrapper listenerWrapper =
+ deliverUpdateForListenerWrapper(
(OnSafetyCenterDataChangedListenerWrapper)
- listenersForUserId.getBroadcastItem(i);
- SafetyCenterData safetyCenterData = null;
- if (updateSafetyCenterData) {
- String packageName = listenerWrapper.getPackageName();
- SafetyCenterData cachedSafetyCenterData =
- safetyCenterDataCache.get(packageName);
- if (cachedSafetyCenterData != null) {
- safetyCenterData = cachedSafetyCenterData;
- } else {
- safetyCenterData =
- mSafetyCenterDataFactory.assembleSafetyCenterData(
- packageName, userProfileGroup);
- safetyCenterDataCache.put(packageName, safetyCenterData);
- }
- }
- deliverUpdateForListener(
- listenerWrapper, safetyCenterData, safetyCenterErrorDetails);
+ listenersForUserId.getBroadcastItem(i),
+ userProfileGroup,
+ safetyCenterDataCache,
+ updateSafetyCenterData,
+ safetyCenterErrorDetails);
}
} finally {
listenersForUserId.finishBroadcast();
}
}
+ private void deliverUpdateForListenerWrapper(
+ OnSafetyCenterDataChangedListenerWrapper listenerWrapper,
+ @NonNull UserProfileGroup userProfileGroup,
+ @NonNull ArrayMap<String, SafetyCenterData> safetyCenterDataCache,
+ boolean updateSafetyCenterData,
+ @Nullable SafetyCenterErrorDetails safetyCenterErrorDetails) {
+ SafetyCenterData safetyCenterData = null;
+ if (updateSafetyCenterData) {
+ safetyCenterData =
+ assembleSafetyCenterDataIfAbsent(
+ safetyCenterDataCache,
+ listenerWrapper.getPackageName(),
+ userProfileGroup);
+ }
+ deliverUpdateForListener(listenerWrapper, safetyCenterData, safetyCenterErrorDetails);
+ }
+
+ @NonNull
+ private SafetyCenterData assembleSafetyCenterDataIfAbsent(
+ @NonNull ArrayMap<String, SafetyCenterData> safetyCenterDataCache,
+ @NonNull String packageName,
+ @NonNull UserProfileGroup userProfileGroup) {
+ SafetyCenterData cachedSafetyCenterData = safetyCenterDataCache.get(packageName);
+ if (cachedSafetyCenterData != null) {
+ return cachedSafetyCenterData;
+ }
+ SafetyCenterData safetyCenterData =
+ mSafetyCenterDataFactory.assembleSafetyCenterData(packageName, userProfileGroup);
+ safetyCenterDataCache.put(packageName, safetyCenterData);
+ return safetyCenterData;
+ }
+
/** Dumps state for debugging purposes. */
void dump(@NonNull PrintWriter fout) {
int userIdCount = mSafetyCenterDataChangedListeners.size();
diff --git a/service/java/com/android/safetycenter/SafetyCenterNotificationFactory.java b/service/java/com/android/safetycenter/SafetyCenterNotificationFactory.java
index a82a73312..a6d1124dc 100644
--- a/service/java/com/android/safetycenter/SafetyCenterNotificationFactory.java
+++ b/service/java/com/android/safetycenter/SafetyCenterNotificationFactory.java
@@ -33,8 +33,12 @@ import android.util.Log;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
+import java.util.List;
+
/**
* Factory that builds {@link Notification} objects from {@link SafetySourceIssue} instances with
* appropriate {@link PendingIntent}s for click and dismiss callbacks.
@@ -70,18 +74,36 @@ final class SafetyCenterNotificationFactory {
return null;
}
+ CharSequence title = issue.getTitle();
+ CharSequence text = issue.getSummary();
+ List<SafetySourceIssue.Action> issueActions = issue.getActions();
+
+ if (SdkLevel.isAtLeastU()) {
+ SafetySourceIssue.Notification customNotification = issue.getCustomNotification();
+ if (customNotification != null) {
+ title = customNotification.getTitle();
+ text = customNotification.getText();
+ issueActions = customNotification.getActions();
+ }
+ }
+
Notification.Builder builder =
new Notification.Builder(mContext, channelId)
// TODO(b/259399024): Use correct icon here
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setExtras(getNotificationExtras())
- .setContentTitle(issue.getTitle())
- .setContentText(issue.getSummary())
+ .setContentTitle(title)
+ .setContentText(text)
.setContentIntent(newSafetyCenterPendingIntent(issue))
.setDeleteIntent(
SafetyCenterNotificationReceiver.newNotificationDismissedIntent(
mContext, issueKey));
- // TODO(b/260084296): Include issue actions on notifications;
+
+ for (int i = 0; i < issueActions.size(); i++) {
+ Notification.Action notificationAction =
+ toNotificationAction(issueKey, issueActions.get(i));
+ builder.addAction(notificationAction);
+ }
return builder.build();
}
@@ -115,6 +137,26 @@ final class SafetyCenterNotificationFactory {
return extras;
}
+ @NonNull
+ private Notification.Action toNotificationAction(
+ @NonNull SafetyCenterIssueKey issueKey, @NonNull SafetySourceIssue.Action issueAction) {
+ // We do not use the action's PendingIntent directly here instead we build a new PI which
+ // will be handled by our SafetyCenterNotificationReceiver which will in turn dispatch
+ // the source-provided action PI. This ensures that action execution is consistent across
+ // between Safety Center UI and notifications, for example executing an action from a
+ // notification will send an "action in-flight" update to any current listeners.
+ SafetyCenterIssueActionId issueActionId =
+ SafetyCenterIssueActionId.newBuilder()
+ .setSafetyCenterIssueKey(issueKey)
+ .setSafetySourceIssueActionId(issueAction.getId())
+ .build();
+ PendingIntent receiverPendingIntent =
+ SafetyCenterNotificationReceiver.newNotificationActionClickedIntent(
+ mContext, issueActionId);
+ return new Notification.Action.Builder(null, issueAction.getLabel(), receiverPendingIntent)
+ .build();
+ }
+
/**
* Creates a {@link NotificationChannel} using the given {@link NotificationManager}, dropping
* any calling identity so that it can be unblockable. Returns the new channel's ID if it was
diff --git a/service/java/com/android/safetycenter/SafetyCenterNotificationReceiver.java b/service/java/com/android/safetycenter/SafetyCenterNotificationReceiver.java
index 2ae580332..94deeecbc 100644
--- a/service/java/com/android/safetycenter/SafetyCenterNotificationReceiver.java
+++ b/service/java/com/android/safetycenter/SafetyCenterNotificationReceiver.java
@@ -29,7 +29,9 @@ import android.util.Log;
import androidx.annotation.RequiresApi;
+import com.android.internal.annotations.GuardedBy;
import com.android.safetycenter.internaldata.SafetyCenterIds;
+import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
/**
@@ -37,8 +39,9 @@ import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
* notifications e.g. when a notification is dismissed.
*
* <p>Use {@link #register(Context)} to register this receiver with the correct {@link IntentFilter}
- * and use the {@link #newNotificationDismissedIntent(Context, SafetyCenterIssueKey)} factory method
- * to create new intents for this receiver.
+ * and use the {@link #newNotificationDismissedIntent(Context, SafetyCenterIssueKey)} and {@link
+ * #newNotificationActionClickedIntent(Context, SafetyCenterIssueActionId)} factory methods to
+ * create new {@link PendingIntent} instances for this receiver.
*/
@RequiresApi(TIRAMISU)
final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
@@ -47,13 +50,17 @@ final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
private static final String ACTION_NOTIFICATION_DISMISSED =
"com.android.safetycenter.action.NOTIFICATION_DISMISSED";
+ private static final String ACTION_NOTIFICATION_ACTION_CLICKED =
+ "com.android.safetycenter.action.NOTIFICATION_ACTION_CLICKED";
private static final String EXTRA_ISSUE_KEY = "com.android.safetycenter.extra.ISSUE_KEY";
+ private static final String EXTRA_ISSUE_ACTION_ID =
+ "com.android.safetycenter.extra.ISSUE_ACTION_ID";
private static final int REQUEST_CODE_UNUSED = 0;
/**
- * Creates a {@code PendingIntent} for a broadcast {@code Intent} which will start this receiver
- * and cause it to handle a notification dismissal event.
+ * Creates a broadcast {@code PendingIntent} for this receiver which will handle a Safety Center
+ * notification being dismissed.
*/
@NonNull
static PendingIntent newNotificationDismissedIntent(
@@ -67,6 +74,24 @@ final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
context, REQUEST_CODE_UNUSED, intent, flags);
}
+ /**
+ * Creates a broadcast {@code PendingIntent} for this receiver which will handle a Safety Center
+ * notification action being clicked.
+ *
+ * <p>Safety Center notification actions correspond to Safety Center issue actions.
+ */
+ @NonNull
+ static PendingIntent newNotificationActionClickedIntent(
+ @NonNull Context context, @NonNull SafetyCenterIssueActionId issueActionId) {
+ String issueActionIdString = SafetyCenterIds.encodeToString(issueActionId);
+ Intent intent = new Intent(ACTION_NOTIFICATION_ACTION_CLICKED);
+ intent.putExtra(EXTRA_ISSUE_ACTION_ID, issueActionIdString);
+ intent.setIdentifier(issueActionIdString);
+ int flags = PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT;
+ return PendingIntentFactory.getNonProtectedSystemOnlyBroadcastPendingIntent(
+ context, REQUEST_CODE_UNUSED, intent, flags);
+ }
+
@Nullable
private static SafetyCenterIssueKey getIssueKeyExtra(@NonNull Intent intent) {
String issueKeyString = intent.getStringExtra(EXTRA_ISSUE_KEY);
@@ -82,11 +107,34 @@ final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
}
}
- @NonNull private final SafetyCenterIssueCache mIssueCache;
+ @Nullable
+ private static SafetyCenterIssueActionId getIssueActionIdExtra(@NonNull Intent intent) {
+ String issueActionIdString = intent.getStringExtra(EXTRA_ISSUE_ACTION_ID);
+ if (issueActionIdString == null) {
+ Log.w(TAG, "Received notification action broadcast with null issue action ID");
+ return null;
+ }
+ try {
+ return SafetyCenterIds.issueActionIdFromString(issueActionIdString);
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Could not decode the issue action ID", e);
+ return null;
+ }
+ }
+
+ @NonNull private final SafetyCenterService mService;
+
+ @GuardedBy("mApiLock")
+ @NonNull
+ private final SafetyCenterIssueCache mIssueCache;
+
@NonNull private final Object mApiLock;
SafetyCenterNotificationReceiver(
- @NonNull SafetyCenterIssueCache issueCache, @NonNull Object apiLock) {
+ @NonNull SafetyCenterService service,
+ @NonNull SafetyCenterIssueCache issueCache,
+ @NonNull Object apiLock) {
+ mService = service;
mIssueCache = issueCache;
mApiLock = apiLock;
}
@@ -100,6 +148,7 @@ final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
void register(@NonNull Context context) {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_NOTIFICATION_DISMISSED);
+ filter.addAction(ACTION_NOTIFICATION_ACTION_CLICKED);
context.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
}
@@ -117,10 +166,16 @@ final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
return;
}
- if (ACTION_NOTIFICATION_DISMISSED.equals(action)) {
- onNotificationDismissed(intent);
- } else {
- Log.w(TAG, "Received broadcast with unrecognized action: " + action);
+ switch (action) {
+ case ACTION_NOTIFICATION_DISMISSED:
+ onNotificationDismissed(intent);
+ break;
+ case ACTION_NOTIFICATION_ACTION_CLICKED:
+ onNotificationActionClicked(intent);
+ break;
+ default:
+ Log.w(TAG, "Received broadcast with unrecognized action: " + action);
+ break;
}
}
@@ -133,4 +188,12 @@ final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
mIssueCache.dismissNotification(issueKey);
}
}
+
+ private void onNotificationActionClicked(@NonNull Intent intent) {
+ SafetyCenterIssueActionId issueActionId = getIssueActionIdExtra(intent);
+ if (issueActionId == null) {
+ return;
+ }
+ mService.executeIssueActionInternal(issueActionId);
+ }
}
diff --git a/service/java/com/android/safetycenter/SafetyCenterNotificationSender.java b/service/java/com/android/safetycenter/SafetyCenterNotificationSender.java
index 909ff6ec8..0910750d3 100644
--- a/service/java/com/android/safetycenter/SafetyCenterNotificationSender.java
+++ b/service/java/com/android/safetycenter/SafetyCenterNotificationSender.java
@@ -36,6 +36,7 @@ import android.util.Log;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
import java.io.PrintWriter;
@@ -94,6 +95,8 @@ final class SafetyCenterNotificationSender {
@NonNull private final SafetyCenterRepository mSafetyCenterRepository;
+ @NonNull private final SafetyCenterConfigReader mSafetyCenterConfigReader;
+
private final ArrayMap<SafetyCenterIssueKey, SafetySourceIssue> mNotifiedIssues =
new ArrayMap<>();
@@ -101,11 +104,13 @@ final class SafetyCenterNotificationSender {
@NonNull Context context,
@NonNull SafetyCenterNotificationFactory notificationFactory,
@NonNull SafetyCenterIssueCache issueCache,
- @NonNull SafetyCenterRepository safetyCenterRepository) {
+ @NonNull SafetyCenterRepository safetyCenterRepository,
+ @NonNull SafetyCenterConfigReader safetyCenterConfigReader) {
mContext = context;
mNotificationFactory = notificationFactory;
mIssueCache = issueCache;
mSafetyCenterRepository = safetyCenterRepository;
+ mSafetyCenterConfigReader = safetyCenterConfigReader;
}
/**
@@ -210,6 +215,16 @@ final class SafetyCenterNotificationSender {
@NotificationBehaviorInternal
private int getBehavior(
@NonNull SafetySourceIssue issue, @NonNull SafetyCenterIssueKey issueKey) {
+ if (SdkLevel.isAtLeastU()) {
+ switch (issue.getNotificationBehavior()) {
+ case SafetySourceIssue.NOTIFICATION_BEHAVIOR_NEVER:
+ return NOTIFICATION_BEHAVIOR_INTERNAL_NEVER;
+ case SafetySourceIssue.NOTIFICATION_BEHAVIOR_DELAYED:
+ return NOTIFICATION_BEHAVIOR_INTERNAL_DELAYED;
+ case SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY:
+ return NOTIFICATION_BEHAVIOR_INTERNAL_IMMEDIATELY;
+ }
+ }
// On Android T all issues are assumed to have "unspecified" behavior
return getBehaviorForIssueWithUnspecifiedBehavior(issueKey);
}
@@ -221,6 +236,14 @@ final class SafetyCenterNotificationSender {
}
private boolean areNotificationsAllowed(@NonNull String sourceId) {
+ if (SdkLevel.isAtLeastU()) {
+ SafetyCenterConfigReader.ExternalSafetySource externalSafetySource =
+ mSafetyCenterConfigReader.getExternalSafetySource(sourceId);
+ if (externalSafetySource != null
+ && externalSafetySource.getSafetySource().areNotificationsAllowed()) {
+ return true;
+ }
+ }
return SafetyCenterFlags.getNotificationsAllowedSourceIds().contains(sourceId);
}
diff --git a/service/java/com/android/safetycenter/SafetyCenterPullAtomCallback.java b/service/java/com/android/safetycenter/SafetyCenterPullAtomCallback.java
index 64369b384..c0fc6a3cf 100644
--- a/service/java/com/android/safetycenter/SafetyCenterPullAtomCallback.java
+++ b/service/java/com/android/safetycenter/SafetyCenterPullAtomCallback.java
@@ -39,6 +39,7 @@ import android.util.StatsEvent;
import androidx.annotation.RequiresApi;
import com.android.internal.annotations.GuardedBy;
+import com.android.modules.utils.build.SdkLevel;
import com.android.permission.PermissionStatsLog;
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
@@ -139,15 +140,24 @@ final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback {
mSafetyCenterDataFactory.assembleSafetyCenterData(
"android", userProfileGroup, loggableGroups);
long openIssuesCount = loggableData.getIssues().size();
- long dismissedIssuesCount =
- mSafetyCenterIssueCache.countActiveLoggableIssues(userProfileGroup)
- - openIssuesCount;
+ long dismissedIssuesCount = getDismissedIssuesCountLocked(loggableData, userProfileGroup);
return mStatsdLogger.createSafetyStateEvent(
loggableData.getStatus().getSeverityLevel(), openIssuesCount, dismissedIssuesCount);
}
@GuardedBy("mApiLock")
+ private long getDismissedIssuesCountLocked(
+ @NonNull SafetyCenterData loggableData, @NonNull UserProfileGroup userProfileGroup) {
+ if (SdkLevel.isAtLeastU()) {
+ return loggableData.getDismissedIssues().size();
+ }
+ long openIssuesCount = loggableData.getIssues().size();
+ return mSafetyCenterIssueCache.countActiveLoggableIssues(userProfileGroup)
+ - openIssuesCount;
+ }
+
+ @GuardedBy("mApiLock")
private void writeSafetySourceStateCollectedAtomsLocked(
@NonNull UserProfileGroup userProfileGroup,
@NonNull List<SafetySourcesGroup> loggableGroups) {
diff --git a/service/java/com/android/safetycenter/SafetyCenterRepository.java b/service/java/com/android/safetycenter/SafetyCenterRepository.java
index c42d849ad..2e5410b58 100644
--- a/service/java/com/android/safetycenter/SafetyCenterRepository.java
+++ b/service/java/com/android/safetycenter/SafetyCenterRepository.java
@@ -25,6 +25,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
import android.os.SystemClock;
import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyEvent;
@@ -40,6 +42,7 @@ import android.util.Log;
import androidx.annotation.RequiresApi;
+import com.android.modules.utils.build.SdkLevel;
import com.android.permission.util.UserUtils;
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
@@ -48,6 +51,7 @@ import java.io.PrintWriter;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import javax.annotation.concurrent.NotThreadSafe;
@@ -76,6 +80,7 @@ final class SafetyCenterRepository {
@NonNull private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker;
@NonNull private final StatsdLogger mStatsdLogger;
@NonNull private final SafetyCenterIssueCache mSafetyCenterIssueCache;
+ @NonNull private final PackageManager mPackageManager;
SafetyCenterRepository(
@NonNull Context context,
@@ -88,6 +93,7 @@ final class SafetyCenterRepository {
mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
mStatsdLogger = statsdLogger;
mSafetyCenterIssueCache = safetyCenterIssueCache;
+ mPackageManager = mContext.getPackageManager();
}
/**
@@ -163,6 +169,9 @@ final class SafetyCenterRepository {
*
* <p>This method does not perform any validation, {@link #getSafetySourceData(String, String,
* int)} should be called wherever validation is required.
+ *
+ * <p>Returns {@code null} if it was never set since boot, or if the entry was evicted using
+ * {@link #setSafetySourceData} with a {@code null} value.
*/
@Nullable
SafetySourceData getSafetySourceData(@NonNull SafetySourceKey safetySourceKey) {
@@ -444,17 +453,7 @@ final class SafetyCenterRepository {
}
SafetySource safetySource = externalSafetySource.getSafetySource();
-
- // TODO(b/222330089): Security: check certs?
- if (!packageName.equals(safetySource.getPackageName())) {
- throw new IllegalArgumentException(
- "Unexpected package name: "
- + packageName
- + ", for safety source: "
- + safetySourceId);
- }
-
- // TODO(b/222327845): Security: check package is installed for user?
+ validateCallingPackage(safetySource, packageName, safetySourceId);
if (UserUtils.isManagedProfile(userId, mContext)
&& !SafetySources.supportsManagedProfiles(safetySource)) {
@@ -484,12 +483,12 @@ final class SafetyCenterRepository {
if (safetySourceStatus != null) {
int sourceSeverityLevel = safetySourceStatus.getSeverityLevel();
- if (externalSafetySource.hasEntryInRigidGroup()
+ if (externalSafetySource.hasEntryInStatelessGroup()
&& sourceSeverityLevel != SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED) {
throw new IllegalArgumentException(
"Safety source: "
+ safetySourceId
- + " is in a rigid group but specified a severity level: "
+ + " is in a stateless group but specified a severity level: "
+ sourceSeverityLevel);
}
@@ -533,6 +532,59 @@ final class SafetyCenterRepository {
return mSafetyCenterConfigReader.isExternalSafetySourceActive(safetySourceId);
}
+ private void validateCallingPackage(
+ @NonNull SafetySource safetySource,
+ @NonNull String packageName,
+ @NonNull String safetySourceId) {
+ if (!packageName.equals(safetySource.getPackageName())) {
+ throw new IllegalArgumentException(
+ "Unexpected package name: "
+ + packageName
+ + ", for safety source: "
+ + safetySourceId);
+ }
+
+ // TODO(b/222327845): Security: check package is installed for user?
+
+ if (!SdkLevel.isAtLeastU()) {
+ // No more validation checks possible on T devices
+ return;
+ }
+
+ Set<String> certificateHashes = safetySource.getPackageCertificateHashes();
+ if (certificateHashes.isEmpty()) {
+ Log.d(TAG, "No cert check requested for package " + packageName);
+ return;
+ }
+
+ boolean hasMatchingCert = false;
+ for (String certHash : certificateHashes) {
+ try {
+ byte[] certificate = new Signature(certHash).toByteArray();
+ if (mPackageManager.hasSigningCertificate(
+ packageName, certificate, PackageManager.CERT_INPUT_SHA256)) {
+ Log.d(TAG, "Package " + packageName + " has expected signature");
+ hasMatchingCert = true;
+ }
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Failed to parse signing certificate: " + certHash, e);
+ throw new IllegalStateException(
+ "Failed to parse signing certificate: " + certHash, e);
+ }
+ }
+
+ if (!hasMatchingCert) {
+ Log.e(
+ TAG,
+ "Package "
+ + packageName
+ + " for source "
+ + safetySourceId
+ + " signed with invalid signature");
+ throw new IllegalArgumentException("Invalid signature for package " + packageName);
+ }
+ }
+
private boolean processSafetyEvent(
@NonNull String safetySourceId,
@NonNull SafetyEvent safetyEvent,
diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java
index 79b43befc..3e55c0dc3 100644
--- a/service/java/com/android/safetycenter/SafetyCenterService.java
+++ b/service/java/com/android/safetycenter/SafetyCenterService.java
@@ -19,8 +19,10 @@ package com.android.safetycenter;
import static android.Manifest.permission.MANAGE_SAFETY_CENTER;
import static android.Manifest.permission.READ_SAFETY_CENTER_STATUS;
import static android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE;
+import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.TIRAMISU;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER;
import static android.safetycenter.SafetyCenterManager.RefreshReason;
import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED;
@@ -71,6 +73,7 @@ import androidx.annotation.RequiresApi;
import com.android.internal.annotations.GuardedBy;
import com.android.modules.utils.BackgroundThread;
+import com.android.modules.utils.build.SdkLevel;
import com.android.permission.util.ForegroundThread;
import com.android.permission.util.UserUtils;
import com.android.safetycenter.internaldata.SafetyCenterIds;
@@ -114,11 +117,6 @@ public final class SafetyCenterService extends SystemService {
/** The name of the file used to persist the Safety Center issue cache. */
private static final String SAFETY_CENTER_ISSUES_CACHE_FILE_NAME = "safety_center_issues.xml";
- /** The START_TASKS_FROM_RECENTS permission. */
- // TODO(b/242905922): Remove once in API.
- private static final String START_TASKS_FROM_RECENTS =
- "android.permission.START_TASKS_FROM_RECENTS";
-
/** The time delay used to throttle and aggregate writes to disk. */
private static final Duration WRITE_DELAY = Duration.ofMillis(500);
@@ -199,17 +197,24 @@ public final class SafetyCenterService extends SystemService {
mSafetyCenterRefreshTracker,
mPendingIntentFactory,
mSafetyCenterIssueCache,
- mSafetyCenterRepository);
+ mSafetyCenterRepository,
+ SdkLevel.isAtLeastU()
+ ? new SafetyCenterIssueDeduplicator(mSafetyCenterIssueCache)
+ : null);
mSafetyCenterListeners = new SafetyCenterListeners(mSafetyCenterDataFactory);
mNotificationSender =
new SafetyCenterNotificationSender(
context,
new SafetyCenterNotificationFactory(context),
mSafetyCenterIssueCache,
- mSafetyCenterRepository);
+ mSafetyCenterRepository,
+ mSafetyCenterConfigReader);
mSafetyCenterBroadcastDispatcher =
new SafetyCenterBroadcastDispatcher(
- context, mSafetyCenterConfigReader, mSafetyCenterRefreshTracker);
+ context,
+ mSafetyCenterConfigReader,
+ mSafetyCenterRefreshTracker,
+ mSafetyCenterRepository);
mPullAtomCallback =
new SafetyCenterPullAtomCallback(
context,
@@ -226,6 +231,9 @@ public final class SafetyCenterService extends SystemService {
Resources.getSystem()
.getIdentifier(
"config_enableSafetyCenter", "bool", "android"));
+ if (!mDeviceSupportsSafetyCenter) {
+ Log.v(TAG, "Device does not support safety center, safety center will be disabled.");
+ }
}
@Override
@@ -237,7 +245,7 @@ public final class SafetyCenterService extends SystemService {
if (mConfigAvailable) {
readSafetyCenterIssueCacheFileLocked();
new UserBroadcastReceiver().register(getContext());
- new SafetyCenterNotificationReceiver(mSafetyCenterIssueCache, mApiLock)
+ new SafetyCenterNotificationReceiver(this, mSafetyCenterIssueCache, mApiLock)
.register(getContext());
}
}
@@ -386,6 +394,23 @@ public final class SafetyCenterService extends SystemService {
}
@Override
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public void refreshSpecificSafetySources(
+ @RefreshReason int refreshReason,
+ @UserIdInt int userId,
+ @NonNull List<String> safetySourceIds) {
+ getContext()
+ .enforceCallingPermission(MANAGE_SAFETY_CENTER, "refreshSpecificSafetySources");
+ requireNonNull(safetySourceIds, "safetySourceIds cannot be null");
+ RefreshReasons.validate(refreshReason);
+ if (!enforceCrossUserPermission("refreshSpecificSafetySources", userId)
+ || !checkApiEnabled("refreshSpecificSafetySources")) {
+ return;
+ }
+ startRefreshingSafetySources(refreshReason, userId, safetySourceIds);
+ }
+
+ @Override
@Nullable
public SafetyCenterConfig getSafetyCenterConfig() {
getContext()
@@ -770,9 +795,12 @@ public final class SafetyCenterService extends SystemService {
private void setInitialState() {
mSafetyCenterEnabled = SafetyCenterFlags.getSafetyCenterEnabled();
+ Log.v(TAG, "SafetyCenter is " + (mSafetyCenterEnabled ? "enabled." : "disabled."));
}
private void onSafetyCenterEnabledChanged(boolean safetyCenterEnabled) {
+ Log.v(TAG, "SafetyCenter is now " + (safetyCenterEnabled ? "enabled." : "disabled."));
+
if (safetyCenterEnabled) {
onApiEnabled();
} else {
@@ -970,13 +998,20 @@ public final class SafetyCenterService extends SystemService {
private void startRefreshingSafetySources(
@RefreshReason int refreshReason, @UserIdInt int userId) {
+ startRefreshingSafetySources(refreshReason, userId, null);
+ }
+
+ private void startRefreshingSafetySources(
+ @RefreshReason int refreshReason,
+ @UserIdInt int userId,
+ @Nullable List<String> selectedSafetySourceIds) {
UserProfileGroup userProfileGroup = UserProfileGroup.from(getContext(), userId);
synchronized (mApiLock) {
mSafetyCenterRepository.clearSafetySourceErrors(userProfileGroup);
String refreshBroadcastId =
mSafetyCenterBroadcastDispatcher.sendRefreshSafetySources(
- refreshReason, userProfileGroup);
+ refreshReason, userProfileGroup, selectedSafetySourceIds);
if (refreshBroadcastId == null) {
return;
}
@@ -990,6 +1025,21 @@ public final class SafetyCenterService extends SystemService {
}
}
+ /**
+ * Executes the {@link SafetySourceIssue.Action} specified by the given {@link
+ * SafetyCenterIssueActionId}.
+ *
+ * <p>No validation is performed on the contents of the given ID.
+ */
+ void executeIssueActionInternal(@NonNull SafetyCenterIssueActionId safetyCenterIssueActionId) {
+ SafetyCenterIssueKey safetyCenterIssueKey =
+ safetyCenterIssueActionId.getSafetyCenterIssueKey();
+ UserProfileGroup userProfileGroup =
+ UserProfileGroup.from(getContext(), safetyCenterIssueKey.getUserId());
+ executeIssueActionInternal(
+ safetyCenterIssueKey, safetyCenterIssueActionId, userProfileGroup, null);
+ }
+
private void executeIssueActionInternal(
@NonNull SafetyCenterIssueKey safetyCenterIssueKey,
@NonNull SafetyCenterIssueActionId safetyCenterIssueActionId,
@@ -998,6 +1048,7 @@ public final class SafetyCenterService extends SystemService {
synchronized (mApiLock) {
SafetySourceIssue.Action safetySourceIssueAction =
mSafetyCenterRepository.getSafetySourceIssueAction(safetyCenterIssueActionId);
+
if (safetySourceIssueAction == null) {
Log.w(
TAG,
diff --git a/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java b/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java
index f66aabe94..a96309ba7 100644
--- a/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java
+++ b/service/java/com/android/safetycenter/SafetyCenterShellCommandHandler.java
@@ -21,6 +21,7 @@ import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_LOC
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_REBOOT;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN;
+import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_PERIODIC;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK;
import static android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED;
@@ -37,12 +38,17 @@ import android.safetycenter.SafetyCenterManager.RefreshReason;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.BasicShellCommandHandler;
+import com.android.modules.utils.build.SdkLevel;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;
-/** A {@link BasicShellCommandHandler} implementation to handle Safety Center commands. */
+/**
+ * A {@link BasicShellCommandHandler} implementation to handle Safety Center commands.
+ *
+ * <p>Example usage: $ adb shell cmd safety_center refresh --reason PAGE_OPEN --user 10
+ */
@RequiresApi(TIRAMISU)
final class SafetyCenterShellCommandHandler extends BasicShellCommandHandler {
@@ -199,6 +205,9 @@ final class SafetyCenterShellCommandHandler extends BasicShellCommandHandler {
reasons.put("LOCALE_CHANGE", REFRESH_REASON_DEVICE_LOCALE_CHANGE);
reasons.put("SAFETY_CENTER_ENABLED", REFRESH_REASON_SAFETY_CENTER_ENABLED);
reasons.put("OTHER", REFRESH_REASON_OTHER);
+ if (SdkLevel.isAtLeastU()) {
+ reasons.put("PERIODIC", REFRESH_REASON_PERIODIC);
+ }
return unmodifiableMap(reasons);
}
}
diff --git a/service/java/com/android/safetycenter/TEST_MAPPING b/service/java/com/android/safetycenter/TEST_MAPPING
index e2eb5ef5d..c702ee852 100644
--- a/service/java/com/android/safetycenter/TEST_MAPPING
+++ b/service/java/com/android/safetycenter/TEST_MAPPING
@@ -24,5 +24,15 @@
{
"name": "SafetyCenterFunctionalTestCases"
}
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsSafetyCenterTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
]
}
diff --git a/tests/cts/safetycenter/Android.bp b/tests/cts/safetycenter/Android.bp
index 1b2577841..abb89bdc2 100644
--- a/tests/cts/safetycenter/Android.bp
+++ b/tests/cts/safetycenter/Android.bp
@@ -29,6 +29,7 @@ android_test {
"src/**/*.kt",
],
static_libs: [
+ "androidx.core_core-ktx",
"androidx.test.core",
"androidx.test.ext.junit",
"androidx.test.ext.truth",
@@ -43,6 +44,7 @@ android_test {
"safety-center-internal-data",
"safety-center-resources-lib",
"safety-center-test-util-lib",
+ "modules-utils-build",
"truth-prebuilt",
"Nene",
"Harrier",
diff --git a/tests/cts/safetycenter/AndroidManifest.xml b/tests/cts/safetycenter/AndroidManifest.xml
index 50143663c..7d1da1273 100644
--- a/tests/cts/safetycenter/AndroidManifest.xml
+++ b/tests/cts/safetycenter/AndroidManifest.xml
@@ -60,6 +60,7 @@
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"/>
</manifest>
diff --git a/tests/cts/safetycenter/AndroidTest.xml b/tests/cts/safetycenter/AndroidTest.xml
index f169f96f7..9f512231a 100644
--- a/tests/cts/safetycenter/AndroidTest.xml
+++ b/tests/cts/safetycenter/AndroidTest.xml
@@ -28,6 +28,7 @@
<!-- Multi-user code is tested separately using Bedstead. See SafetyCenterMultiUsersTest. -->
<option name="config-descriptor:metadata" key="parameter"
value="not_secondary_user"/>
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.permission.apex" />
<option name="test-suite-tag" value="cts"/>
diff --git a/tests/cts/safetycenter/TEST_MAPPING b/tests/cts/safetycenter/TEST_MAPPING
index 5a26efe66..5feb2e901 100644
--- a/tests/cts/safetycenter/TEST_MAPPING
+++ b/tests/cts/safetycenter/TEST_MAPPING
@@ -13,5 +13,15 @@
{
"name": "CtsSafetyCenterTestCases"
}
+ ],
+ "mainline-presubmit": [
+ {
+ "name": "CtsSafetyCenterTestCases[com.google.android.permission.apex]",
+ "options": [
+ {
+ "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
]
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
index 68943e645..bff60a149 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
@@ -19,6 +19,8 @@ package android.safetycenter.cts
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
+import android.os.Build
+import android.os.Bundle
import android.safetycenter.SafetyCenterData
import android.safetycenter.SafetyCenterEntry
import android.safetycenter.SafetyCenterEntryGroup
@@ -27,9 +29,14 @@ import android.safetycenter.SafetyCenterIssue
import android.safetycenter.SafetyCenterStaticEntry
import android.safetycenter.SafetyCenterStaticEntryGroup
import android.safetycenter.SafetyCenterStatus
+import android.safetycenter.cts.testing.SafetyCenterCtsData.Companion.withDismissedIssuesIfAtLeastU
+import android.safetycenter.cts.testing.SafetyCenterCtsData.Companion.withExtrasIfAtLeastU
+import androidx.core.os.bundleOf
import androidx.test.core.app.ApplicationProvider
+import androidx.test.core.os.Parcelables.forceParcel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
+import androidx.test.filters.SdkSuppress
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
@@ -98,12 +105,132 @@ class SafetyCenterDataTest {
private val staticEntryGroup2 =
SafetyCenterStaticEntryGroup("Another static entry group title", listOf(staticEntry2))
+ private val filledExtras =
+ Bundle().apply {
+ putBundle(SOURCE_EXTRA_KEY_1, bundleOf(EXTRA_KEY_1 to EXTRA_VALUE_1))
+ putBundle(SOURCE_EXTRA_KEY_2, bundleOf(EXTRA_KEY_2 to EXTRA_VALUE_2))
+ }
+
private val data1 =
SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1), listOf(staticEntryGroup1))
private val data2 =
SafetyCenterData(status2, listOf(issue2), listOf(entryOrGroup2), listOf(staticEntryGroup2))
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getStatus_withDefaultBuilder_returnsStatus() {
+ val safetyCenterData = SafetyCenterData.Builder(status1).build()
+
+ assertThat(safetyCenterData.status).isEqualTo(status1)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getIssues_withDefaultBuilder_returnsEmptyList() {
+ val safetyCenterData = SafetyCenterData.Builder(status1).build()
+
+ assertThat(safetyCenterData.issues).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getIssues_whenSetExplicitly_returnsIssues() {
+ val safetyCenterData =
+ SafetyCenterData.Builder(status1).addIssue(issue1).addIssue(issue2).build()
+
+ assertThat(safetyCenterData.issues).containsExactly(issue1, issue2).inOrder()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getEntriesOrGroups_withDefaultBuilder_returnsEmptyList() {
+ val safetyCenterData = SafetyCenterData.Builder(status1).build()
+
+ assertThat(safetyCenterData.entriesOrGroups).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getEntriesOrGroups_whenSetExplicitly_returnsEntriesOrGroups() {
+ val safetyCenterData =
+ SafetyCenterData.Builder(status1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addEntryOrGroup(entryOrGroup2)
+ .build()
+
+ assertThat(safetyCenterData.entriesOrGroups)
+ .containsExactly(entryOrGroup1, entryOrGroup2)
+ .inOrder()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getStaticGroups_withDefaultBuilder_returnsEmptyList() {
+ val safetyCenterData = SafetyCenterData.Builder(status1).build()
+
+ assertThat(safetyCenterData.staticEntryGroups).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getStaticEntryGroups_whenSetExplicitly_returnsStaticEntryGroups() {
+ val safetyCenterData =
+ SafetyCenterData.Builder(status1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addStaticEntryGroup(staticEntryGroup2)
+ .build()
+
+ assertThat(safetyCenterData.staticEntryGroups)
+ .containsExactly(staticEntryGroup1, staticEntryGroup2)
+ .inOrder()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getDismissedIssues_withDefaultBuilder_returnsEmptyList() {
+ val safetyCenterData = SafetyCenterData.Builder(status1).build()
+
+ assertThat(safetyCenterData.dismissedIssues).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getDismissedIssues_whenSetExplicitly_returnsIssues() {
+ val safetyCenterData =
+ SafetyCenterData.Builder(status1)
+ .addDismissedIssue(issue1)
+ .addDismissedIssue(issue2)
+ .build()
+
+ assertThat(safetyCenterData.dismissedIssues).containsExactly(issue1, issue2).inOrder()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getExtras_withDefaultBuilder_returnsEmptyBundle() {
+ val safetyCenterData = SafetyCenterData.Builder(status1).build()
+
+ assertThat(safetyCenterData.extras.keySet()).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getExtras_whenSetExplicitly_returnsExtras() {
+ val safetyCenterData = SafetyCenterData.Builder(status1).setExtras(filledExtras).build()
+
+ assertContainsExtras(safetyCenterData)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getExtras_whenCleared_returnsEmptyBundle() {
+ val safetyCenterData =
+ SafetyCenterData.Builder(status1).setExtras(filledExtras).clearExtras().build()
+
+ assertThat(safetyCenterData.extras.keySet()).isEmpty()
+ }
+
+ @Test
fun getStatus_returnsStatus() {
assertThat(data1.status).isEqualTo(status1)
assertThat(data2.status).isEqualTo(status2)
@@ -116,6 +243,23 @@ class SafetyCenterDataTest {
}
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getDismissedIssues_returnsDismissedIssues() {
+ val data3 = data1.withDismissedIssuesIfAtLeastU(listOf(issue2))
+
+ assertThat(data1.dismissedIssues).isEmpty()
+ assertThat(data3.dismissedIssues).containsExactly(issue2)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getDismissedIssues_mutationsAreNotAllowed() {
+ val mutatedDismissedIssues = data1.dismissedIssues
+
+ assertFailsWith(UnsupportedOperationException::class) { mutatedDismissedIssues.add(issue2) }
+ }
+
+ @Test
fun getIssues_mutationsAreNotAllowed() {
val mutatedIssues = data1.issues
@@ -153,6 +297,104 @@ class SafetyCenterDataTest {
}
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun builder_addIssue_doesNotMutatePreviouslyBuiltInstance() {
+ val safetyCenterDataBuilder = SafetyCenterData.Builder(status1).addIssue(issue1)
+ val issues = safetyCenterDataBuilder.build().issues
+
+ safetyCenterDataBuilder.addIssue(issue2)
+
+ assertThat(issues).containsExactly(issue1)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun builder_clearIssues_removesAllIssues() {
+ val safetyCenterData =
+ SafetyCenterData.Builder(status1)
+ .addIssue(issue1)
+ .addIssue(issue2)
+ .clearIssues()
+ .build()
+
+ assertThat(safetyCenterData.issues).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun builder_addEntryOrGroup_doesNotMutatePreviouslyBuiltInstance() {
+ val safetyCenterDataBuilder =
+ SafetyCenterData.Builder(status1).addEntryOrGroup(entryOrGroup1)
+ val entriesOrGroups = safetyCenterDataBuilder.build().entriesOrGroups
+
+ safetyCenterDataBuilder.addEntryOrGroup(entryOrGroup2)
+
+ assertThat(entriesOrGroups).containsExactly(entryOrGroup1)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun builder_clearEntriesOrGroups_removesAllEntriesOrGroups() {
+ val safetyCenterData =
+ SafetyCenterData.Builder(status1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addEntryOrGroup(entryOrGroup2)
+ .clearEntriesOrGroups()
+ .build()
+
+ assertThat(safetyCenterData.entriesOrGroups).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun builder_addStaticEntryGroup_doesNotMutatePreviouslyBuiltInstance() {
+ val safetyCenterDataBuilder =
+ SafetyCenterData.Builder(status1).addStaticEntryGroup(staticEntryGroup1)
+ val staticEntryGroups = safetyCenterDataBuilder.build().staticEntryGroups
+
+ safetyCenterDataBuilder.addStaticEntryGroup(staticEntryGroup2)
+
+ assertThat(staticEntryGroups).containsExactly(staticEntryGroup1)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun builder_clearStaticEntryGroups_removesAllStaticEntryGroups() {
+ val safetyCenterData =
+ SafetyCenterData.Builder(status1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addStaticEntryGroup(staticEntryGroup2)
+ .clearStaticEntryGroups()
+ .build()
+
+ assertThat(safetyCenterData.staticEntryGroups).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun builder_addDismissedIssue_doesNotMutatePreviouslyBuiltInstance() {
+ val safetyCenterDataBuilder = SafetyCenterData.Builder(status1).addDismissedIssue(issue1)
+ val dismissedIssues = safetyCenterDataBuilder.build().dismissedIssues
+
+ safetyCenterDataBuilder.addDismissedIssue(issue2)
+
+ assertThat(dismissedIssues).containsExactly(issue1)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun builder_clearDismissedIssues_removesAllDismissedIssues() {
+ val safetyCenterData =
+ SafetyCenterData.Builder(status1)
+ .addDismissedIssue(issue1)
+ .addDismissedIssue(issue2)
+ .clearDismissedIssues()
+ .build()
+
+ assertThat(safetyCenterData.dismissedIssues).isEmpty()
+ }
+
+ @Test
fun describeContents_returns0() {
assertThat(data1.describeContents()).isEqualTo(0)
assertThat(data2.describeContents()).isEqualTo(0)
@@ -165,8 +407,96 @@ class SafetyCenterDataTest {
}
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun parcelRoundTrip_withDismissedIssues_recreatesEqual() {
+ val data3 = data1.withDismissedIssuesIfAtLeastU(listOf(issue2))
+ val data4 = data2.withDismissedIssuesIfAtLeastU(listOf(issue1))
+
+ assertThat(data3).recreatesEqual(SafetyCenterData.CREATOR)
+ assertThat(data4).recreatesEqual(SafetyCenterData.CREATOR)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun parcelRoundTrip_withExtras_recreatesEqual() {
+ val safetyCenterDataWithExtras = data1.withExtrasIfAtLeastU(filledExtras)
+ val safetyCenterDatafromParcel =
+ forceParcel(safetyCenterDataWithExtras, SafetyCenterData.CREATOR)
+
+ assertThat(safetyCenterDatafromParcel).isEqualTo(safetyCenterDataWithExtras)
+ assertContainsExtras(safetyCenterDatafromParcel)
+ }
+
+ @Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ val equalsHashCodeToStringTester =
+ EqualsHashCodeToStringTester.ofParcelable(parcelableCreator = SafetyCenterData.CREATOR)
+ .addEqualityGroup(
+ data1,
+ SafetyCenterData(
+ status1,
+ listOf(issue1),
+ listOf(entryOrGroup1),
+ listOf(staticEntryGroup1)
+ )
+ )
+ .addEqualityGroup(
+ data2,
+ SafetyCenterData(
+ status2,
+ listOf(issue2),
+ listOf(entryOrGroup2),
+ listOf(staticEntryGroup2)
+ )
+ )
+ .addEqualityGroup(
+ SafetyCenterData(status1, listOf(), listOf(), listOf()),
+ SafetyCenterData(status1, listOf(), listOf(), listOf())
+ )
+ .addEqualityGroup(
+ SafetyCenterData(
+ status2,
+ listOf(issue1),
+ listOf(entryOrGroup1),
+ listOf(staticEntryGroup1)
+ )
+ )
+ .addEqualityGroup(
+ SafetyCenterData(
+ status1,
+ listOf(issue2),
+ listOf(entryOrGroup1),
+ listOf(staticEntryGroup1)
+ )
+ )
+ .addEqualityGroup(
+ SafetyCenterData(
+ status1,
+ listOf(issue1),
+ listOf(entryOrGroup2),
+ listOf(staticEntryGroup1)
+ )
+ )
+ .addEqualityGroup(
+ SafetyCenterData(
+ status1,
+ listOf(issue1),
+ listOf(entryOrGroup1),
+ listOf(staticEntryGroup2)
+ )
+ )
+
+ equalsHashCodeToStringTester.test()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun equalsHashCode_atLeastU_usingEqualsHashCodeToStringTester() {
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterData.CREATOR,
+ ignoreToString = true,
+ createCopy = { SafetyCenterData.Builder(it).build() }
+ )
.addEqualityGroup(
data1,
SafetyCenterData(
@@ -174,53 +504,141 @@ class SafetyCenterDataTest {
listOf(issue1),
listOf(entryOrGroup1),
listOf(staticEntryGroup1)
- )
+ ),
+ SafetyCenterData.Builder(status1)
+ .addIssue(issue1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .build(),
+ SafetyCenterData.Builder(status1)
+ .addIssue(issue1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .setExtras(filledExtras)
+ .build()
)
.addEqualityGroup(
- data2,
- SafetyCenterData(
- status2,
- listOf(issue2),
- listOf(entryOrGroup2),
- listOf(staticEntryGroup2)
- )
+ SafetyCenterData.Builder(status1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addDismissedIssue(issue1)
+ .build(),
+ SafetyCenterData.Builder(status1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addDismissedIssue(issue1)
+ .setExtras(filledExtras)
+ .build()
)
.addEqualityGroup(
- SafetyCenterData(status1, listOf(), listOf(), listOf()),
- SafetyCenterData(status1, listOf(), listOf(), listOf())
+ SafetyCenterData.Builder(status1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addDismissedIssue(issue2)
+ .build(),
+ SafetyCenterData.Builder(status1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addDismissedIssue(issue2)
+ .setExtras(filledExtras)
+ .build()
)
.addEqualityGroup(
- SafetyCenterData(
- status2,
- listOf(issue1),
- listOf(entryOrGroup1),
- listOf(staticEntryGroup1)
- )
- )
- .addEqualityGroup(
- SafetyCenterData(
- status1,
- listOf(issue2),
- listOf(entryOrGroup1),
- listOf(staticEntryGroup1)
- )
+ SafetyCenterData.Builder(status1)
+ .addIssue(issue2)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addIssue(issue1)
+ .setExtras(filledExtras)
+ .build(),
+ SafetyCenterData.Builder(status1)
+ .addIssue(issue2)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addIssue(issue1)
+ .setExtras(filledExtras)
+ .build()
)
.addEqualityGroup(
- SafetyCenterData(
- status1,
- listOf(issue1),
- listOf(entryOrGroup2),
- listOf(staticEntryGroup1)
- )
+ SafetyCenterData.Builder(status1)
+ .addIssue(issue1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addDismissedIssue(issue2)
+ .build(),
+ SafetyCenterData.Builder(status1)
+ .addIssue(issue1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addDismissedIssue(issue2)
+ .setExtras(filledExtras)
+ .build()
)
.addEqualityGroup(
- SafetyCenterData(
- status1,
- listOf(issue1),
- listOf(entryOrGroup1),
- listOf(staticEntryGroup2)
- )
+ SafetyCenterData.Builder(status1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addDismissedIssue(issue1)
+ .addDismissedIssue(issue2)
+ .build(),
+ SafetyCenterData.Builder(status1)
+ .addEntryOrGroup(entryOrGroup1)
+ .addStaticEntryGroup(staticEntryGroup1)
+ .addDismissedIssue(issue1)
+ .addDismissedIssue(issue2)
+ .setExtras(filledExtras)
+ .build()
)
.test()
}
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun toString_withExtras_containsHasExtras() {
+ val safetyCenterDataWithExtras = data1.withExtrasIfAtLeastU(filledExtras)
+
+ val stringRepresentation = safetyCenterDataWithExtras.toString()
+
+ assertThat(stringRepresentation).contains("(has extras)")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun toString_withoutExtras_doesNotContainHasExtras() {
+ val safetyCenterDataWithoutExtras = data1
+
+ val stringRepresentation = safetyCenterDataWithoutExtras.toString()
+
+ assertThat(stringRepresentation).doesNotContain("(has extras)")
+ }
+
+ private fun assertContainsExtras(data: SafetyCenterData) {
+ assertThat(data.extras.keySet()).containsExactly(SOURCE_EXTRA_KEY_1, SOURCE_EXTRA_KEY_2)
+ val sourceExtra1 = data.extras.getBundle(SOURCE_EXTRA_KEY_1)!!
+ val sourceExtra2 = data.extras.getBundle(SOURCE_EXTRA_KEY_2)!!
+ assertThat(sourceExtra1.keySet()).containsExactly(EXTRA_KEY_1)
+ assertThat(sourceExtra1.getString(EXTRA_KEY_1, "")).isEqualTo(EXTRA_VALUE_1)
+ assertThat(sourceExtra2.keySet()).containsExactly(EXTRA_KEY_2)
+ assertThat(sourceExtra2.getString(EXTRA_KEY_2, "")).isEqualTo(EXTRA_VALUE_2)
+ }
+
+ private companion object {
+ /** Key of extra data in [Bundle]. */
+ const val EXTRA_KEY_1 = "extra_key_1"
+
+ /** Key of extra data in [Bundle]. */
+ const val EXTRA_KEY_2 = "extra_key_2"
+
+ /** Value of extra data in [Bundle]. */
+ const val EXTRA_VALUE_1 = "extra_value_1"
+
+ /** Value of extra data in [Bundle]. */
+ const val EXTRA_VALUE_2 = "extra_value_2"
+
+ /** Key of [SafetySourceData] extra data in combined [Bundle]. */
+ const val SOURCE_EXTRA_KEY_1 = "source_extra_key_1"
+
+ /** Key of [SafetySourceData] extra data in combined [Bundle]. */
+ const val SOURCE_EXTRA_KEY_2 = "source_extra_key_2"
+ }
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt
index 4ba4f219c..7ae5fb347 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt
@@ -173,10 +173,12 @@ class SafetyCenterEntryGroupTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterEntryGroup.CREATOR,
+ createCopy = { SafetyCenterEntryGroup.Builder(it).build() }
+ )
.addEqualityGroup(
entryGroup1,
- SafetyCenterEntryGroup.Builder(entryGroup1).build(),
SafetyCenterEntryGroup.Builder(groupId1, "A group title")
.setSummary("A group summary")
.setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt
index 1dd6e54ec..eb0e96898 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt
@@ -99,7 +99,9 @@ class SafetyCenterEntryOrGroupTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterEntryOrGroup.CREATOR
+ )
.addEqualityGroup(entryOrGroupWithEntry, SafetyCenterEntryOrGroup(entry1))
.addEqualityGroup(entryOrGroupWithGroup, SafetyCenterEntryOrGroup(entryGroup1))
.addEqualityGroup(SafetyCenterEntryOrGroup(entry2))
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt
index af8b63aa4..116164288 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt
@@ -211,8 +211,11 @@ class SafetyCenterEntryTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
- .addEqualityGroup(entry1, SafetyCenterEntry.Builder(entry1).build())
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterEntry.CREATOR,
+ createCopy = { SafetyCenterEntry.Builder(it).build() }
+ )
+ .addEqualityGroup(entry1)
.addEqualityGroup(
SafetyCenterEntry.Builder("id", "a title")
.setSummary("a summary")
@@ -330,7 +333,9 @@ class SafetyCenterEntryTest {
@Test
fun iconAction_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterEntry.IconAction.CREATOR
+ )
.addEqualityGroup(
iconAction1,
SafetyCenterEntry.IconAction(
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt
index 66e4eb3da..0d97026bd 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterErrorDetailsTest.kt
@@ -51,7 +51,9 @@ class SafetyCenterErrorDetailsTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterErrorDetails.CREATOR
+ )
.addEqualityGroup(errorDetails1, SafetyCenterErrorDetails("an error message"))
.addEqualityGroup(errorDetails2, SafetyCenterErrorDetails("another error message"))
.addEqualityGroup(
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
index d648a2956..14bfaae73 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
@@ -19,13 +19,20 @@ package android.safetycenter.cts
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
+import android.os.Build
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.SafetyCenterIssue
+import android.safetycenter.SafetyCenterIssue.Action.ConfirmationDialogDetails
+import androidx.annotation.RequiresApi
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
+import androidx.test.filters.SdkSuppress
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
+import org.junit.Assume.assumeFalse
import org.junit.Test
import org.junit.runner.RunWith
@@ -104,6 +111,35 @@ class SafetyCenterIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getAttributionTitle_returnsAttributionTitle() {
+ assertThat(
+ SafetyCenterIssue.Builder(issue1)
+ .setAttributionTitle("an attributionTitle")
+ .build()
+ .attributionTitle
+ )
+ .isEqualTo("an attributionTitle")
+ assertThat(
+ SafetyCenterIssue.Builder(issue1)
+ .setAttributionTitle("another attributionTitle")
+ .build()
+ .attributionTitle
+ )
+ .isEqualTo("another attributionTitle")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getAttributionTitle_withNullAttributionTitle_returnsNull() {
+ val safetyCenterIssue =
+ SafetyCenterIssue.Builder("issue_id", "Everything's good", "Please acknowledge this")
+ .build()
+
+ assertThat(safetyCenterIssue.attributionTitle).isNull()
+ }
+
+ @Test
fun getSeverityLevel_returnsSeverityLevel() {
assertThat(
SafetyCenterIssue.Builder(issue1)
@@ -192,6 +228,64 @@ class SafetyCenterIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getGroupId_withNonNullValue_returnsGroupId() {
+ val issue = SafetyCenterIssue.Builder(issue1).setGroupId("group_id").build()
+
+ assertThat(issue.groupId).isEqualTo("group_id")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getGroupId_withNullValue_returnsNull() {
+ val issue =
+ SafetyCenterIssue.Builder("issue_id", "Everything's good", "Please acknowledge this")
+ .build()
+
+ assertThat(issue.groupId).isNull()
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun getGroupId_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val issue =
+ SafetyCenterIssue.Builder("issue_id", "Everything's good", "Please acknowledge this")
+ .build()
+
+ val exception = assertFailsWith(UnsupportedOperationException::class) { issue.groupId }
+
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("Method not supported for versions lower than UPSIDE_DOWN_CAKE")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun setGroupId_withNullValue_returnsNull() {
+ val issue = SafetyCenterIssue.Builder(issue1).setGroupId(null).build()
+
+ assertThat(issue.groupId).isNull()
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun setGroupId_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+
+ val exception =
+ assertFailsWith(UnsupportedOperationException::class) {
+ SafetyCenterIssue.Builder(issue1).setGroupId("group_id").build()
+ }
+
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("Method not supported for versions lower than UPSIDE_DOWN_CAKE")
+ }
+
+ @Test
fun describeContents_returns0() {
assertThat(issue1.describeContents()).isEqualTo(0)
assertThat(issueWithRequiredFieldsOnly.describeContents()).isEqualTo(0)
@@ -204,43 +298,39 @@ class SafetyCenterIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun parcelRoundTrip_recreatesEqual_atLeastAndroidU() {
+ val safetyCenterIssue =
+ SafetyCenterIssue.Builder("issue_id", "Everything's good", "Please acknowledge this")
+ .setSubtitle("In the neighborhood")
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+ .setDismissible(true)
+ .setShouldConfirmDismissal(true)
+ .setActions(
+ listOf(
+ SafetyCenterIssue.Action.Builder(action1)
+ .setConfirmationDialogDetails(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ .build()
+ )
+ )
+ .setAttributionTitle("Attribution title")
+ .setGroupId("group_id")
+ .build()
+
+ assertThat(safetyCenterIssue).recreatesEqual(SafetyCenterIssue.CREATOR)
+ }
+
+ @Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
- .addEqualityGroup(issue1, SafetyCenterIssue.Builder(issue1).build())
- .addEqualityGroup(issueWithRequiredFieldsOnly)
- .addEqualityGroup(
- SafetyCenterIssue.Builder("an id", "a title", "Please acknowledge this")
- .setSubtitle("In the neighborhood")
- .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
- .setActions(listOf(action1))
- .build(),
- SafetyCenterIssue.Builder("an id", "a title", "Please acknowledge this")
- .setSubtitle("In the neighborhood")
- .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
- .setActions(listOf(action1))
- .build()
- )
- .addEqualityGroup(SafetyCenterIssue.Builder(issue1).setId("a different id").build())
- .addEqualityGroup(
- SafetyCenterIssue.Builder(issue1).setTitle("a different title").build()
- )
- .addEqualityGroup(
- SafetyCenterIssue.Builder(issue1).setSubtitle("a different subtitle").build()
- )
- .addEqualityGroup(
- SafetyCenterIssue.Builder(issue1).setSummary("a different summary").build()
- )
- .addEqualityGroup(
- SafetyCenterIssue.Builder(issue1)
- .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
- .build()
- )
- .addEqualityGroup(SafetyCenterIssue.Builder(issue1).setDismissible(false).build())
- .addEqualityGroup(
- SafetyCenterIssue.Builder(issue1).setShouldConfirmDismissal(false).build()
- )
- .addEqualityGroup(SafetyCenterIssue.Builder(issue1).setActions(listOf(action2)).build())
- .test()
+ newTiramisuEqualsHashCodeToStringTester().test()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
+ newUpsideDownCakeEqualsHashCodeToStringTester().test()
}
@Test
@@ -280,6 +370,51 @@ class SafetyCenterIssueTest {
}
@Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun action_getConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+
+ assertFailsWith(UnsupportedOperationException::class) { action1.confirmationDialogDetails }
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun action_setConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ assertFailsWith(UnsupportedOperationException::class) {
+ SafetyCenterIssue.Action.Builder("action_id", "Action label", pendingIntent1)
+ .setConfirmationDialogDetails(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun action_getConfirmationDialogDetails_withDefaultBuilder_returnsNull() {
+ val action =
+ SafetyCenterIssue.Action.Builder("action_id", "Action label", pendingIntent1).build()
+
+ assertThat(action.confirmationDialogDetails).isNull()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun action_getConfirmationDialogDetails_whenSetExplicitly_returnsConfirmation() {
+ val action =
+ SafetyCenterIssue.Action.Builder("action_id", "Action label", pendingIntent1)
+ .setConfirmationDialogDetails(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ .build()
+
+ assertThat(action.confirmationDialogDetails)
+ .isEqualTo(ConfirmationDialogDetails("Title", "Text", "Accept", "Deny"))
+ }
+
+ @Test
fun action_describeContents_returns0() {
assertThat(action1.describeContents()).isEqualTo(0)
assertThat(action2.describeContents()).isEqualTo(0)
@@ -292,57 +427,96 @@ class SafetyCenterIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun action_parcelRoundTrip_recreatesEqual_atLeastAndroidU() {
+ val action =
+ SafetyCenterIssue.Action.Builder(action1)
+ .setConfirmationDialogDetails(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ .build()
+
+ assertThat(action).recreatesEqual(SafetyCenterIssue.Action.CREATOR)
+ }
+
+ @Test
fun action_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
- .addEqualityGroup(action1)
- .addEqualityGroup(action2)
+ issueActionNewTiramisuEqualsHashCodeToStringTester().test()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun action_equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ issueActionNewTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder = { SafetyCenterIssue.Action.Builder(it).build() }
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder(action1)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder(action2)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
+ .build()
+ )
.addEqualityGroup(
SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
.setWillResolve(true)
.setIsInFlight(true)
.setSuccessMessage("a success message")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build(),
SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
.setWillResolve(true)
.setIsInFlight(true)
.setSuccessMessage("a success message")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
.setSuccessMessage("a success message")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
SafetyCenterIssue.Action.Builder("a_different_id", "a label", pendingIntent1)
.setSuccessMessage("a success message")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
SafetyCenterIssue.Action.Builder("an_id", "a different label", pendingIntent1)
.setSuccessMessage("a success message")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent2)
.setSuccessMessage("a success message")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
.setWillResolve(true)
.setSuccessMessage("a success message")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
.setIsInFlight(true)
.setSuccessMessage("a success message")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
.setSuccessMessage("a different success message")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
@@ -350,8 +524,247 @@ class SafetyCenterIssueTest {
.setId("another_id")
.setLabel("another_label")
.setPendingIntent(pendingIntent2)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.test()
}
+
+ /**
+ * Creates a new [EqualsHashCodeToStringTester] instance with all the equality groups in the
+ * [newTiramisuEqualsHashCodeToStringTester] plus new equality groups covering all the new
+ * fields added in U.
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ private fun newUpsideDownCakeEqualsHashCodeToStringTester():
+ EqualsHashCodeToStringTester<SafetyCenterIssue> {
+ val issueWithTiramisuFields =
+ SafetyCenterIssue.Builder("issue_id", "Everything's good", "Please acknowledge this")
+ .setSubtitle("In the neighborhood")
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+ .setDismissible(true)
+ .setShouldConfirmDismissal(true)
+ .setActions(listOf(action1))
+ .build()
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ return newTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder = { SafetyCenterIssue.Builder(it).build() }
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issueWithTiramisuFields)
+ .setAttributionTitle("Attribution title")
+ .build(),
+ SafetyCenterIssue.Builder(issueWithTiramisuFields)
+ .setAttributionTitle("Attribution title")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issueWithTiramisuFields)
+ .setAttributionTitle("a different attribution title")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issueWithTiramisuFields)
+ .setAttributionTitle("Attribution title")
+ .setGroupId("group_id")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issueWithTiramisuFields).setGroupId("group_id").build(),
+ SafetyCenterIssue.Builder(issueWithTiramisuFields).setGroupId("group_id").build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issueWithTiramisuFields)
+ .setGroupId("a different group_id")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issueWithTiramisuFields)
+ .setActions(
+ listOf(
+ SafetyCenterIssue.Action.Builder(action1)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
+ .build()
+ )
+ )
+ .build()
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_getTitle_returnsTitle() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails.title).isEqualTo("Title")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_getText_returnsText() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails.text).isEqualTo("Text")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_getAcceptButtonText_returnsAcceptButtonText() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails.acceptButtonText).isEqualTo("Accept")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_getDenyButtonText_returnsDenyButtonText() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails.denyButtonText).isEqualTo("Deny")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_describeContents_returns0() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_parcelRoundTrip_recreatesEqual() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails).recreatesEqual(ConfirmationDialogDetails.CREATOR)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = ConfirmationDialogDetails.CREATOR
+ )
+ .addEqualityGroup(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny"),
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ .addEqualityGroup(ConfirmationDialogDetails("Other title", "Text", "Accept", "Deny"))
+ .addEqualityGroup(ConfirmationDialogDetails("Title", "Other text", "Accept", "Deny"))
+ .addEqualityGroup(ConfirmationDialogDetails("Title", "Text", "Other accept", "Deny"))
+ .addEqualityGroup(ConfirmationDialogDetails("Title", "Text", "Accept", "Other deny"))
+ .test()
+ }
+
+ /**
+ * Creates a new [EqualsHashCodeToStringTester] instance which covers all the fields in the T
+ * API and is safe to use on any T+ API level.
+ */
+ private fun newTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder: ((SafetyCenterIssue) -> SafetyCenterIssue)? = null
+ ) =
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterIssue.CREATOR,
+ createCopy = createCopyFromBuilder
+ )
+ .addEqualityGroup(issue1, SafetyCenterIssue.Builder(issue1).build())
+ .addEqualityGroup(issueWithRequiredFieldsOnly)
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder("an id", "a title", "Please acknowledge this")
+ .setSubtitle("In the neighborhood")
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+ .setActions(listOf(action1))
+ .build(),
+ SafetyCenterIssue.Builder("an id", "a title", "Please acknowledge this")
+ .setSubtitle("In the neighborhood")
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+ .setActions(listOf(action1))
+ .build()
+ )
+ .addEqualityGroup(SafetyCenterIssue.Builder(issue1).setId("a different id").build())
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issue1).setTitle("a different title").build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issue1).setSubtitle("a different subtitle").build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issue1).setSummary("a different summary").build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issue1)
+ .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
+ .build()
+ )
+ .addEqualityGroup(SafetyCenterIssue.Builder(issue1).setDismissible(false).build())
+ .addEqualityGroup(
+ SafetyCenterIssue.Builder(issue1).setShouldConfirmDismissal(false).build()
+ )
+ .addEqualityGroup(SafetyCenterIssue.Builder(issue1).setActions(listOf(action2)).build())
+
+ private fun issueActionNewTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder: ((SafetyCenterIssue.Action) -> SafetyCenterIssue.Action)? = null
+ ) =
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterIssue.Action.CREATOR,
+ createCopy = createCopyFromBuilder
+ )
+ .addEqualityGroup(action1)
+ .addEqualityGroup(action2)
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
+ .setWillResolve(true)
+ .setIsInFlight(true)
+ .setSuccessMessage("a success message")
+ .build(),
+ SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
+ .setWillResolve(true)
+ .setIsInFlight(true)
+ .setSuccessMessage("a success message")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
+ .setSuccessMessage("a success message")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder("a_different_id", "a label", pendingIntent1)
+ .setSuccessMessage("a success message")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder("an_id", "a different label", pendingIntent1)
+ .setSuccessMessage("a success message")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent2)
+ .setSuccessMessage("a success message")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
+ .setWillResolve(true)
+ .setSuccessMessage("a success message")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
+ .setIsInFlight(true)
+ .setSuccessMessage("a success message")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
+ .setSuccessMessage("a different success message")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetyCenterIssue.Action.Builder("an_id", "a label", pendingIntent1)
+ .setId("another_id")
+ .setLabel("another_label")
+ .setPendingIntent(pendingIntent2)
+ .build()
+ )
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
index 1d9ecc3e9..d4763da0a 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
@@ -17,6 +17,10 @@
package android.safetycenter.cts
import android.content.Context
+import android.os.Build
+import android.os.Build.VERSION.CODENAME
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.UserHandle.USER_NULL
import android.safetycenter.SafetyCenterData
import android.safetycenter.SafetyCenterEntry
@@ -35,6 +39,7 @@ import android.safetycenter.SafetyCenterManager
import android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN
+import android.safetycenter.SafetyCenterManager.REFRESH_REASON_PERIODIC
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK
import android.safetycenter.SafetyCenterStaticEntry
import android.safetycenter.SafetyCenterStaticEntryGroup
@@ -83,22 +88,31 @@ import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_DISABLED_
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_GROUP_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_HIDDEN_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_HIDDEN_WITH_SEARCH_ID
-import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_IN_COLLAPSIBLE_ID
-import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_IN_RIGID_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_IN_STATEFUL_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_IN_STATELESS_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_OTHER_PACKAGE_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.HIDDEN_ONLY_CONFIG
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_ALL_OPTIONAL_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_BAREBONE_ID
-import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_IN_RIGID_ID
-import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MIXED_COLLAPSIBLE_GROUP_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_GROUP_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_IN_STATELESS_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_SOURCE_CONFIG
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_SOURCE_NO_GROUP_TITLE_CONFIG
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MIXED_STATEFUL_GROUP_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MIXED_STATELESS_GROUP_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MULTIPLE_SOURCES_CONFIG
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MULTIPLE_SOURCES_GROUP_ID_1
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MULTIPLE_SOURCES_GROUP_ID_2
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.NO_PAGE_OPEN_CONFIG
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.PACKAGE_CERT_HASH_INVALID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SAMPLE_SOURCE_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SEVERITY_ZERO_CONFIG
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_CONFIG
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_INVALID_INTENT_CONFIG
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_OTHER_PACKAGE_CONFIG
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_WITH_FAKE_CERT
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_WITH_INVALID_CERT
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_1
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_2
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_3
@@ -107,11 +121,14 @@ import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_5
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_6
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_7
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.STATIC_BAREBONE_ID
-import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.STATIC_IN_COLLAPSIBLE_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.STATIC_IN_STATEFUL_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SUMMARY_TEST_CONFIG
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SUMMARY_TEST_GROUP_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.getLockScreenSourceConfig
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.multipleSourcesWithDeduplicationInfoConfig
import android.safetycenter.cts.testing.SafetyCenterCtsData
+import android.safetycenter.cts.testing.SafetyCenterCtsData.Companion.withAttributionTitleInIssuesIfAtLeastU
+import android.safetycenter.cts.testing.SafetyCenterCtsData.Companion.withDismissedIssuesIfAtLeastU
import android.safetycenter.cts.testing.SafetyCenterCtsHelper
import android.safetycenter.cts.testing.SafetyCenterCtsListener
import android.safetycenter.cts.testing.SafetyCenterEnabledChangedReceiver
@@ -132,6 +149,7 @@ import android.safetycenter.cts.testing.SafetySourceReceiver.Companion.refreshSa
import android.safetycenter.cts.testing.SafetySourceReceiver.Companion.refreshSafetySourcesWithoutReceiverPermissionAndWait
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
import com.android.compatibility.common.preconditions.ScreenLockHelper
import com.android.safetycenter.resources.SafetyCenterResourcesContext
import com.google.common.base.Preconditions.checkState
@@ -141,6 +159,7 @@ import java.time.Duration
import kotlin.test.assertFailsWith
import kotlinx.coroutines.TimeoutCancellationException
import org.junit.After
+import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Test
@@ -303,14 +322,14 @@ class SafetyCenterManagerTest {
private val safetyCenterEntryGroupMixedFromComplexConfig =
SafetyCenterEntryOrGroup(
- SafetyCenterEntryGroup.Builder(MIXED_COLLAPSIBLE_GROUP_ID, "OK")
+ SafetyCenterEntryGroup.Builder(MIXED_STATEFUL_GROUP_ID, "OK")
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNKNOWN)
.setSummary(safetyCenterResourcesContext.getStringByName("group_unknown_summary"))
.setEntries(
listOf(
- safetyCenterCtsData.safetyCenterEntryDefault(DYNAMIC_IN_COLLAPSIBLE_ID),
+ safetyCenterCtsData.safetyCenterEntryDefault(DYNAMIC_IN_STATEFUL_ID),
SafetyCenterEntry.Builder(
- SafetyCenterCtsData.entryId(STATIC_IN_COLLAPSIBLE_ID),
+ SafetyCenterCtsData.entryId(STATIC_IN_STATEFUL_ID),
"OK"
)
.setSeverityLevel(ENTRY_SEVERITY_LEVEL_UNSPECIFIED)
@@ -595,12 +614,30 @@ class SafetyCenterManagerTest {
SafetyCenterData(
safetyCenterCtsData.safetyCenterStatusCritical(6),
listOf(
- safetyCenterCtsData.safetyCenterIssueCritical(DYNAMIC_BAREBONE_ID),
- safetyCenterCtsData.safetyCenterIssueCritical(ISSUE_ONLY_BAREBONE_ID),
- safetyCenterCtsData.safetyCenterIssueRecommendation(DYNAMIC_DISABLED_ID),
- safetyCenterCtsData.safetyCenterIssueRecommendation(ISSUE_ONLY_ALL_OPTIONAL_ID),
- safetyCenterCtsData.safetyCenterIssueInformation(DYNAMIC_IN_RIGID_ID),
- safetyCenterCtsData.safetyCenterIssueInformation(ISSUE_ONLY_IN_RIGID_ID)
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ DYNAMIC_BAREBONE_ID,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ ISSUE_ONLY_BAREBONE_ID,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterCtsData.safetyCenterIssueRecommendation(
+ DYNAMIC_DISABLED_ID,
+ groupId = DYNAMIC_GROUP_ID
+ ),
+ safetyCenterCtsData.safetyCenterIssueRecommendation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ groupId = ISSUE_ONLY_GROUP_ID
+ ),
+ safetyCenterCtsData.safetyCenterIssueInformation(
+ DYNAMIC_IN_STATELESS_ID,
+ groupId = MIXED_STATELESS_GROUP_ID
+ ),
+ safetyCenterCtsData.safetyCenterIssueInformation(
+ ISSUE_ONLY_IN_STATELESS_ID,
+ groupId = MIXED_STATELESS_GROUP_ID
+ )
),
listOf(
SafetyCenterEntryOrGroup(
@@ -726,14 +763,14 @@ class SafetyCenterManagerTest {
}
@Test
- fun setSafetySourceData_sourceInRigidGroupUnspecified_setsValue() {
+ fun setSafetySourceData_sourceInStatelessGroupUnspecified_setsValue() {
safetyCenterCtsHelper.setConfig(COMPLEX_CONFIG)
val dataToSet = safetySourceCtsData.unspecified
- safetyCenterCtsHelper.setData(DYNAMIC_IN_RIGID_ID, dataToSet)
+ safetyCenterCtsHelper.setData(DYNAMIC_IN_STATELESS_ID, dataToSet)
val apiSafetySourceData =
- safetyCenterManager.getSafetySourceDataWithPermission(DYNAMIC_IN_RIGID_ID)
+ safetyCenterManager.getSafetySourceDataWithPermission(DYNAMIC_IN_STATELESS_ID)
assertThat(apiSafetySourceData).isEqualTo(dataToSet)
}
@@ -782,19 +819,52 @@ class SafetyCenterManagerTest {
}
@Test
- fun setSafetySourceData_sourceInRigidGroupNotUnspecified_throwsIllegalArgumentException() {
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun setSafetySourceData_wronglySignedPackage_throwsIllegalArgumentException() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_WITH_FAKE_CERT)
+
+ val thrown =
+ assertFailsWith(IllegalArgumentException::class) {
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, safetySourceCtsData.unspecified)
+ }
+
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo("Invalid signature for package " + context.packageName)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun setSafetySourceData_invalidPackageCertificate_throwsIllegalArgumentException() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_WITH_INVALID_CERT)
+
+ val thrown =
+ assertFailsWith(IllegalStateException::class) {
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, safetySourceCtsData.unspecified)
+ }
+
+ assertThat(thrown)
+ .hasMessageThat()
+ .isEqualTo("Failed to parse signing certificate: " + PACKAGE_CERT_HASH_INVALID)
+ }
+
+ @Test
+ fun setSafetySourceData_sourceInStatelessGroupNotUnspecified_throwsIllegalArgumentException() {
safetyCenterCtsHelper.setConfig(COMPLEX_CONFIG)
val thrown =
assertFailsWith(IllegalArgumentException::class) {
- safetyCenterCtsHelper.setData(DYNAMIC_IN_RIGID_ID, safetySourceCtsData.information)
+ safetyCenterCtsHelper.setData(
+ DYNAMIC_IN_STATELESS_ID,
+ safetySourceCtsData.information
+ )
}
assertThat(thrown)
.hasMessageThat()
.isEqualTo(
- "Safety source: $DYNAMIC_IN_RIGID_ID is in a rigid group but specified a severity" +
- " level: ${SafetySourceData.SEVERITY_LEVEL_INFORMATION}"
+ "Safety source: $DYNAMIC_IN_STATELESS_ID is in a stateless group but specified a " +
+ "severity level: $SEVERITY_LEVEL_INFORMATION"
)
}
@@ -913,8 +983,8 @@ class SafetyCenterManagerTest {
.hasMessageThat()
.isEqualTo(
"Unexpected severity level: ${
- SafetySourceData.SEVERITY_LEVEL_INFORMATION
- }, for issue in safety source: $SINGLE_SOURCE_ID"
+ SafetySourceData.SEVERITY_LEVEL_INFORMATION
+ }, for issue in safety source: $SINGLE_SOURCE_ID"
)
}
@@ -934,8 +1004,8 @@ class SafetyCenterManagerTest {
.hasMessageThat()
.isEqualTo(
"Unexpected severity level: ${
- SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
- }, for safety source: $SINGLE_SOURCE_ID"
+ SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
+ }, for safety source: $SINGLE_SOURCE_ID"
)
}
@@ -967,8 +1037,8 @@ class SafetyCenterManagerTest {
.hasMessageThat()
.isEqualTo(
"Unexpected severity level: ${
- SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
- }, for safety source: $DYNAMIC_ALL_OPTIONAL_ID"
+ SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
+ }, for safety source: $DYNAMIC_ALL_OPTIONAL_ID"
)
}
@@ -1003,8 +1073,8 @@ class SafetyCenterManagerTest {
.hasMessageThat()
.isEqualTo(
"Unexpected severity level: ${
- SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
- }, for issue in safety source: $ISSUE_ONLY_ALL_OPTIONAL_ID"
+ SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
+ }, for issue in safety source: $ISSUE_ONLY_ALL_OPTIONAL_ID"
)
}
@@ -1424,11 +1494,12 @@ class SafetyCenterManagerTest {
}
@Test
- fun refreshSafetySources_withRefreshReasonPageOpen_notCalledIfSourceDoesntSupportPageOpen() {
+ fun refreshSafetySources_reasonPageOpen_noConditionsMet_noBroadcastSent() {
safetyCenterCtsHelper.setConfig(NO_PAGE_OPEN_CONFIG)
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, safetySourceCtsData.information)
SafetySourceReceiver.setResponse(
Request.Refresh(SINGLE_SOURCE_ID),
- Response.SetData(safetySourceCtsData.information)
+ Response.SetData(safetySourceCtsData.informationWithIssue)
)
assertFailsWith(TimeoutCancellationException::class) {
@@ -1440,7 +1511,90 @@ class SafetyCenterManagerTest {
val apiSafetySourceData =
safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
- assertThat(apiSafetySourceData).isNull()
+ assertThat(apiSafetySourceData).isEqualTo(safetySourceCtsData.information)
+ }
+
+ @Test
+ fun refreshSafetySources_reasonPageOpen_allowedByConfig_broadcastSent() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, safetySourceCtsData.information)
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceCtsData.informationWithIssue)
+ )
+
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN
+ )
+
+ val apiSafetySourceData =
+ safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ assertThat(apiSafetySourceData).isEqualTo(safetySourceCtsData.informationWithIssue)
+ }
+
+ @Test
+ fun refreshSafetySources_reasonPageOpen_allowedByFlag_broadcastSent() {
+ safetyCenterCtsHelper.setConfig(NO_PAGE_OPEN_CONFIG)
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, safetySourceCtsData.information)
+ SafetyCenterFlags.overrideRefreshOnPageOpenSources = setOf(SINGLE_SOURCE_ID)
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceCtsData.informationWithIssue)
+ )
+
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN
+ )
+
+ val apiSafetySourceData =
+ safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ assertThat(apiSafetySourceData).isEqualTo(safetySourceCtsData.informationWithIssue)
+ }
+
+ @Test
+ fun refreshSafetySources_reasonPageOpen_allowedByFlagLater_broadcastSentLater() {
+ safetyCenterCtsHelper.setConfig(NO_PAGE_OPEN_CONFIG)
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, safetySourceCtsData.information)
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceCtsData.informationWithIssue)
+ )
+
+ assertFailsWith(TimeoutCancellationException::class) {
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN,
+ TIMEOUT_SHORT
+ )
+ }
+ val apiSafetySourceDataBeforeSettingFlag =
+ safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ SafetyCenterFlags.overrideRefreshOnPageOpenSources = setOf(SINGLE_SOURCE_ID)
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN
+ )
+ val apiSafetySourceDataAfterSettingFlag =
+ safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+
+ assertThat(apiSafetySourceDataBeforeSettingFlag).isEqualTo(safetySourceCtsData.information)
+ assertThat(apiSafetySourceDataAfterSettingFlag)
+ .isEqualTo(safetySourceCtsData.informationWithIssue)
+ }
+
+ @Test
+ fun refreshSafetySources_reasonPageOpen_noDataForSource_broadcastSent() {
+ safetyCenterCtsHelper.setConfig(NO_PAGE_OPEN_CONFIG)
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceCtsData.information)
+ )
+
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN
+ )
+
+ val apiSafetySourceData =
+ safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ assertThat(apiSafetySourceData).isEqualTo(safetySourceCtsData.information)
}
@Test
@@ -1491,6 +1645,7 @@ class SafetyCenterManagerTest {
@Test
fun refreshSafetySources_withMultipleSourcesOnPageOpen_onlyUpdatesAllowedSources() {
safetyCenterCtsHelper.setConfig(MULTIPLE_SOURCES_CONFIG)
+ safetyCenterCtsHelper.setData(SOURCE_ID_3, safetySourceCtsData.information)
SafetySourceReceiver.apply {
setResponse(
Request.Refresh(SOURCE_ID_1),
@@ -1498,7 +1653,7 @@ class SafetyCenterManagerTest {
)
setResponse(
Request.Refresh(SOURCE_ID_3),
- Response.SetData(safetySourceCtsData.information)
+ Response.SetData(safetySourceCtsData.informationWithIssue)
)
}
@@ -1516,7 +1671,7 @@ class SafetyCenterManagerTest {
// SOURCE_ID_3 doesn't support refresh on page open.
val apiSafetySourceData3 =
safetyCenterManager.getSafetySourceDataWithPermission(SOURCE_ID_3)
- assertThat(apiSafetySourceData3).isNull()
+ assertThat(apiSafetySourceData3).isEqualTo(safetySourceCtsData.information)
}
@Test
@@ -2024,6 +2179,129 @@ class SafetyCenterManagerTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun refreshSafetySources_withRefreshReasonPeriodic_noBackgroundRefreshSourceDoesNotSendData() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceCtsData.information)
+ )
+ SafetyCenterFlags.backgroundRefreshDeniedSources = setOf(SINGLE_SOURCE_ID)
+
+ assertFailsWith(TimeoutCancellationException::class) {
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PERIODIC,
+ TIMEOUT_SHORT
+ )
+ }
+
+ val apiSafetySourceData =
+ safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ assertThat(apiSafetySourceData).isNull()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun refreshSafetySources_withRefreshReasonPeriodic_backgroundRefreshSourceSendsData() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceCtsData.criticalWithResolvingGeneralIssue)
+ )
+
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PERIODIC
+ )
+
+ val sourceData = safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ assertThat(sourceData).isEqualTo(safetySourceCtsData.criticalWithResolvingGeneralIssue)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun refreshSafetySources_withSafetySourceIds_onlySpecifiedSourcesSendData() {
+ safetyCenterCtsHelper.setConfig(MULTIPLE_SOURCES_CONFIG)
+ SafetySourceReceiver.apply {
+ setResponse(
+ Request.Refresh(SOURCE_ID_1),
+ Response.SetData(safetySourceCtsData.information)
+ )
+ setResponse(
+ Request.Refresh(SOURCE_ID_2),
+ Response.SetData(safetySourceCtsData.information)
+ )
+ setResponse(
+ Request.Refresh(SOURCE_ID_3),
+ Response.SetData(safetySourceCtsData.information)
+ )
+ }
+
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN,
+ safetySourceIds = listOf(SOURCE_ID_1, SOURCE_ID_2)
+ )
+
+ val apiSafetySourceData1 =
+ safetyCenterManager.getSafetySourceDataWithPermission(SOURCE_ID_1)
+ assertThat(apiSafetySourceData1).isEqualTo(safetySourceCtsData.information)
+ val apiSafetySourceData2 =
+ safetyCenterManager.getSafetySourceDataWithPermission(SOURCE_ID_2)
+ assertThat(apiSafetySourceData2).isEqualTo(safetySourceCtsData.information)
+ val apiSafetySourceData3 =
+ safetyCenterManager.getSafetySourceDataWithPermission(SOURCE_ID_3)
+ assertThat(apiSafetySourceData3).isNull()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun refreshSafetySources_withEmptySafetySourceIds_NoSourcesSendData() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceCtsData.criticalWithResolvingGeneralIssue)
+ )
+
+ assertFailsWith(TimeoutCancellationException::class) {
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN,
+ TIMEOUT_SHORT,
+ emptyList()
+ )
+ }
+
+ val sourceData = safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ assertThat(sourceData).isNull()
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun refreshSafetySources_versionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ safetyCenterCtsHelper.setConfig(MULTIPLE_SOURCES_CONFIG)
+
+ val exception =
+ assertFailsWith(UnsupportedOperationException::class) {
+ safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
+ REFRESH_REASON_PAGE_OPEN,
+ safetySourceIds = listOf(SOURCE_ID_1, SOURCE_ID_3)
+ )
+ }
+
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("Method not supported for versions lower than UPSIDE_DOWN_CAKE")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun refreshSafetySources_withSafetySourceIds_withoutPermission_throwsSecurityException() {
+ assertFailsWith(SecurityException::class) {
+ safetyCenterManager.refreshSafetySources(REFRESH_REASON_PAGE_OPEN, listOf())
+ }
+ }
+
+ @Test
fun refreshSafetySources_withoutPermission_throwsSecurityException() {
assertFailsWith(SecurityException::class) {
safetyCenterManager.refreshSafetySources(REFRESH_REASON_RESCAN_BUTTON_CLICK)
@@ -2105,11 +2383,78 @@ class SafetyCenterManagerTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_attributionTitleProvidedBySource_returnsIssueWithAttributionTitle() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(
+ SINGLE_SOURCE_ID,
+ safetySourceCtsData.informationWithIssueWithAttributionTitle
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ val expectedSafetyCenterData =
+ safetyCenterDataOkOneAlert.withAttributionTitleInIssuesIfAtLeastU("Attribution Title")
+ assertThat(apiSafetyCenterData).isEqualTo(expectedSafetyCenterData)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_attributionTitleNotProvided_returnsGroupTitleAsAttributionTitle() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, safetySourceCtsData.informationWithIssue)
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ val expectedSafetyCenterData =
+ safetyCenterDataOkOneAlert.withAttributionTitleInIssuesIfAtLeastU("OK")
+ assertThat(apiSafetyCenterData).isEqualTo(expectedSafetyCenterData)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_attributionNotSetAndGroupTitleNull_returnsNullAttributionTitle() {
+ safetyCenterCtsHelper.setConfig(ISSUE_ONLY_SOURCE_NO_GROUP_TITLE_CONFIG)
+ safetyCenterCtsHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceCtsData.issuesOnly(safetySourceCtsData.recommendationGeneralIssue)
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ val expectedSafetyCenterData =
+ SafetyCenterData(
+ safetyCenterStatusGeneralRecommendationOneAlert,
+ listOf(
+ safetyCenterCtsData.safetyCenterIssueRecommendation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ attributionTitle = null
+ )
+ ),
+ emptyList(),
+ emptyList()
+ )
+ assertThat(apiSafetyCenterData).isEqualTo(expectedSafetyCenterData)
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun getSafetyCenterData_attributionNotSetBySourceOnTiramisu_returnsNullAttributionTitle() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(CODENAME == "UpsideDownCake")
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, safetySourceCtsData.informationWithIssue)
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ assertThat(apiSafetyCenterData).isEqualTo(safetyCenterDataOkOneAlert)
+ }
+
+ @Test
fun getSafetyCenterData_withUpdatedData_returnsUpdatedData() {
safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, safetySourceCtsData.information)
- val previousApiSafetyCenterData =
- safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID)
+ val previousApiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
safetyCenterCtsHelper.setData(
SINGLE_SOURCE_ID,
safetySourceCtsData.criticalWithResolvingGeneralIssue
@@ -2223,6 +2568,156 @@ class SafetyCenterManagerTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_withRecommendationDataIssue_returnsDataRecommendationStatus() {
+ safetyCenterCtsHelper.setConfig(ISSUE_ONLY_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData
+ .defaultRecommendationIssueBuilder()
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DATA)
+ .build()
+ )
+ )
+
+ val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
+
+ assertThat(apiSafetyCenterStatus)
+ .isEqualTo(
+ safetyCenterCtsData.safetyCenterStatusOneAlert(
+ "overall_severity_level_data_recommendation_title",
+ OVERALL_SEVERITY_LEVEL_RECOMMENDATION
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_withCriticalDataIssue_returnsDataCriticalStatus() {
+ safetyCenterCtsHelper.setConfig(ISSUE_ONLY_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData
+ .defaultCriticalResolvingIssueBuilder()
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DATA)
+ .build()
+ )
+ )
+
+ val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
+
+ assertThat(apiSafetyCenterStatus)
+ .isEqualTo(
+ safetyCenterCtsData.safetyCenterStatusOneAlert(
+ "overall_severity_level_critical_data_warning_title",
+ OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_withRecommendationPasswordsIssue_returnsDataRecommendationStatus() {
+ safetyCenterCtsHelper.setConfig(ISSUE_ONLY_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData
+ .defaultRecommendationIssueBuilder()
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS)
+ .build()
+ )
+ )
+
+ val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
+
+ assertThat(apiSafetyCenterStatus)
+ .isEqualTo(
+ safetyCenterCtsData.safetyCenterStatusOneAlert(
+ "overall_severity_level_passwords_recommendation_title",
+ OVERALL_SEVERITY_LEVEL_RECOMMENDATION
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_withCriticalPasswordsIssue_returnsDataCriticalStatus() {
+ safetyCenterCtsHelper.setConfig(ISSUE_ONLY_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData
+ .defaultCriticalResolvingIssueBuilder()
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS)
+ .build()
+ )
+ )
+
+ val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
+
+ assertThat(apiSafetyCenterStatus)
+ .isEqualTo(
+ safetyCenterCtsData.safetyCenterStatusOneAlert(
+ "overall_severity_level_critical_passwords_warning_title",
+ OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_withRecommendationPersonalIssue_returnsDataRecommendationStatus() {
+ safetyCenterCtsHelper.setConfig(ISSUE_ONLY_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData
+ .defaultRecommendationIssueBuilder()
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY)
+ .build()
+ )
+ )
+
+ val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
+
+ assertThat(apiSafetyCenterStatus)
+ .isEqualTo(
+ safetyCenterCtsData.safetyCenterStatusOneAlert(
+ "overall_severity_level_personal_recommendation_title",
+ OVERALL_SEVERITY_LEVEL_RECOMMENDATION
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_withCriticalPersonalIssue_returnsDataCriticalStatus() {
+ safetyCenterCtsHelper.setConfig(ISSUE_ONLY_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData
+ .defaultCriticalResolvingIssueBuilder()
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY)
+ .build()
+ )
+ )
+
+ val apiSafetyCenterStatus = safetyCenterManager.getSafetyCenterDataWithPermission().status
+
+ assertThat(apiSafetyCenterStatus)
+ .isEqualTo(
+ safetyCenterCtsData.safetyCenterStatusOneAlert(
+ "overall_severity_level_critical_personal_warning_title",
+ OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING
+ )
+ )
+ }
+
+ @Test
fun getSafetyCenterData_singleSourceIssues_returnsOverallStatusBasedOnHigherSeverityIssue() {
safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
safetyCenterCtsHelper.setData(
@@ -2270,6 +2765,629 @@ class SafetyCenterManagerTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_duplicateIssuesOfSameSeverities_issueOfFirstSourceInConfigShown() {
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+
+ val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
+
+ assertThat(apiSafetyCenterIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_1,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_differentDuplicationId_bothIssuesShown() {
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("different")
+ )
+ )
+
+ val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
+
+ assertThat(apiSafetyCenterIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_1,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ ),
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ )
+ )
+ .inOrder()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_differentDuplicationGroup_bothIssuesShown() {
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_3
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_6,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+
+ val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
+
+ assertThat(apiSafetyCenterIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_1,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ ),
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_6,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ )
+ )
+ .inOrder()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_threeDuplicateIssues_onlyOneIssueShown() {
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_3
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_4,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_3
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_6,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_3
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_7,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+
+ val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
+
+ assertThat(apiSafetyCenterIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_4,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_duplicateIssuesOfDifferentSeverities_moreSevereIssueShown() {
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_2,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+
+ val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
+
+ assertThat(apiSafetyCenterIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_multipleDuplicationsOfIssues_correctlyDeduplicated() {
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("A")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_2,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("A")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_2
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_3,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("B")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_3
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_4,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("B")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("A")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_3
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_6,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("B")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_3
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_7,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("B")
+ )
+ )
+
+ val apiSafetyCenterIssues = safetyCenterManager.getSafetyCenterDataWithPermission().issues
+
+ assertThat(apiSafetyCenterIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ ),
+ safetyCenterCtsData.safetyCenterIssueRecommendation(
+ SOURCE_ID_3,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ ),
+ safetyCenterCtsData.safetyCenterIssueRecommendation(
+ SOURCE_ID_4,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ )
+ )
+ .inOrder()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_duplicateIssuesBothDismissed_topOneShownAsDismissed() {
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("same")
+ )
+ )
+ safetyCenterManager.dismissSafetyCenterIssueWithPermission(
+ SafetyCenterCtsData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
+ )
+ safetyCenterManager.dismissSafetyCenterIssueWithPermission(
+ SafetyCenterCtsData.issueId(SOURCE_ID_5, RECOMMENDATION_ISSUE_ID)
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+ val apiSafetyCenterIssues = apiSafetyCenterData.issues
+ val apiSafetyCenterDismissedIssues = apiSafetyCenterData.dismissedIssues
+
+ assertThat(apiSafetyCenterIssues).isEmpty()
+ assertThat(apiSafetyCenterDismissedIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_1,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_duplicateIssuesLowerSeverityOneDismissed_topOneShown() {
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("same")
+ )
+ )
+ safetyCenterManager.dismissSafetyCenterIssueWithPermission(
+ SafetyCenterCtsData.issueId(SOURCE_ID_5, RECOMMENDATION_ISSUE_ID)
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+ val apiSafetyCenterIssues = apiSafetyCenterData.issues
+ val apiSafetyCenterDismissedIssues = apiSafetyCenterData.dismissedIssues
+
+ assertThat(apiSafetyCenterIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_1,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ )
+ )
+ assertThat(apiSafetyCenterDismissedIssues).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_duplicateIssuesHigherSeverityOneDismissed_topOneShownAsDismissed() {
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("same")
+ )
+ )
+ safetyCenterManager.dismissSafetyCenterIssueWithPermission(
+ SafetyCenterCtsData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+ val apiSafetyCenterIssues = apiSafetyCenterData.issues
+ val apiSafetyCenterDismissedIssues = apiSafetyCenterData.dismissedIssues
+
+ assertThat(apiSafetyCenterIssues).isEmpty()
+ assertThat(apiSafetyCenterDismissedIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_1,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_dupIssuesLowerPrioritySameSeverityOneDismissed_topShownAsDismissed() {
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ safetyCenterManager.dismissSafetyCenterIssueWithPermission(
+ SafetyCenterCtsData.issueId(SOURCE_ID_5, CRITICAL_ISSUE_ID)
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+ val apiSafetyCenterIssues = apiSafetyCenterData.issues
+ val apiSafetyCenterDismissedIssues = apiSafetyCenterData.dismissedIssues
+
+ assertThat(apiSafetyCenterIssues).isEmpty()
+ assertThat(apiSafetyCenterDismissedIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_1,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_dupIssuesTopOneDismissedThenDisappears_bottomOneReemergesTimely() {
+ SafetyCenterFlags.resurfaceIssueMaxCounts = mapOf(SEVERITY_LEVEL_CRITICAL_WARNING to 99L)
+ SafetyCenterFlags.resurfaceIssueDelays =
+ mapOf(SEVERITY_LEVEL_CRITICAL_WARNING to RESURFACE_DELAY)
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ safetyCenterManager.dismissSafetyCenterIssueWithPermission(
+ SafetyCenterCtsData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
+ )
+ safetyCenterManager.getSafetyCenterDataWithPermission() // data used, 2nd issue dismissed
+ safetyCenterCtsHelper.setData(SOURCE_ID_1, SafetySourceCtsData.issuesOnly())
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+ val apiSafetyCenterDismissedIssues = apiSafetyCenterData.dismissedIssues
+
+ assertThat(apiSafetyCenterDismissedIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ )
+ )
+ waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
+ val hasResurfaced =
+ safetyCenterManager
+ .getSafetyCenterDataWithPermission()
+ .issues
+ .contains(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ )
+ )
+ hasResurfaced
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_dupsOfDiffSeveritiesTopOneDismissedThenGone_bottomOneReemergesTimely() {
+ SafetyCenterFlags.resurfaceIssueMaxCounts =
+ mapOf(
+ SEVERITY_LEVEL_INFORMATION to 0L,
+ SEVERITY_LEVEL_RECOMMENDATION to 99L,
+ SEVERITY_LEVEL_CRITICAL_WARNING to 0L
+ )
+ SafetyCenterFlags.resurfaceIssueDelays =
+ mapOf(
+ SEVERITY_LEVEL_INFORMATION to Duration.ZERO,
+ SEVERITY_LEVEL_RECOMMENDATION to RESURFACE_DELAY,
+ SEVERITY_LEVEL_CRITICAL_WARNING to Duration.ZERO
+ )
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("same")
+ )
+ )
+ safetyCenterManager.dismissSafetyCenterIssueWithPermission(
+ SafetyCenterCtsData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
+ )
+ safetyCenterManager.getSafetyCenterDataWithPermission() // data used, 2nd issue dismissed
+ safetyCenterCtsHelper.setData(SOURCE_ID_1, SafetySourceCtsData.issuesOnly())
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+ val apiSafetyCenterDismissedIssues = apiSafetyCenterData.dismissedIssues
+
+ assertThat(apiSafetyCenterDismissedIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueRecommendation(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ )
+ )
+ waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
+ val hasResurfaced =
+ safetyCenterManager
+ .getSafetyCenterDataWithPermission()
+ .issues
+ .contains(
+ safetyCenterCtsData.safetyCenterIssueRecommendation(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ )
+ )
+ hasResurfaced
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_duplicateIssuesLowerOneResurfaces_lowerOneStillFilteredOut() {
+ SafetyCenterFlags.resurfaceIssueMaxCounts =
+ mapOf(
+ SEVERITY_LEVEL_INFORMATION to 0L,
+ SEVERITY_LEVEL_RECOMMENDATION to 99L,
+ SEVERITY_LEVEL_CRITICAL_WARNING to 99L
+ )
+ SafetyCenterFlags.resurfaceIssueDelays =
+ mapOf(
+ SEVERITY_LEVEL_INFORMATION to Duration.ZERO,
+ SEVERITY_LEVEL_RECOMMENDATION to RESURFACE_DELAY,
+ SEVERITY_LEVEL_CRITICAL_WARNING to RESURFACE_DELAY.multipliedBy(100)
+ )
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("same")
+ )
+ )
+ safetyCenterManager.dismissSafetyCenterIssueWithPermission(
+ SafetyCenterCtsData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
+ )
+
+ // data used, 2nd issue dismissed
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+ val apiSafetyCenterDismissedIssues = apiSafetyCenterData.dismissedIssues
+
+ assertThat(apiSafetyCenterDismissedIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_1,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ )
+ )
+ assertFailsWith(TimeoutCancellationException::class) {
+ waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
+ val hasResurfaced =
+ safetyCenterManager
+ .getSafetyCenterDataWithPermission()
+ .issues
+ .contains(
+ safetyCenterCtsData.safetyCenterIssueRecommendation(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2
+ )
+ )
+ hasResurfaced
+ }
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getSafetyCenterData_duplicateIssuesTopOneResurfaces_topOneShown() {
+ SafetyCenterFlags.resurfaceIssueMaxCounts =
+ mapOf(
+ SEVERITY_LEVEL_INFORMATION to 0L,
+ SEVERITY_LEVEL_RECOMMENDATION to 99L,
+ SEVERITY_LEVEL_CRITICAL_WARNING to 99L
+ )
+ SafetyCenterFlags.resurfaceIssueDelays =
+ mapOf(
+ SEVERITY_LEVEL_INFORMATION to Duration.ZERO,
+ SEVERITY_LEVEL_RECOMMENDATION to RESURFACE_DELAY.multipliedBy(100),
+ SEVERITY_LEVEL_CRITICAL_WARNING to RESURFACE_DELAY
+ )
+ safetyCenterCtsHelper.setConfig(multipleSourcesWithDeduplicationInfoConfig)
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_1,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.criticalIssueWithDeduplicationId("same")
+ )
+ )
+ // Belongs to DEDUPLICATION_GROUP_1
+ safetyCenterCtsHelper.setData(
+ SOURCE_ID_5,
+ SafetySourceCtsData.issuesOnly(
+ safetySourceCtsData.recommendationIssueWithDeduplicationId("same")
+ )
+ )
+ safetyCenterManager.dismissSafetyCenterIssueWithPermission(
+ SafetyCenterCtsData.issueId(SOURCE_ID_1, CRITICAL_ISSUE_ID)
+ )
+
+ // data used, 2nd issue dismissed
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+ val apiSafetyCenterDismissedIssues = apiSafetyCenterData.dismissedIssues
+
+ assertThat(apiSafetyCenterDismissedIssues)
+ .containsExactly(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_1,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ )
+ )
+ waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
+ val hasResurfaced =
+ safetyCenterManager
+ .getSafetyCenterDataWithPermission()
+ .issues
+ .contains(
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ SOURCE_ID_1,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_1
+ )
+ )
+ hasResurfaced
+ }
+ }
+
+ @Test
fun getSafetyCenterData_criticalDeviceIssues_returnsOverallStatusBasedOnAddIssueCallOrder() {
safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
safetyCenterCtsHelper.setData(
@@ -2320,9 +3438,12 @@ class SafetyCenterManagerTest {
ISSUE_ONLY_ALL_OPTIONAL_ID,
SafetySourceCtsData.issuesOnly(safetySourceCtsData.recommendationGeneralIssue)
)
- safetyCenterCtsHelper.setData(DYNAMIC_IN_RIGID_ID, safetySourceCtsData.unspecifiedWithIssue)
safetyCenterCtsHelper.setData(
- ISSUE_ONLY_IN_RIGID_ID,
+ DYNAMIC_IN_STATELESS_ID,
+ safetySourceCtsData.unspecifiedWithIssue
+ )
+ safetyCenterCtsHelper.setData(
+ ISSUE_ONLY_IN_STATELESS_ID,
SafetySourceCtsData.issuesOnly(safetySourceCtsData.informationIssue)
)
@@ -2381,7 +3502,7 @@ class SafetyCenterManagerTest {
entrySummary = "critical 2"
)
)
- // STATIC_IN_COLLAPSIBLE_ID behaves like an UNSPECIFIED dynamic entry
+ // STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
val group =
safetyCenterManager.getSafetyCenterDataWithPermission().getGroup(SUMMARY_TEST_GROUP_ID)
@@ -2434,7 +3555,7 @@ class SafetyCenterManagerTest {
)
)
// SOURCE_ID_7 leave as an UNKNOWN dynamic entry
- // STATIC_IN_COLLAPSIBLE_ID behaves like an UNSPECIFIED dynamic entry
+ // STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
val group =
safetyCenterManager.getSafetyCenterDataWithPermission().getGroup(SUMMARY_TEST_GROUP_ID)
@@ -2497,7 +3618,7 @@ class SafetyCenterManagerTest {
entrySummary = "unspecified 3"
)
)
- // STATIC_IN_COLLAPSIBLE_ID behaves like an UNSPECIFIED dynamic entry
+ // STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
val group =
safetyCenterManager.getSafetyCenterDataWithPermission().getGroup(SUMMARY_TEST_GROUP_ID)
@@ -2558,7 +3679,7 @@ class SafetyCenterManagerTest {
entrySummary = "unspecified 4"
)
)
- // STATIC_IN_COLLAPSIBLE_ID behaves like an UNSPECIFIED dynamic entry
+ // STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
val group =
safetyCenterManager.getSafetyCenterDataWithPermission().getGroup(SUMMARY_TEST_GROUP_ID)
@@ -2620,7 +3741,7 @@ class SafetyCenterManagerTest {
entrySummary = "unspecified 7"
)
)
- // STATIC_IN_COLLAPSIBLE_ID behaves like an UNSPECIFIED dynamic entry
+ // STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
val group =
safetyCenterManager.getSafetyCenterDataWithPermission().getGroup(SUMMARY_TEST_GROUP_ID)
@@ -2664,7 +3785,7 @@ class SafetyCenterManagerTest {
)
)
// SOURCE_ID_7 leave as an UNKNOWN dynamic entry
- // STATIC_IN_COLLAPSIBLE_ID behaves like an UNSPECIFIED dynamic entry
+ // STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
val group =
safetyCenterManager.getSafetyCenterDataWithPermission().getGroup(SUMMARY_TEST_GROUP_ID)
@@ -2705,7 +3826,7 @@ class SafetyCenterManagerTest {
entrySummary = "information"
)
)
- // STATIC_IN_COLLAPSIBLE_ID behaves like an UNSPECIFIED dynamic entry
+ // STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
val group =
safetyCenterManager.getSafetyCenterDataWithPermission().getGroup(SUMMARY_TEST_GROUP_ID)
@@ -2750,7 +3871,7 @@ class SafetyCenterManagerTest {
withIssue = true
)
)
- // STATIC_IN_COLLAPSIBLE_ID behaves like an UNSPECIFIED dynamic entry
+ // STATIC_IN_STATEFUL_ID behaves like an UNSPECIFIED dynamic entry
val group =
safetyCenterManager.getSafetyCenterDataWithPermission().getGroup(SUMMARY_TEST_GROUP_ID)
@@ -3077,7 +4198,11 @@ class SafetyCenterManagerTest {
)
val safetyCenterDataFromListener = listener.receiveSafetyCenterData()
- assertThat(safetyCenterDataFromListener).isEqualTo(safetyCenterDataOkReviewCriticalEntry)
+ val expectedSafetyCenterData =
+ safetyCenterDataOkReviewCriticalEntry.withDismissedIssuesIfAtLeastU(
+ listOf(safetyCenterCtsData.safetyCenterIssueCritical(SINGLE_SOURCE_ID))
+ )
+ assertThat(safetyCenterDataFromListener).isEqualTo(expectedSafetyCenterData)
}
@Test
@@ -3118,7 +4243,6 @@ class SafetyCenterManagerTest {
safetySourceCtsData.criticalWithResolvingGeneralIssue
)
val listener = safetyCenterCtsHelper.addListener()
-
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterCtsData.issueId(
SINGLE_SOURCE_ID,
@@ -3128,7 +4252,11 @@ class SafetyCenterManagerTest {
)
val safetyCenterDataFromListener = listener.receiveSafetyCenterData()
- assertThat(safetyCenterDataFromListener).isEqualTo(safetyCenterDataOkReviewCriticalEntry)
+ val expectedSafetyCenterData =
+ safetyCenterDataOkReviewCriticalEntry.withDismissedIssuesIfAtLeastU(
+ listOf(safetyCenterCtsData.safetyCenterIssueCritical(SINGLE_SOURCE_ID))
+ )
+ assertThat(safetyCenterDataFromListener).isEqualTo(expectedSafetyCenterData)
}
@Test
@@ -3149,8 +4277,11 @@ class SafetyCenterManagerTest {
)
val safetyCenterDataAfterDismissal = listener.receiveSafetyCenterData()
- assertThat(safetyCenterDataAfterDismissal)
- .isEqualTo(safetyCenterDataOkReviewRecommendationEntry)
+ val expectedSafetyCenterData =
+ safetyCenterDataOkReviewRecommendationEntry.withDismissedIssuesIfAtLeastU(
+ listOf(safetyCenterCtsData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID))
+ )
+ assertThat(safetyCenterDataAfterDismissal).isEqualTo(expectedSafetyCenterData)
val safetyCenterDataAfterSourceHandledDismissal = listener.receiveSafetyCenterData()
assertThat(safetyCenterDataAfterSourceHandledDismissal).isEqualTo(safetyCenterDataOk)
}
@@ -3163,14 +4294,16 @@ class SafetyCenterManagerTest {
safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, recommendationDismissPendingIntentIssue)
recommendationDismissPendingIntentIssue.issues.first().onDismissPendingIntent!!.cancel()
val listener = safetyCenterCtsHelper.addListener()
-
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterCtsData.issueId(SINGLE_SOURCE_ID, RECOMMENDATION_ISSUE_ID)
)
val safetyCenterDataFromListener = listener.receiveSafetyCenterData()
- assertThat(safetyCenterDataFromListener)
- .isEqualTo(safetyCenterDataOkReviewRecommendationEntry)
+ val exectedSafetyCenterData =
+ safetyCenterDataOkReviewRecommendationEntry.withDismissedIssuesIfAtLeastU(
+ listOf(safetyCenterCtsData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID))
+ )
+ assertThat(safetyCenterDataFromListener).isEqualTo(exectedSafetyCenterData)
assertFailsWith(TimeoutCancellationException::class) {
listener.receiveSafetyCenterErrorDetails(TIMEOUT_SHORT)
}
@@ -3253,10 +4386,15 @@ class SafetyCenterManagerTest {
SafetyCenterCtsData.issueId(SINGLE_SOURCE_ID, INFORMATION_ISSUE_ID)
)
+ val expectedSafetyCenterData =
+ safetyCenterDataOk.withDismissedIssuesIfAtLeastU(
+ listOf(safetyCenterCtsData.safetyCenterIssueInformation(SINGLE_SOURCE_ID))
+ )
assertFailsWith(TimeoutCancellationException::class) {
waitForWithTimeout(timeout = TIMEOUT_SHORT) {
val hasResurfaced =
- safetyCenterManager.getSafetyCenterDataWithPermission() != safetyCenterDataOk
+ safetyCenterManager.getSafetyCenterDataWithPermission() !=
+ expectedSafetyCenterData
hasResurfaced
}
}
@@ -3285,10 +4423,15 @@ class SafetyCenterManagerTest {
SafetyCenterCtsData.issueId(SINGLE_SOURCE_ID, INFORMATION_ISSUE_ID)
)
+ val expectedSafetyCenterData =
+ safetyCenterDataOk.withDismissedIssuesIfAtLeastU(
+ listOf(safetyCenterCtsData.safetyCenterIssueInformation(SINGLE_SOURCE_ID))
+ )
assertFailsWith(TimeoutCancellationException::class) {
waitForWithTimeout(timeout = TIMEOUT_SHORT) {
val hasResurfaced =
- safetyCenterManager.getSafetyCenterDataWithPermission() != safetyCenterDataOk
+ safetyCenterManager.getSafetyCenterDataWithPermission() !=
+ expectedSafetyCenterData
hasResurfaced
}
}
@@ -3331,11 +4474,16 @@ class SafetyCenterManagerTest {
SafetyCenterCtsData.issueId(SINGLE_SOURCE_ID, CRITICAL_ISSUE_ID)
)
+ val expectedSafetyCenterData =
+ safetyCenterDataOkReviewCriticalEntry.withDismissedIssuesIfAtLeastU(
+ listOf(safetyCenterCtsData.safetyCenterIssueCritical(SINGLE_SOURCE_ID))
+ )
assertFailsWith(TimeoutCancellationException::class) {
waitForWithTimeout(timeout = TIMEOUT_SHORT) {
val hasResurfaced =
safetyCenterManager.getSafetyCenterDataWithPermission() !=
- safetyCenterDataOkReviewCriticalEntry
+ expectedSafetyCenterData
+
hasResurfaced
}
}
@@ -3367,14 +4515,16 @@ class SafetyCenterManagerTest {
val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
checkState(apiSafetyCenterData == safetyCenterDataDeviceRecommendationOneAlert)
val listener = safetyCenterCtsHelper.addListener()
-
safetyCenterManager.dismissSafetyCenterIssueWithPermission(
SafetyCenterCtsData.issueId(SINGLE_SOURCE_ID, RECOMMENDATION_ISSUE_ID)
)
val safetyCenterDataAfterDismissal = listener.receiveSafetyCenterData()
- assertThat(safetyCenterDataAfterDismissal)
- .isEqualTo(safetyCenterDataOkReviewRecommendationEntry)
+ val expectedSafetyCenterData =
+ safetyCenterDataOkReviewRecommendationEntry.withDismissedIssuesIfAtLeastU(
+ listOf(safetyCenterCtsData.safetyCenterIssueRecommendation(SINGLE_SOURCE_ID))
+ )
+ assertThat(safetyCenterDataAfterDismissal).isEqualTo(expectedSafetyCenterData)
waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
val hasResurfacedExactly =
safetyCenterManager.getSafetyCenterDataWithPermission() ==
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterMultiUsersTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterMultiUsersTest.kt
index ff635037f..ee34e7d4f 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterMultiUsersTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterMultiUsersTest.kt
@@ -42,11 +42,11 @@ import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_BAREBONE_
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_DISABLED_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_GROUP_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_HIDDEN_ID
-import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_IN_RIGID_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_IN_STATELESS_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_ALL_OPTIONAL_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_ALL_PROFILE_SOURCE_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_BAREBONE_ID
-import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_IN_RIGID_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_IN_STATELESS_ID
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_SOURCE_ALL_PROFILE_CONFIG
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_SOURCE_CONFIG
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_ALL_PROFILE_CONFIG
@@ -68,12 +68,12 @@ import android.safetycenter.cts.testing.UiTestHelper.waitAllTextNotDisplayed
import androidx.test.core.app.ApplicationProvider
import com.android.bedstead.harrier.BedsteadJUnit4
import com.android.bedstead.harrier.DeviceState
-import com.android.bedstead.harrier.OptionalBoolean.TRUE
import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile
import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser
import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile
import com.android.bedstead.harrier.annotations.Postsubmit
import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner
+import com.android.bedstead.nene.types.OptionalBoolean.TRUE
import com.android.compatibility.common.util.DisableAnimationRule
import com.android.compatibility.common.util.FreezeRotationRule
import com.android.safetycenter.resources.SafetyCenterResourcesContext
@@ -124,11 +124,17 @@ class SafetyCenterMultiUsersTest {
private val primaryProfileOnlyIssues =
listOf(
safetyCenterCtsData.safetyCenterIssueCritical(DYNAMIC_BAREBONE_ID),
- safetyCenterCtsData.safetyCenterIssueCritical(ISSUE_ONLY_BAREBONE_ID),
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ ISSUE_ONLY_BAREBONE_ID,
+ attributionTitle = null
+ ),
safetyCenterCtsData.safetyCenterIssueRecommendation(DYNAMIC_DISABLED_ID),
- safetyCenterCtsData.safetyCenterIssueRecommendation(ISSUE_ONLY_ALL_OPTIONAL_ID),
- safetyCenterCtsData.safetyCenterIssueInformation(DYNAMIC_IN_RIGID_ID),
- safetyCenterCtsData.safetyCenterIssueInformation(ISSUE_ONLY_IN_RIGID_ID)
+ safetyCenterCtsData.safetyCenterIssueRecommendation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ attributionTitle = null
+ ),
+ safetyCenterCtsData.safetyCenterIssueInformation(DYNAMIC_IN_STATELESS_ID),
+ safetyCenterCtsData.safetyCenterIssueInformation(ISSUE_ONLY_IN_STATELESS_ID)
)
private val dynamicBareboneDefault =
@@ -219,19 +225,19 @@ class SafetyCenterMultiUsersTest {
.setEnabled(false)
.build()
- private val rigidEntry =
+ private val staticEntry =
SafetyCenterStaticEntry.Builder("OK")
.setSummary("OK")
.setPendingIntent(safetySourceCtsData.testActivityRedirectPendingIntent)
.build()
- private val rigidEntryUpdated =
+ private val staticEntryUpdated =
SafetyCenterStaticEntry.Builder("Unspecified title")
.setSummary("Unspecified summary")
.setPendingIntent(safetySourceCtsData.testActivityRedirectPendingIntent)
.build()
- private val rigidEntryForWorkBuilder
+ private val staticEntryForWorkBuilder
get() =
SafetyCenterStaticEntry.Builder("Paste")
.setSummary("OK")
@@ -241,18 +247,18 @@ class SafetyCenterMultiUsersTest {
)
)
- private val rigidEntryForWork
- get() = rigidEntryForWorkBuilder.build()
+ private val staticEntryForWork
+ get() = staticEntryForWorkBuilder.build()
- private val rigidEntryForWorkPaused
+ private val staticEntryForWorkPaused
get() =
- rigidEntryForWorkBuilder
+ staticEntryForWorkBuilder
// TODO(b/233188021): This needs to use the Enterprise API to override the "work"
// keyword.
.setSummary(safetyCenterResourcesContext.getStringByName("work_profile_paused"))
.build()
- private val rigidEntryForWorkUpdated =
+ private val staticEntryForWorkUpdated =
SafetyCenterStaticEntry.Builder("Unspecified title for Work")
.setSummary("Unspecified summary")
.setPendingIntent(safetySourceCtsData.testActivityRedirectPendingIntent)
@@ -431,7 +437,7 @@ class SafetyCenterMultiUsersTest {
.build()
)
),
- listOf(SafetyCenterStaticEntryGroup("OK", listOf(rigidEntry, rigidEntry)))
+ listOf(SafetyCenterStaticEntryGroup("OK", listOf(staticEntry, staticEntry)))
)
assertThat(apiSafetyCenterData).isEqualTo(safetyCenterDataFromComplexConfig)
}
@@ -477,7 +483,7 @@ class SafetyCenterMultiUsersTest {
listOf(
SafetyCenterStaticEntryGroup(
"OK",
- listOf(rigidEntry, rigidEntryForWork, rigidEntry, rigidEntryForWork)
+ listOf(staticEntry, staticEntryForWork, staticEntry, staticEntryForWork)
)
)
)
@@ -523,7 +529,12 @@ class SafetyCenterMultiUsersTest {
listOf(
SafetyCenterStaticEntryGroup(
"OK",
- listOf(rigidEntryUpdated, rigidEntryForWork, rigidEntry, rigidEntryForWork)
+ listOf(
+ staticEntryUpdated,
+ staticEntryForWork,
+ staticEntry,
+ staticEntryForWork
+ )
)
)
)
@@ -546,9 +557,15 @@ class SafetyCenterMultiUsersTest {
safetyCenterCtsData.safetyCenterStatusCritical(11),
listOf(
safetyCenterCtsData.safetyCenterIssueCritical(DYNAMIC_BAREBONE_ID),
- safetyCenterCtsData.safetyCenterIssueCritical(ISSUE_ONLY_BAREBONE_ID),
+ safetyCenterCtsData.safetyCenterIssueCritical(
+ ISSUE_ONLY_BAREBONE_ID,
+ attributionTitle = null
+ ),
safetyCenterCtsData.safetyCenterIssueRecommendation(DYNAMIC_DISABLED_ID),
- safetyCenterCtsData.safetyCenterIssueRecommendation(ISSUE_ONLY_ALL_OPTIONAL_ID),
+ safetyCenterCtsData.safetyCenterIssueRecommendation(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ attributionTitle = null
+ ),
safetyCenterCtsData.safetyCenterIssueInformation(
DYNAMIC_DISABLED_ID,
managedUserId
@@ -559,16 +576,17 @@ class SafetyCenterMultiUsersTest {
),
safetyCenterCtsData.safetyCenterIssueInformation(
ISSUE_ONLY_ALL_OPTIONAL_ID,
- managedUserId
+ managedUserId,
+ attributionTitle = null
),
- safetyCenterCtsData.safetyCenterIssueInformation(DYNAMIC_IN_RIGID_ID),
+ safetyCenterCtsData.safetyCenterIssueInformation(DYNAMIC_IN_STATELESS_ID),
safetyCenterCtsData.safetyCenterIssueInformation(
- DYNAMIC_IN_RIGID_ID,
+ DYNAMIC_IN_STATELESS_ID,
managedUserId
),
- safetyCenterCtsData.safetyCenterIssueInformation(ISSUE_ONLY_IN_RIGID_ID),
+ safetyCenterCtsData.safetyCenterIssueInformation(ISSUE_ONLY_IN_STATELESS_ID),
safetyCenterCtsData.safetyCenterIssueInformation(
- ISSUE_ONLY_IN_RIGID_ID,
+ ISSUE_ONLY_IN_STATELESS_ID,
managedUserId
)
),
@@ -600,10 +618,10 @@ class SafetyCenterMultiUsersTest {
SafetyCenterStaticEntryGroup(
"OK",
listOf(
- rigidEntryUpdated,
- rigidEntryForWorkUpdated,
- rigidEntry,
- rigidEntryForWork
+ staticEntryUpdated,
+ staticEntryForWorkUpdated,
+ staticEntry,
+ staticEntryForWork
)
)
)
@@ -659,10 +677,10 @@ class SafetyCenterMultiUsersTest {
SafetyCenterStaticEntryGroup(
"OK",
listOf(
- rigidEntryUpdated,
- rigidEntryForWorkPaused,
- rigidEntry,
- rigidEntryForWorkPaused
+ staticEntryUpdated,
+ staticEntryForWorkPaused,
+ staticEntry,
+ staticEntryForWorkPaused
)
)
)
@@ -1049,9 +1067,12 @@ class SafetyCenterMultiUsersTest {
ISSUE_ONLY_ALL_OPTIONAL_ID,
SafetySourceCtsData.issuesOnly(safetySourceCtsData.recommendationGeneralIssue)
)
- safetyCenterCtsHelper.setData(DYNAMIC_IN_RIGID_ID, safetySourceCtsData.unspecifiedWithIssue)
safetyCenterCtsHelper.setData(
- ISSUE_ONLY_IN_RIGID_ID,
+ DYNAMIC_IN_STATELESS_ID,
+ safetySourceCtsData.unspecifiedWithIssue
+ )
+ safetyCenterCtsHelper.setData(
+ ISSUE_ONLY_IN_STATELESS_ID,
SafetySourceCtsData.issuesOnly(safetySourceCtsData.informationIssue)
)
}
@@ -1072,11 +1093,11 @@ class SafetyCenterMultiUsersTest {
SafetySourceCtsData.issuesOnly(safetySourceCtsData.informationIssue)
)
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
- DYNAMIC_IN_RIGID_ID,
+ DYNAMIC_IN_STATELESS_ID,
safetySourceCtsData.unspecifiedWithIssueForWork
)
managedSafetyCenterManager.setSafetySourceDataWithInteractAcrossUsersPermission(
- ISSUE_ONLY_IN_RIGID_ID,
+ ISSUE_ONLY_IN_STATELESS_ID,
SafetySourceCtsData.issuesOnly(safetySourceCtsData.informationIssue)
)
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterNotificationTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterNotificationTest.kt
index 49d0ecc3e..ba75c1a46 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterNotificationTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterNotificationTest.kt
@@ -16,8 +16,15 @@
package android.safetycenter.cts
+import android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE
+import android.app.Notification
import android.content.Context
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.safetycenter.SafetyCenterData
+import android.safetycenter.SafetyCenterIssue
import android.safetycenter.SafetyCenterManager
+import android.safetycenter.SafetyCenterStatus
+import android.safetycenter.SafetySourceIssue
import android.safetycenter.cts.testing.Coroutines.TIMEOUT_SHORT
import android.safetycenter.cts.testing.CtsNotificationListener
import android.safetycenter.cts.testing.NotificationCharacteristics
@@ -25,13 +32,21 @@ import android.safetycenter.cts.testing.SafetyCenterApisWithShellPermissions.cle
import android.safetycenter.cts.testing.SafetyCenterApisWithShellPermissions.dismissSafetyCenterIssueWithPermission
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_CONFIG
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.dynamicSafetySourceBuilder
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.singleSourceConfig
import android.safetycenter.cts.testing.SafetyCenterCtsData
import android.safetycenter.cts.testing.SafetyCenterCtsHelper
import android.safetycenter.cts.testing.SafetyCenterFlags
import android.safetycenter.cts.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
import android.safetycenter.cts.testing.SafetySourceCtsData
+import android.safetycenter.cts.testing.SafetySourceIntentHandler.Request
+import android.safetycenter.cts.testing.SafetySourceIntentHandler.Response
+import android.safetycenter.cts.testing.SafetySourceReceiver
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
+import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
import kotlinx.coroutines.TimeoutCancellationException
import org.junit.After
@@ -115,7 +130,54 @@ class SafetyCenterNotificationTest {
}
@Test
- fun setSafetySourceData_withNotificationAllowedSource_sendsNotification() {
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun setSafetySourceData_withNotificationBehaviorNever_noNotification() {
+ val data =
+ safetySourceCtsData
+ .defaultRecommendationDataBuilder()
+ .addIssue(
+ safetySourceCtsData
+ .defaultRecommendationIssueBuilder()
+ .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_NEVER)
+ .build()
+ )
+ .build()
+
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data)
+
+ CtsNotificationListener.waitForZeroNotifications()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun setSafetySourceData_withNotificationBehaviorImmediately_sendsNotification() {
+ val data =
+ safetySourceCtsData
+ .defaultRecommendationDataBuilder()
+ .addIssue(
+ safetySourceCtsData
+ .defaultRecommendationIssueBuilder("Notify immediately", "This is urgent!")
+ .setNotificationBehavior(
+ SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY
+ )
+ .build()
+ )
+ .build()
+
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data)
+
+ CtsNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ title = "Notify immediately",
+ text = "This is urgent!",
+ actions = listOf("See issue")
+ )
+ )
+ }
+
+ @Test
+ fun setSafetySourceData_withNotificationsAllowedForSourceByFlag_sendsNotification() {
+ SafetyCenterFlags.notificationsAllowedSources = setOf(SINGLE_SOURCE_ID)
val data = safetySourceCtsData.recommendationWithAccountIssue
safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data)
@@ -123,7 +185,142 @@ class SafetyCenterNotificationTest {
CtsNotificationListener.waitForSingleNotificationMatching(
NotificationCharacteristics(
title = "Recommendation issue title",
- text = "Recommendation issue summary"
+ text = "Recommendation issue summary",
+ actions = listOf("See issue")
+ )
+ )
+ }
+
+ @Test
+ fun setSafetySourceData_issueWithTwoActions_notificationWithTwoActions() {
+ val intent1 = safetySourceCtsData.testActivityRedirectPendingIntent(identifier = "1")
+ val intent2 = safetySourceCtsData.testActivityRedirectPendingIntent(identifier = "2")
+
+ val data =
+ safetySourceCtsData
+ .defaultRecommendationDataBuilder()
+ .addIssue(
+ safetySourceCtsData
+ .defaultRecommendationIssueBuilder()
+ .clearActions()
+ .addAction(
+ SafetySourceIssue.Action.Builder("action1", "Action 1", intent1).build()
+ )
+ .addAction(
+ SafetySourceIssue.Action.Builder("action2", "Action 2", intent2).build()
+ )
+ .build()
+ )
+ .build()
+
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data)
+
+ CtsNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ title = "Recommendation issue title",
+ text = "Recommendation issue summary",
+ actions = listOf("Action 1", "Action 2")
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun setSafetySourceData_withNotificationsAllowedForSourceByConfig_sendsNotification() {
+ safetyCenterCtsHelper.setConfig(
+ singleSourceConfig(
+ dynamicSafetySourceBuilder("MyNotifiableSource")
+ .setNotificationsAllowed(true)
+ .build()
+ )
+ )
+ val data = safetySourceCtsData.recommendationWithAccountIssue
+
+ safetyCenterCtsHelper.setData("MyNotifiableSource", data)
+
+ CtsNotificationListener.waitForSingleNotification()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun setSafetySourceData_withCustomNotification_usesCustomValues() {
+ val intent1 = safetySourceCtsData.testActivityRedirectPendingIntent(identifier = "1")
+ val intent2 = safetySourceCtsData.testActivityRedirectPendingIntent(identifier = "2")
+
+ val notification =
+ SafetySourceIssue.Notification.Builder("Custom title", "Custom text")
+ .addAction(
+ SafetySourceIssue.Action.Builder("action1", "Custom action 1", intent1).build()
+ )
+ .addAction(
+ SafetySourceIssue.Action.Builder("action2", "Custom action 2", intent1).build()
+ )
+ .build()
+
+ val data =
+ safetySourceCtsData
+ .defaultRecommendationDataBuilder()
+ .addIssue(
+ safetySourceCtsData
+ .defaultRecommendationIssueBuilder("Default title", "Default text")
+ .addAction(
+ SafetySourceIssue.Action.Builder(
+ "default_action",
+ "Default action",
+ safetySourceCtsData.testActivityRedirectPendingIntent
+ )
+ .build()
+ )
+ .setCustomNotification(notification)
+ .build()
+ )
+ .build()
+
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data)
+
+ CtsNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ title = "Custom title",
+ text = "Custom text",
+ actions = listOf("Custom action 1", "Custom action 2")
+ )
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun setSafetySourceData_withEmptyCustomActions_notificationHasNoActions() {
+ val notification =
+ SafetySourceIssue.Notification.Builder("Custom title", "Custom text")
+ .clearActions()
+ .build()
+
+ val data =
+ safetySourceCtsData
+ .defaultRecommendationDataBuilder()
+ .addIssue(
+ safetySourceCtsData
+ .defaultRecommendationIssueBuilder("Default title", "Default text")
+ .addAction(
+ SafetySourceIssue.Action.Builder(
+ "default_action",
+ "Default action",
+ safetySourceCtsData.testActivityRedirectPendingIntent
+ )
+ .build()
+ )
+ .setCustomNotification(notification)
+ .build()
+ )
+ .build()
+
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data)
+
+ CtsNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ title = "Custom title",
+ text = "Custom text",
+ actions = emptyList()
)
)
}
@@ -140,9 +337,14 @@ class SafetyCenterNotificationTest {
safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data1)
- CtsNotificationListener.waitForSingleNotificationMatching(
- NotificationCharacteristics(title = "Initial", text = "Blah")
- )
+ val initialNotification =
+ CtsNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ title = "Initial",
+ text = "Blah",
+ actions = listOf("See issue")
+ )
+ )
val data2 =
safetySourceCtsData
@@ -150,15 +352,31 @@ class SafetyCenterNotificationTest {
.addIssue(
safetySourceCtsData
.defaultRecommendationIssueBuilder("Revised", "Different")
+ .addAction(
+ SafetySourceIssue.Action.Builder(
+ "new_action",
+ "New action",
+ safetySourceCtsData.testActivityRedirectPendingIntent(
+ identifier = "new_action"
+ )
+ )
+ .build()
+ )
.build()
)
.build()
safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data2)
- CtsNotificationListener.waitForSingleNotificationMatching(
- NotificationCharacteristics(title = "Revised", text = "Different")
- )
+ val revisedNotification =
+ CtsNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ title = "Revised",
+ text = "Different",
+ actions = listOf("See issue", "New action")
+ )
+ )
+ assertThat(initialNotification.key).isEqualTo(revisedNotification.key)
}
@Test
@@ -190,6 +408,10 @@ class SafetyCenterNotificationTest {
@Test
fun setSafetySourceData_withDismissedIssueId_doesNotNotify() {
+ // We use two different issues/data here to ensure that the reason the notification is not
+ // posted the second time is specifically because of the dismissal. Notifications are not
+ // re-posted/updated for unchanged issues but that functionality is different and tested
+ // separately.
val data1 =
safetySourceCtsData
.defaultRecommendationDataBuilder()
@@ -211,7 +433,11 @@ class SafetyCenterNotificationTest {
val notification =
CtsNotificationListener.waitForSingleNotificationMatching(
- NotificationCharacteristics(title = "Initial", text = "Blah")
+ NotificationCharacteristics(
+ title = "Initial",
+ text = "Blah",
+ actions = listOf("See issue")
+ )
)
CtsNotificationListener.cancelAndWait(notification.key)
@@ -246,11 +472,12 @@ class SafetyCenterNotificationTest {
SINGLE_SOURCE_ID,
safetySourceCtsData.criticalWithResolvingGeneralIssue
)
+ // Add the listener after setting the initial data so that we don't need to consume/receive
+ // an update for that
+ val listener = safetyCenterCtsHelper.addListener()
val notification = CtsNotificationListener.waitForSingleNotification()
- val listener = safetyCenterCtsHelper.addListener()
-
CtsNotificationListener.cancelAndWait(notification.key)
assertFailsWith(TimeoutCancellationException::class) {
@@ -270,4 +497,72 @@ class SafetyCenterNotificationTest {
CtsNotificationListener.waitForZeroNotifications()
}
+
+ @Test
+ fun sendActionPendingIntent_successful_updatesListenerRemovesNotification() {
+ // Here we cause a notification with an action to be posted and prepare the fake receiver
+ // to resolve that action successfully.
+ safetyCenterCtsHelper.setData(
+ SINGLE_SOURCE_ID,
+ safetySourceCtsData.criticalWithResolvingGeneralIssue
+ )
+ val notification = CtsNotificationListener.waitForSingleNotification()
+ val action = notification.notification.actions.firstOrNull()
+ checkNotNull(action) { "Notification action unexpectedly null" }
+ SafetySourceReceiver.setResponse(
+ Request.ResolveAction(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceCtsData.information)
+ )
+ val listener = safetyCenterCtsHelper.addListener()
+
+ sendActionPendingIntentAndWaitWithPermission(action)
+
+ val listenerData1 = listener.receiveSafetyCenterData()
+ assertThat(listenerData1.inFlightActions).hasSize(1)
+ val listenerData2 = listener.receiveSafetyCenterData()
+ assertThat(listenerData2.issues).isEmpty()
+ assertThat(listenerData2.status.severityLevel)
+ .isEqualTo(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+ CtsNotificationListener.waitForZeroNotifications()
+ }
+
+ @Test
+ fun sendActionPendingIntent_error_updatesListenerDoesNotRemoveNotification() {
+ // Here we cause a notification with an action to be posted and prepare the fake receiver
+ // to resolve that action successfully.
+ safetyCenterCtsHelper.setData(
+ SINGLE_SOURCE_ID,
+ safetySourceCtsData.criticalWithResolvingGeneralIssue
+ )
+ val notification = CtsNotificationListener.waitForSingleNotification()
+ val action = notification.notification.actions.firstOrNull()
+ checkNotNull(action) { "Notification action unexpectedly null" }
+ SafetySourceReceiver.setResponse(Request.ResolveAction(SINGLE_SOURCE_ID), Response.Error)
+ val listener = safetyCenterCtsHelper.addListener()
+
+ sendActionPendingIntentAndWaitWithPermission(action)
+
+ val listenerData1 = listener.receiveSafetyCenterData()
+ assertThat(listenerData1.inFlightActions).hasSize(1)
+ val listenerData2 = listener.receiveSafetyCenterData()
+ assertThat(listenerData2.issues).hasSize(1)
+ assertThat(listenerData2.inFlightActions).isEmpty()
+ assertThat(listenerData2.status.severityLevel)
+ .isEqualTo(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+ CtsNotificationListener.waitForSingleNotification()
+ }
+
+ companion object {
+ private val SafetyCenterData.inFlightActions: List<SafetyCenterIssue.Action>
+ get() = issues.flatMap { it.actions }.filter { it.isInFlight }
+
+ private fun sendActionPendingIntentAndWaitWithPermission(action: Notification.Action) {
+ callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
+ action.actionIntent.send()
+ // Sending the action's PendingIntent above is asynchronous and we need to wait for
+ // it to be received by the fake receiver below.
+ SafetySourceReceiver.receiveResolveAction()
+ }
+ }
+ }
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryGroupTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryGroupTest.kt
index 1174fbbb1..71be3d5eb 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryGroupTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryGroupTest.kt
@@ -99,7 +99,9 @@ class SafetyCenterStaticEntryGroupTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterStaticEntryGroup.CREATOR
+ )
.addEqualityGroup(
staticEntryGroup,
SafetyCenterStaticEntryGroup("a title", listOf(staticEntry1, staticEntry2))
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryTest.kt
index 738d96284..045a50f75 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStaticEntryTest.kt
@@ -99,14 +99,16 @@ class SafetyCenterStaticEntryTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterStaticEntry.CREATOR,
+ createCopy = { SafetyCenterStaticEntry.Builder(it).build() }
+ )
.addEqualityGroup(
staticEntry1,
SafetyCenterStaticEntry.Builder("a title")
.setSummary("a summary")
.setPendingIntent(pendingIntent1)
- .build(),
- SafetyCenterStaticEntry.Builder(staticEntry1).build()
+ .build()
)
.addEqualityGroup(staticEntry2)
.addEqualityGroup(staticEntryMinimal, SafetyCenterStaticEntry.Builder("").build())
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt
index 3060e0500..4df4e5d35 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt
@@ -154,7 +154,10 @@ class SafetyCenterStatusTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterStatus.CREATOR,
+ createCopy = { SafetyCenterStatus.Builder(it).build() }
+ )
.addEqualityGroup(
baseStatus,
SafetyCenterStatus.Builder("This is my title", "This is my summary")
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt
index 8b06201ad..3dec4f509 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyEventTest.kt
@@ -16,6 +16,7 @@
package android.safetycenter.cts
+import android.os.Build
import android.safetycenter.SafetyEvent
import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED
import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED
@@ -25,6 +26,7 @@ import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCE
import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
+import androidx.test.filters.SdkSuppress
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
@@ -184,7 +186,25 @@ class SafetyEventTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ newTiramisuEqualsHashCodeToStringTester().test()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
+ newTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder = { SafetyEvent.Builder(it).build() }
+ )
+ .test()
+ }
+
+ private fun newTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder: ((SafetyEvent) -> SafetyEvent)? = null
+ ) =
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyEvent.CREATOR,
+ createCopy = createCopyFromBuilder
+ )
.addEqualityGroup(SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build())
.addEqualityGroup(
SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED)
@@ -245,8 +265,6 @@ class SafetyEventTest {
.setSafetySourceIssueActionId(OTHER_SAFETY_SOURCE_ISSUE_ACTION_ID)
.build()
)
- .test()
- }
companion object {
private const val REFRESH_BROADCAST_ID = "refresh_broadcast_id"
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
index c67d1f485..d7208c098 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
@@ -20,6 +20,8 @@ import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.Context
import android.content.Intent
+import android.os.Build
+import android.os.Bundle
import android.safetycenter.SafetySourceData
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_INFORMATION
@@ -27,9 +29,12 @@ import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED
import android.safetycenter.SafetySourceIssue
import android.safetycenter.SafetySourceStatus
+import androidx.core.os.bundleOf
import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.core.os.Parcelables.forceParcel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
+import androidx.test.filters.SdkSuppress
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
@@ -91,6 +96,41 @@ class SafetySourceDataTest {
}
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getExtras_withDefaultBuilder_returnsEmptyBundle() {
+ val safetySourceData =
+ SafetySourceData.Builder().setStatus(createStatus(SEVERITY_LEVEL_INFORMATION)).build()
+
+ assertThat(safetySourceData.extras.keySet()).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getExtras_whenSetExplicitly_returnsExtras() {
+ val safetySourceData =
+ SafetySourceData.Builder()
+ .setStatus(createStatus(SEVERITY_LEVEL_INFORMATION))
+ .setExtras(bundleOf(EXTRA_KEY to EXTRA_VALUE))
+ .build()
+
+ assertThat(safetySourceData.extras.keySet()).containsExactly(EXTRA_KEY)
+ assertThat(safetySourceData.extras.getString(EXTRA_KEY, "")).isEqualTo(EXTRA_VALUE)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getExtras_whenCleared_returnsEmptyBundle() {
+ val safetySourceData =
+ SafetySourceData.Builder()
+ .setStatus(createStatus(SEVERITY_LEVEL_INFORMATION))
+ .setExtras(bundleOf(EXTRA_KEY to EXTRA_VALUE))
+ .clearExtras()
+ .build()
+
+ assertThat(safetySourceData.extras.keySet()).isEmpty()
+ }
+
+ @Test
fun builder_addIssue_doesNotMutatePreviouslyBuiltInstance() {
val firstIssue = createIssue(SEVERITY_LEVEL_INFORMATION, 1)
val secondIssue = createIssue(SEVERITY_LEVEL_INFORMATION, 2)
@@ -285,12 +325,29 @@ class SafetySourceDataTest {
}
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun parcelRoundTrip_withExtras_recreatesEqual() {
+ val safetySourceData =
+ SafetySourceData.Builder()
+ .setStatus(createStatus(SEVERITY_LEVEL_RECOMMENDATION))
+ .addIssue(createIssue(SEVERITY_LEVEL_RECOMMENDATION, 1))
+ .addIssue(createIssue(SEVERITY_LEVEL_INFORMATION, 2))
+ .setExtras(bundleOf(EXTRA_KEY to EXTRA_VALUE))
+ .build()
+ val recreatedSafetySourceData = forceParcel(safetySourceData, SafetySourceData.CREATOR)
+
+ assertThat(recreatedSafetySourceData).isEqualTo(safetySourceData)
+ assertThat(recreatedSafetySourceData.extras.keySet()).containsExactly(EXTRA_KEY)
+ assertThat(recreatedSafetySourceData.extras.getString(EXTRA_KEY, "")).isEqualTo(EXTRA_VALUE)
+ }
+
+ @Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
val firstStatus = createStatus(SEVERITY_LEVEL_INFORMATION, 1)
val secondStatus = createStatus(SEVERITY_LEVEL_INFORMATION, 2)
val firstIssue = createIssue(SEVERITY_LEVEL_INFORMATION, 1)
val secondIssue = createIssue(SEVERITY_LEVEL_INFORMATION, 2)
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(parcelableCreator = SafetySourceData.CREATOR)
.addEqualityGroup(
SafetySourceData.Builder().setStatus(firstStatus).build(),
SafetySourceData.Builder().setStatus(firstStatus).build()
@@ -326,6 +383,101 @@ class SafetySourceDataTest {
.test()
}
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun equalsHashCode_atLeastU_usingEqualsHashCodeToStringTester() {
+ val firstStatus = createStatus(SEVERITY_LEVEL_INFORMATION, 1)
+ val secondStatus = createStatus(SEVERITY_LEVEL_INFORMATION, 2)
+ val firstIssue = createIssue(SEVERITY_LEVEL_INFORMATION, 1)
+ val secondIssue = createIssue(SEVERITY_LEVEL_INFORMATION, 2)
+ val filledExtras = bundleOf(EXTRA_KEY to EXTRA_VALUE)
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetySourceData.CREATOR,
+ ignoreToString = true,
+ createCopy = { SafetySourceData.Builder(it).build() }
+ )
+ .addEqualityGroup(
+ SafetySourceData.Builder().setStatus(firstStatus).build(),
+ SafetySourceData.Builder().setStatus(firstStatus).setExtras(filledExtras).build()
+ )
+ .addEqualityGroup(
+ SafetySourceData.Builder().addIssue(firstIssue).addIssue(secondIssue).build(),
+ SafetySourceData.Builder()
+ .addIssue(firstIssue)
+ .addIssue(secondIssue)
+ .setExtras(filledExtras)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceData.Builder()
+ .setStatus(firstStatus)
+ .addIssue(firstIssue)
+ .addIssue(secondIssue)
+ .build(),
+ SafetySourceData.Builder()
+ .setStatus(firstStatus)
+ .addIssue(firstIssue)
+ .addIssue(secondIssue)
+ .setExtras(filledExtras)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceData.Builder().setStatus(secondStatus).build(),
+ SafetySourceData.Builder().setStatus(secondStatus).setExtras(filledExtras).build()
+ )
+ .addEqualityGroup(
+ SafetySourceData.Builder().addIssue(secondIssue).addIssue(firstIssue).build(),
+ SafetySourceData.Builder()
+ .addIssue(secondIssue)
+ .addIssue(firstIssue)
+ .setExtras(filledExtras)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceData.Builder().addIssue(firstIssue).build(),
+ SafetySourceData.Builder().addIssue(firstIssue).setExtras(filledExtras).build()
+ )
+ .addEqualityGroup(
+ SafetySourceData.Builder()
+ .setStatus(secondStatus)
+ .addIssue(firstIssue)
+ .addIssue(secondIssue)
+ .build(),
+ SafetySourceData.Builder()
+ .setStatus(secondStatus)
+ .addIssue(firstIssue)
+ .addIssue(secondIssue)
+ .setExtras(filledExtras)
+ .build()
+ )
+ .test()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun toString_withExtras_containsHasExtras() {
+ val safetySourceDataWithExtras =
+ SafetySourceData.Builder()
+ .setStatus(createStatus(SEVERITY_LEVEL_INFORMATION))
+ .setExtras(bundleOf(EXTRA_KEY to EXTRA_VALUE))
+ .build()
+
+ val stringRepresentation = safetySourceDataWithExtras.toString()
+
+ assertThat(stringRepresentation).contains("(has extras)")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun toString_withoutExtras_doesNotContainHasExtras() {
+ val safetySourceDataWithoutExtras =
+ SafetySourceData.Builder().setStatus(createStatus(SEVERITY_LEVEL_INFORMATION)).build()
+
+ val stringRepresentation = safetySourceDataWithoutExtras.toString()
+
+ assertThat(stringRepresentation).doesNotContain("(has extras)")
+ }
+
private fun createStatus(severityLevel: Int, id: Int = 0) =
SafetySourceStatus.Builder("Status title $id", "Status summary $id", severityLevel)
.setPendingIntent(
@@ -360,4 +512,12 @@ class SafetySourceDataTest {
.build()
)
.build()
+
+ private companion object {
+ /** Key of extra data in [Bundle]. */
+ const val EXTRA_KEY = "extra_key"
+
+ /** Value of extra data in [Bundle]. */
+ const val EXTRA_VALUE = "extra_value"
+ }
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt
index 06d649452..336462491 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceErrorDetailsTest.kt
@@ -44,7 +44,9 @@ class SafetySourceErrorDetailsTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetySourceErrorDetails.CREATOR
+ )
.addEqualityGroup(
SafetySourceErrorDetails(SAFETY_EVENT),
SafetySourceErrorDetails(
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
index 7ead37f8b..cf9f37245 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
@@ -20,21 +20,30 @@ import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.Context
import android.content.Intent
+import android.os.Build
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_INFORMATION
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED
import android.safetycenter.SafetySourceIssue
import android.safetycenter.SafetySourceIssue.Action
+import android.safetycenter.SafetySourceIssue.Action.ConfirmationDialogDetails
import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT
import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_DEVICE
import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_GENERAL
+import android.safetycenter.SafetySourceIssue.Notification
import android.safetycenter.cts.testing.Generic
+import androidx.annotation.RequiresApi
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
+import androidx.test.filters.SdkSuppress
+import com.android.modules.utils.build.SdkLevel
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
+import org.junit.Assume.assumeFalse
import org.junit.Test
import org.junit.runner.RunWith
@@ -77,7 +86,9 @@ class SafetySourceIssueTest {
@Test
fun action_willResolve_whenSetExplicitly_returnsWillResolve() {
val action =
- Action.Builder("action_id", "Action label", pendingIntent1).setWillResolve(true).build()
+ Action.Builder("action_id", "Action label", pendingIntentService)
+ .setWillResolve(true)
+ .build()
assertThat(action.willResolve()).isTrue()
}
@@ -107,6 +118,51 @@ class SafetySourceIssueTest {
}
@Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun action_getConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val action = Action.Builder("action_id", "Action label", pendingIntent1).build()
+
+ assertFailsWith(UnsupportedOperationException::class) { action.confirmationDialogDetails }
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun action_setConfirmationDialogDetails_withVersionLessThanU_throwsUnsupportedOperation() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ assertFailsWith(UnsupportedOperationException::class) {
+ Action.Builder("action_id", "Action label", pendingIntent1)
+ .setConfirmationDialogDetails(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun action_getConfirmationDialogDetails_withDefaultBuilder_returnsNull() {
+ val action = Action.Builder("action_id", "Action label", pendingIntent1).build()
+
+ assertThat(action.confirmationDialogDetails).isNull()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun action_getConfirmationDialogDetails_whenSetExplicitly_returnsConfirmation() {
+ val action =
+ Action.Builder("action_id", "Action label", pendingIntent1)
+ .setConfirmationDialogDetails(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ .build()
+
+ assertThat(action.confirmationDialogDetails)
+ .isEqualTo(ConfirmationDialogDetails("Title", "Text", "Accept", "Deny"))
+ }
+
+ @Test
fun action_build_withNullId_throwsNullPointerException() {
assertFailsWith(NullPointerException::class) {
Action.Builder(Generic.asNull(), "Action label", pendingIntent1)
@@ -128,6 +184,14 @@ class SafetySourceIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun action_build_withActivityPendingIntentAndWillResolve_throwsIllegalArgumentException() {
+ assertFailsWith(IllegalArgumentException::class) {
+ Action.Builder("action_id", "Action label", pendingIntent1).setWillResolve(true).build()
+ }
+ }
+
+ @Test
fun action_describeContents_returns0() {
val action = Action.Builder("action_id", "Action label", pendingIntent1).build()
@@ -145,29 +209,62 @@ class SafetySourceIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun action_parcelRoundTrip_recreatesEqual_atLeastAndroidU() {
+ val action =
+ Action.Builder("action_id", "Action label", pendingIntent1)
+ .setConfirmationDialogDetails(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ .build()
+
+ assertThat(action).recreatesEqual(Action.CREATOR)
+ }
+
+ @Test
fun action_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ actionNewTiramisuEqualsHashCodeToStringTester().test()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun action_equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ actionNewTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder = { Action.Builder(it).build() }
+ )
.addEqualityGroup(
- Action.Builder("action_id", "Action label", pendingIntent1).build(),
- Action.Builder("action_id", "Action label", pendingIntent1).build(),
Action.Builder("action_id", "Action label", pendingIntent1)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
+ .build(),
+ Action.Builder("action_id", "Action label", pendingIntent1)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
+ .build(),
+ Action.Builder("action_id", "Action label", pendingIntent1)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.setWillResolve(false)
.build()
)
.addEqualityGroup(
Action.Builder("action_id", "Action label", pendingIntent1)
.setSuccessMessage("Action successfully completed")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
- Action.Builder("action_id", "Other action label", pendingIntent1).build()
+ Action.Builder("action_id", "Other action label", pendingIntent1)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
+ .build()
)
.addEqualityGroup(
- Action.Builder("other_action_id", "Action label", pendingIntent1).build()
+ Action.Builder("other_action_id", "Action label", pendingIntent1)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
+ .build()
)
.addEqualityGroup(
- Action.Builder("action_id", "Action label", pendingIntent1)
+ Action.Builder("action_id", "Action label", pendingIntentService)
.setWillResolve(true)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
@@ -181,17 +278,281 @@ class SafetySourceIssueTest {
FLAG_IMMUTABLE
)
)
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.addEqualityGroup(
Action.Builder("action_id", "Action label", pendingIntent1)
.setSuccessMessage("Other action successfully completed")
+ .setConfirmationDialogDetails(confirmationDialogDetails)
.build()
)
.test()
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_getTitle_returnsTitle() {
+ val notification = Notification.Builder("Notification title", "Notification text").build()
+
+ assertThat(notification.title).isEqualTo("Notification title")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_getText_returnsText() {
+ val notification = Notification.Builder("Notification title", "Notification text").build()
+
+ assertThat(notification.text).isEqualTo("Notification text")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_getActions_withDefaultBuilder_returnsEmptyList() {
+ val notification = Notification.Builder("", "").build()
+
+ assertThat(notification.actions).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_getActions_returnsActions() {
+ val notification =
+ Notification.Builder("", "").addAction(action1).addAction(action2).build()
+
+ assertThat(notification.actions).containsExactly(action1, action2).inOrder()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_getActions_mutationsAreNotAllowed() {
+ val notification =
+ Notification.Builder("", "").addAction(action1).addAction(action2).build()
+
+ assertFailsWith(UnsupportedOperationException::class) { notification.actions.add(action3) }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_describeContents_returns0() {
+ val notification =
+ Notification.Builder("Notification title", "Notification text")
+ .addAction(action1)
+ .addAction(action2)
+ .build()
+
+ assertThat(notification.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_parcelRoundTrip_recreatesEqual() {
+ val notification =
+ Notification.Builder("Notification title", "Notification text")
+ .addAction(action1)
+ .addAction(action2)
+ .build()
+
+ assertThat(notification).recreatesEqual(Notification.CREATOR)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_builder_withNullTitle_throwsNullPointerException() {
+ assertFailsWith(NullPointerException::class) {
+ Notification.Builder(Generic.asNull(), "Notification text")
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_builder_withNullText_throwsNullPointerException() {
+ assertFailsWith(NullPointerException::class) {
+ Notification.Builder("Notification title", Generic.asNull())
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_builder_addAction_doesNotMutatePreviouslyBuiltInstance() {
+ val notificationBuilder = Notification.Builder("", "").addAction(action1)
+ val actions = notificationBuilder.build().actions
+
+ notificationBuilder.addAction(action2)
+
+ assertThat(actions).containsExactly(action1)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_builder_addAction_withNull_throwsIllegalArgumentException() {
+ assertFailsWith(NullPointerException::class) {
+ Notification.Builder("", "").addAction(Generic.asNull())
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_builder_addActions_keepsPreviouslyAddedActions() {
+ val notificationBuilder = Notification.Builder("", "").addAction(action1)
+
+ notificationBuilder.addActions(listOf(action2))
+
+ assertThat(notificationBuilder.build().actions).containsExactly(action1, action2).inOrder()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_builder_addActions_doesNotMutatePreviouslyBuiltInstance() {
+ val notificationBuilder = Notification.Builder("", "").addActions(listOf(action1))
+ val actions = notificationBuilder.build().actions
+
+ notificationBuilder.addActions(listOf(action2, action3))
+
+ assertThat(actions).containsExactly(action1)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_builder_addActions_withNull_throwsIllegalArgumentException() {
+ assertFailsWith(NullPointerException::class) {
+ Notification.Builder("", "").addActions(Generic.asNull())
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_builder_clearActions_removesAllActions() {
+ val notification =
+ Notification.Builder("", "")
+ .addAction(action1)
+ .addAction(action2)
+ .clearActions()
+ .addAction(action3)
+ .build()
+
+ assertThat(notification.actions).containsExactly(action3)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_build_withDuplicateActionIds_throwsIllegalArgumentException() {
+ val notificationBuilder =
+ Notification.Builder("Notification title", "Notification text")
+ .addAction(action1)
+ .addAction(action1)
+
+ val exception =
+ assertFailsWith(IllegalArgumentException::class) { notificationBuilder.build() }
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("Custom notification cannot have duplicate action ids")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_build_withMoreThanTwoActions_throwsIllegalArgumentException() {
+ val notificationBuilder =
+ Notification.Builder("Notification title", "Notification text")
+ .addAction(action1)
+ .addAction(action2)
+ .addAction(action3)
+
+ val exception =
+ assertFailsWith(IllegalArgumentException::class) { notificationBuilder.build() }
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("Custom notification must not contain more than 2 actions")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun notification_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = Notification.CREATOR,
+ createCopy = { Notification.Builder(it).build() }
+ )
+ .addEqualityGroup(
+ Notification.Builder("Title", "Text").build(),
+ Notification.Builder("Title", "Text").build(),
+ )
+ .addEqualityGroup(Notification.Builder("Other title", "Text").build())
+ .addEqualityGroup(Notification.Builder("Title", "Other text").build())
+ .addEqualityGroup(Notification.Builder("Title", "Text").addAction(action1).build())
+ .addEqualityGroup(Notification.Builder("Title", "Text").addAction(action2).build())
+ .addEqualityGroup(
+ Notification.Builder("Title", "Text").addAction(action1).addAction(action2).build(),
+ Notification.Builder("Title", "Text").addActions(listOf(action1, action2)).build()
+ )
+ .test()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_getTitle_returnsTitle() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails.title).isEqualTo("Title")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_getText_returnsText() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails.text).isEqualTo("Text")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_getAcceptButtonText_returnsAcceptButtonText() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails.acceptButtonText).isEqualTo("Accept")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_getDenyButtonText_returnsDenyButtonText() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails.denyButtonText).isEqualTo("Deny")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_describeContents_returns0() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails.describeContents()).isEqualTo(0)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_parcelRoundTrip_recreatesEqual() {
+ val confirmationDialogDetails = ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+
+ assertThat(confirmationDialogDetails).recreatesEqual(ConfirmationDialogDetails.CREATOR)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun actionConfirmation_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = ConfirmationDialogDetails.CREATOR
+ )
+ .addEqualityGroup(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny"),
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ .addEqualityGroup(ConfirmationDialogDetails("Other title", "Text", "Accept", "Deny"))
+ .addEqualityGroup(ConfirmationDialogDetails("Title", "Other text", "Accept", "Deny"))
+ .addEqualityGroup(ConfirmationDialogDetails("Title", "Text", "Other accept", "Deny"))
+ .addEqualityGroup(ConfirmationDialogDetails("Title", "Text", "Accept", "Other deny"))
+ .test()
+ }
+
+ @Test
fun getId_returnsId() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -273,6 +634,79 @@ class SafetySourceIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getAttributionTitle_withNullAttributionTitle_returnsNull() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.attributionTitle).isNull()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getAttributionTitle_returnsAttributionTitle() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .setAttributionTitle("attribution title")
+ .build()
+
+ assertThat(safetySourceIssue.attributionTitle).isEqualTo("attribution title")
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun getAttributionTitle_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .build()
+
+ assertFailsWith(UnsupportedOperationException::class) { safetySourceIssue.attributionTitle }
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun setAttributionTitle_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val safetySourceIssueBuilder =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+
+ assertFailsWith(UnsupportedOperationException::class) {
+ safetySourceIssueBuilder.setAttributionTitle("title")
+ }
+ }
+
+ @Test
fun getSeverityLevel_returnsSeverityLevel() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -322,6 +756,25 @@ class SafetySourceIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getIssueCategory_whenSetExplicitlyWithUValueOnU_returnsIssueCategory() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS)
+ .build()
+
+ assertThat(safetySourceIssue.issueCategory)
+ .isEqualTo(SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS)
+ }
+
+ @Test
fun getActions_returnsActions() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -427,6 +880,79 @@ class SafetySourceIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getDeduplicationId_withDefaultBuilder_returnsNull() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.deduplicationId).isNull()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getDeduplicationId_whenSetExplicitly_returnsDeduplicationId() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .setDeduplicationId("deduplication_id")
+ .build()
+
+ assertThat(safetySourceIssue.deduplicationId).isEqualTo("deduplication_id")
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun getDeduplicationId_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .build()
+
+ assertFailsWith(UnsupportedOperationException::class) { safetySourceIssue.deduplicationId }
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun setDeduplicationId_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val safetySourceIssueBuilder =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+
+ assertFailsWith(UnsupportedOperationException::class) {
+ safetySourceIssueBuilder.setDeduplicationId("id")
+ }
+ }
+
+ @Test
fun getIssueTypeId_returnsIssueTypeId() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -443,6 +969,284 @@ class SafetySourceIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getCustomNotification_withDefaultBuilder_returnsNull() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.customNotification).isNull()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getCustomNotification_whenSetExplicitly_returnsCustomNotification() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .setCustomNotification(
+ Notification.Builder("Notification title", "Notification text")
+ .addAction(action2)
+ .build()
+ )
+ .build()
+
+ assertThat(safetySourceIssue.customNotification)
+ .isEqualTo(
+ Notification.Builder("Notification title", "Notification text")
+ .addAction(action2)
+ .build()
+ )
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun getCustomNotification_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .build()
+
+ assertFailsWith(UnsupportedOperationException::class) {
+ safetySourceIssue.customNotification
+ }
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun setCustomNotification_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val safetySourceIssueBuilder =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+
+ assertFailsWith(UnsupportedOperationException::class) {
+ safetySourceIssueBuilder.setCustomNotification(null)
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getNotificationBehavior_withDefaultBuilder_returnsUnspecified() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.notificationBehavior)
+ .isEqualTo(SafetySourceIssue.NOTIFICATION_BEHAVIOR_UNSPECIFIED)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getNotificationBehavior_whenSetExplicitly_returnsSpecifiedBehavior() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_NEVER)
+ .build()
+
+ assertThat(safetySourceIssue.notificationBehavior)
+ .isEqualTo(SafetySourceIssue.NOTIFICATION_BEHAVIOR_NEVER)
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun getNotificationBehavior_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .build()
+
+ assertFailsWith(UnsupportedOperationException::class) {
+ safetySourceIssue.notificationBehavior
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun setNotificationBehavior_withInvalidNotificationBehavior_throwsIllegalArgumentException() {
+ val builder =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+
+ val exception =
+ assertFailsWith(IllegalArgumentException::class) { builder.setNotificationBehavior(-1) }
+
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("Unexpected NotificationBehavior for SafetySourceIssue: -1")
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun setNotificationBehavior_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val safetySourceIssueBuilder =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+
+ assertFailsWith(UnsupportedOperationException::class) {
+ safetySourceIssueBuilder.setNotificationBehavior(0)
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getIssueActionability_withDefaultBuilder_returnsManual() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .build()
+
+ assertThat(safetySourceIssue.issueActionability)
+ .isEqualTo(SafetySourceIssue.ISSUE_ACTIONABILITY_MANUAL)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getIssueActionability_whenSetExplicitly_returnsValueSet() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
+ .build()
+
+ assertThat(safetySourceIssue.issueActionability)
+ .isEqualTo(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun getIssueActionability_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+ .build()
+
+ assertFailsWith(UnsupportedOperationException::class) {
+ safetySourceIssue.issueActionability
+ }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun setIssueActionability_withInvalidIssueActionability_throwsIllegalArgumentException() {
+ val builder =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+
+ val exception =
+ assertFailsWith(IllegalArgumentException::class) { builder.setIssueActionability(-1) }
+
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("Unexpected IssueActionability for SafetySourceIssue: -1")
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun setIssueActionability_withVersionLessThanU_throwsUnsupportedOperationException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val safetySourceIssueBuilder =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+
+ assertFailsWith(UnsupportedOperationException::class) {
+ safetySourceIssueBuilder.setIssueActionability(0)
+ }
+ }
+
+ @Test
fun build_withNullId_throwsNullPointerException() {
assertFailsWith(NullPointerException::class) {
SafetySourceIssue.Builder(
@@ -538,14 +1342,39 @@ class SafetySourceIssueTest {
SEVERITY_LEVEL_INFORMATION,
"issue_type_id"
)
+
val exception =
assertFailsWith(IllegalArgumentException::class) { builder.setIssueCategory(-1) }
+
assertThat(exception)
.hasMessageThat()
.isEqualTo("Unexpected IssueCategory for SafetySourceIssue: -1")
}
@Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun build_withUIssueCategoryValueOnT_throwsIllegalArgumentException() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(Build.VERSION.CODENAME == "UpsideDownCake")
+ val builder =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .addAction(action1)
+
+ val exception =
+ assertFailsWith(IllegalArgumentException::class) { builder.setIssueCategory(600) }
+
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("Unexpected IssueCategory for SafetySourceIssue: 600")
+ }
+
+ @Test
fun build_withInvalidOnDismissPendingIntent_throwsIllegalArgumentException() {
val builder =
SafetySourceIssue.Builder(
@@ -604,9 +1433,16 @@ class SafetySourceIssueTest {
val exception =
assertFailsWith(IllegalArgumentException::class) { safetySourceIssueBuilder.build() }
+
assertThat(exception)
.hasMessageThat()
- .isEqualTo("Safety source issue must contain at least 1 action")
+ .isEqualTo(
+ if (SdkLevel.isAtLeastU()) {
+ "Actionable safety source issue must contain at least 1 action"
+ } else {
+ "Safety source issue must contain at least 1 action"
+ }
+ )
}
@Test
@@ -631,6 +1467,60 @@ class SafetySourceIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun build_withNoActionsAndManualActionabilityOnU_throwsIllegalArgumentException() {
+ val safetySourceIssueBuilder =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+
+ val exception =
+ assertFailsWith(IllegalArgumentException::class) { safetySourceIssueBuilder.build() }
+
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo("Actionable safety source issue must contain at least 1 action")
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun build_withNoActionsAndTipActionabilityOnU_success() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_TIP)
+ .build()
+
+ assertThat(safetySourceIssue.actions).isEmpty()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun build_withNoActionsAndAutomaticActionabilityOnU_success() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
+ .build()
+
+ assertThat(safetySourceIssue.actions).isEmpty()
+ }
+
+ @Test
fun describeContents_returns0() {
val safetySourceIssue =
SafetySourceIssue.Builder(
@@ -671,8 +1561,379 @@ class SafetySourceIssueTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun parcelRoundTrip_recreatesEqual_atLeastAndroidU() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(
+ Action.Builder(action1)
+ .setConfirmationDialogDetails(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ .build()
+ )
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntentService)
+ .build()
+
+ assertThat(safetySourceIssue).recreatesEqual(SafetySourceIssue.CREATOR)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun parcelRoundTrip_recreatesEqual_atLeastUpsideDownCake() {
+ val safetySourceIssue =
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DATA)
+ .setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_TIP)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntentService)
+ .setCustomNotification(
+ Notification.Builder("Notification title", "Notification text")
+ .addAction(action2)
+ .build()
+ )
+ .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_DELAYED)
+ .setAttributionTitle("attribution title")
+ .setDeduplicationId("deduplication_id")
+ .build()
+
+ assertThat(safetySourceIssue).recreatesEqual(SafetySourceIssue.CREATOR)
+ }
+
+ @Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ newTiramisuEqualsHashCodeToStringTester().test()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastUpsideDownCake() {
+ newUpsideDownCakeEqualsHashCodeToStringTester().test()
+ }
+
+ /**
+ * Creates a new [EqualsHashCodeToStringTester] instance with all the equality groups in the
+ * [newTiramisuEqualsHashCodeToStringTester] plus new equality groups covering all the new
+ * fields added in U.
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ private fun newUpsideDownCakeEqualsHashCodeToStringTester() =
+ newTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder = { SafetySourceIssue.Builder(it).build() }
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntentService)
+ .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_DELAYED)
+ .setCustomNotification(
+ Notification.Builder("Notification title", "Notification text")
+ .addAction(action2)
+ .build()
+ )
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntentService)
+ .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY)
+ .setCustomNotification(
+ Notification.Builder("Other title", "Other text").addAction(action2).build()
+ )
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntentService)
+ .setAttributionTitle("attribution title")
+ .build(),
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntentService)
+ .setAttributionTitle("attribution title")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_CRITICAL_WARNING,
+ "issue_type_id"
+ )
+ .setAttributionTitle("Other issue attribution title")
+ .addAction(action1)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntentService)
+ .setAttributionTitle("attribution title")
+ .setDeduplicationId("deduplication_id")
+ .build(),
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntentService)
+ .setAttributionTitle("attribution title")
+ .setDeduplicationId("deduplication_id")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntentService)
+ .setAttributionTitle("attribution title")
+ .setDeduplicationId("other_deduplication_id")
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Other issue subtitle")
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_DATA)
+ .addAction(action1)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Other issue subtitle")
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS)
+ .addAction(action1)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Other issue subtitle")
+ .setIssueCategory(SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY)
+ .addAction(action1)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_MANUAL)
+ .addAction(action1)
+ .setAttributionTitle("Attribution title")
+ .setDeduplicationId("dedup_id")
+ .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_NEVER)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_TIP)
+ .addAction(action1)
+ .build(),
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_TIP)
+ .addAction(action1)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
+ .addAction(action1)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setNotificationBehavior(SafetySourceIssue.NOTIFICATION_BEHAVIOR_DELAYED)
+ .setIssueActionability(SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(
+ Action.Builder(action1)
+ .setConfirmationDialogDetails(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ .build()
+ )
+ .addAction(action2)
+ .setOnDismissPendingIntent(pendingIntentService)
+ .build()
+ )
+ .addEqualityGroup(
+ SafetySourceIssue.Builder(
+ "Issue id",
+ "Issue title",
+ "Issue summary",
+ SEVERITY_LEVEL_INFORMATION,
+ "issue_type_id"
+ )
+ .setSubtitle("Issue subtitle")
+ .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+ .addAction(action1)
+ .addAction(
+ Action.Builder(action2)
+ .setConfirmationDialogDetails(
+ ConfirmationDialogDetails("Title", "Text", "Accept", "Deny")
+ )
+ .build()
+ )
+ .setOnDismissPendingIntent(pendingIntentService)
+ .build()
+ )
+
+ /**
+ * Creates a new [EqualsHashCodeToStringTester] instance which covers all the fields in the T
+ * API and is safe to use on any T+ API level.
+ */
+ private fun newTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder: ((SafetySourceIssue) -> SafetySourceIssue)? = null
+ ) =
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetySourceIssue.CREATOR,
+ createCopy = createCopyFromBuilder
+ )
.addEqualityGroup(
SafetySourceIssue.Builder(
"Issue id",
@@ -845,6 +2106,53 @@ class SafetySourceIssueTest {
)
.build()
)
- .test()
- }
+
+ private fun actionNewTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder: ((Action) -> Action)? = null
+ ) =
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = Action.CREATOR,
+ createCopy = createCopyFromBuilder
+ )
+ .addEqualityGroup(
+ Action.Builder("action_id", "Action label", pendingIntent1).build(),
+ Action.Builder("action_id", "Action label", pendingIntent1).build(),
+ Action.Builder("action_id", "Action label", pendingIntent1)
+ .setWillResolve(false)
+ .build()
+ )
+ .addEqualityGroup(
+ Action.Builder("action_id", "Action label", pendingIntent1)
+ .setSuccessMessage("Action successfully completed")
+ .build()
+ )
+ .addEqualityGroup(
+ Action.Builder("action_id", "Other action label", pendingIntent1).build()
+ )
+ .addEqualityGroup(
+ Action.Builder("other_action_id", "Action label", pendingIntent1).build()
+ )
+ .addEqualityGroup(
+ Action.Builder("action_id", "Action label", pendingIntentService)
+ .setWillResolve(true)
+ .build()
+ )
+ .addEqualityGroup(
+ Action.Builder(
+ "action_id",
+ "Action label",
+ PendingIntent.getActivity(
+ context,
+ 0,
+ Intent("Other action PendingIntent"),
+ FLAG_IMMUTABLE
+ )
+ )
+ .build()
+ )
+ .addEqualityGroup(
+ Action.Builder("action_id", "Action label", pendingIntent1)
+ .setSuccessMessage("Other action successfully completed")
+ .build()
+ )
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
index 2a4bff875..5aeea21f1 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
@@ -20,6 +20,7 @@ import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.content.Context
import android.content.Intent
+import android.os.Build
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_INFORMATION
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED
@@ -31,6 +32,7 @@ import android.safetycenter.cts.testing.Generic
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
+import androidx.test.filters.SdkSuppress
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
@@ -93,7 +95,7 @@ class SafetySourceStatusTest {
@Test
fun iconAction_equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(parcelableCreator = IconAction.CREATOR)
.addEqualityGroup(
IconAction(ICON_TYPE_GEAR, pendingIntent1),
IconAction(ICON_TYPE_GEAR, pendingIntent1)
@@ -279,7 +281,25 @@ class SafetySourceStatusTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ newTiramisuEqualsHashCodeToStringTester().test()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
+ newTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder = { SafetySourceStatus.Builder(it).build() }
+ )
+ .test()
+ }
+
+ private fun newTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder: ((SafetySourceStatus) -> SafetySourceStatus)? = null
+ ) =
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetySourceStatus.CREATOR,
+ createCopy = createCopyFromBuilder
+ )
.addEqualityGroup(
SafetySourceStatus.Builder(
"Status title",
@@ -359,6 +379,4 @@ class SafetySourceStatusTest {
.setEnabled(false)
.build()
)
- .test()
- }
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt
index 338b433b6..7dff9dd54 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetyCenterConfigTest.kt
@@ -16,9 +16,11 @@
package android.safetycenter.cts.config
+import android.os.Build
import android.safetycenter.config.SafetyCenterConfig
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
+import androidx.test.filters.SdkSuppress
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
@@ -32,7 +34,10 @@ class SafetyCenterConfigTest {
@Test
fun getSafetySourcesGroups_returnsSafetySourcesGroups() {
assertThat(BASE.safetySourcesGroups)
- .containsExactly(SafetySourcesGroupTest.RIGID, SafetySourcesGroupTest.HIDDEN)
+ .containsExactly(
+ SafetySourcesGroupTest.STATELESS_INFERRED,
+ SafetySourcesGroupTest.HIDDEN_INFERRED
+ )
.inOrder()
}
@@ -41,7 +46,7 @@ class SafetyCenterConfigTest {
val sourcesGroups = BASE.safetySourcesGroups
assertFailsWith(UnsupportedOperationException::class) {
- sourcesGroups.add(SafetySourcesGroupTest.COLLAPSIBLE_WITH_SUMMARY)
+ sourcesGroups.add(SafetySourcesGroupTest.STATEFUL_INFERRED_WITH_SUMMARY)
}
}
@@ -49,16 +54,19 @@ class SafetyCenterConfigTest {
fun builder_addSafetySourcesGroup_doesNotMutatePreviouslyBuiltInstance() {
val safetyCenterConfigBuilder =
SafetyCenterConfig.Builder()
- .addSafetySourcesGroup(SafetySourcesGroupTest.RIGID)
- .addSafetySourcesGroup(SafetySourcesGroupTest.HIDDEN)
+ .addSafetySourcesGroup(SafetySourcesGroupTest.STATELESS_INFERRED)
+ .addSafetySourcesGroup(SafetySourcesGroupTest.HIDDEN_INFERRED)
val sourceGroups = safetyCenterConfigBuilder.build().safetySourcesGroups
safetyCenterConfigBuilder.addSafetySourcesGroup(
- SafetySourcesGroupTest.COLLAPSIBLE_WITH_SUMMARY
+ SafetySourcesGroupTest.STATEFUL_INFERRED_WITH_SUMMARY
)
assertThat(sourceGroups)
- .containsExactly(SafetySourcesGroupTest.RIGID, SafetySourcesGroupTest.HIDDEN)
+ .containsExactly(
+ SafetySourcesGroupTest.STATELESS_INFERRED,
+ SafetySourcesGroupTest.HIDDEN_INFERRED
+ )
.inOrder()
}
@@ -74,28 +82,44 @@ class SafetyCenterConfigTest {
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ newTiramisuEqualsHashCodeToStringTester().test()
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun equalsHashCodeToString_usingEqualsHashCodeToStringTester_atLeastAndroidU() {
+ newTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder = { SafetyCenterConfig.Builder(it).build() }
+ )
+ .test()
+ }
+
+ private fun newTiramisuEqualsHashCodeToStringTester(
+ createCopyFromBuilder: ((SafetyCenterConfig) -> SafetyCenterConfig)? = null
+ ) =
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetyCenterConfig.CREATOR,
+ createCopy = createCopyFromBuilder
+ )
.addEqualityGroup(
BASE,
SafetyCenterConfig.Builder()
- .addSafetySourcesGroup(SafetySourcesGroupTest.RIGID)
- .addSafetySourcesGroup(SafetySourcesGroupTest.HIDDEN)
+ .addSafetySourcesGroup(SafetySourcesGroupTest.STATELESS_INFERRED)
+ .addSafetySourcesGroup(SafetySourcesGroupTest.HIDDEN_INFERRED)
.build()
)
.addEqualityGroup(
SafetyCenterConfig.Builder()
- .addSafetySourcesGroup(SafetySourcesGroupTest.HIDDEN)
- .addSafetySourcesGroup(SafetySourcesGroupTest.RIGID)
+ .addSafetySourcesGroup(SafetySourcesGroupTest.HIDDEN_INFERRED)
+ .addSafetySourcesGroup(SafetySourcesGroupTest.STATELESS_INFERRED)
.build()
)
- .test()
- }
companion object {
private val BASE =
SafetyCenterConfig.Builder()
- .addSafetySourcesGroup(SafetySourcesGroupTest.RIGID)
- .addSafetySourcesGroup(SafetySourcesGroupTest.HIDDEN)
+ .addSafetySourcesGroup(SafetySourcesGroupTest.STATELESS_INFERRED)
+ .addSafetySourcesGroup(SafetySourcesGroupTest.HIDDEN_INFERRED)
.build()
}
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
index 425f6d0aa..055e82ad3 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
@@ -17,9 +17,12 @@
package android.safetycenter.cts.config
import android.content.res.Resources
+import android.os.Build
import android.safetycenter.config.SafetySource
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
+import androidx.test.filters.SdkSuppress
+import com.android.modules.utils.build.SdkLevel
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
@@ -33,7 +36,7 @@ class SafetySourceTest {
@Test
fun getType_returnsType() {
assertThat(DYNAMIC_BAREBONE.type).isEqualTo(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
- assertThat(DYNAMIC_ALL_OPTIONAL.type).isEqualTo(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
+ assertThat(dynamicAllOptional().type).isEqualTo(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
assertThat(DYNAMIC_HIDDEN.type).isEqualTo(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.type)
.isEqualTo(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
@@ -41,40 +44,54 @@ class SafetySourceTest {
assertThat(STATIC_BAREBONE.type).isEqualTo(SafetySource.SAFETY_SOURCE_TYPE_STATIC)
assertThat(STATIC_ALL_OPTIONAL.type).isEqualTo(SafetySource.SAFETY_SOURCE_TYPE_STATIC)
assertThat(ISSUE_ONLY_BAREBONE.type).isEqualTo(SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY)
- assertThat(ISSUE_ONLY_ALL_OPTIONAL.type)
+ assertThat(issueOnlyAllOptional().type)
.isEqualTo(SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY)
}
@Test
fun getId_returnsId() {
assertThat(DYNAMIC_BAREBONE.id).isEqualTo(DYNAMIC_BAREBONE_ID)
- assertThat(DYNAMIC_ALL_OPTIONAL.id).isEqualTo(DYNAMIC_ALL_OPTIONAL_ID)
+ assertThat(dynamicAllOptional().id).isEqualTo(DYNAMIC_ALL_OPTIONAL_ID)
assertThat(DYNAMIC_HIDDEN.id).isEqualTo(DYNAMIC_HIDDEN_ID)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.id).isEqualTo(DYNAMIC_HIDDEN_WITH_SEARCH_ID)
assertThat(DYNAMIC_DISABLED.id).isEqualTo(DYNAMIC_DISABLED_ID)
assertThat(STATIC_BAREBONE.id).isEqualTo(STATIC_BAREBONE_ID)
assertThat(STATIC_ALL_OPTIONAL.id).isEqualTo(STATIC_ALL_OPTIONAL_ID)
assertThat(ISSUE_ONLY_BAREBONE.id).isEqualTo(ISSUE_ONLY_BAREBONE_ID)
- assertThat(ISSUE_ONLY_ALL_OPTIONAL.id).isEqualTo(ISSUE_ONLY_ALL_OPTIONAL_ID)
+ assertThat(issueOnlyAllOptional().id).isEqualTo(ISSUE_ONLY_ALL_OPTIONAL_ID)
}
@Test
fun getPackageName_returnsPackageNameOrThrows() {
assertThat(DYNAMIC_BAREBONE.packageName).isEqualTo(PACKAGE_NAME)
- assertThat(DYNAMIC_ALL_OPTIONAL.packageName).isEqualTo(PACKAGE_NAME)
+ assertThat(dynamicAllOptional().packageName).isEqualTo(PACKAGE_NAME)
assertThat(DYNAMIC_HIDDEN.packageName).isEqualTo(PACKAGE_NAME)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.packageName).isEqualTo(PACKAGE_NAME)
assertThat(DYNAMIC_DISABLED.packageName).isEqualTo(PACKAGE_NAME)
assertThrows(UnsupportedOperationException::class.java) { STATIC_BAREBONE.packageName }
assertThrows(UnsupportedOperationException::class.java) { STATIC_ALL_OPTIONAL.packageName }
assertThat(ISSUE_ONLY_BAREBONE.packageName).isEqualTo(PACKAGE_NAME)
- assertThat(ISSUE_ONLY_ALL_OPTIONAL.packageName).isEqualTo(PACKAGE_NAME)
+ assertThat(issueOnlyAllOptional().packageName).isEqualTo(PACKAGE_NAME)
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun getOptionalPackageName_returnsPackageNameOrNull() {
+ assertThat(DYNAMIC_BAREBONE.optionalPackageName).isEqualTo(PACKAGE_NAME)
+ assertThat(dynamicAllOptional().optionalPackageName).isEqualTo(PACKAGE_NAME)
+ assertThat(DYNAMIC_HIDDEN.optionalPackageName).isEqualTo(PACKAGE_NAME)
+ assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.optionalPackageName).isEqualTo(PACKAGE_NAME)
+ assertThat(DYNAMIC_DISABLED.optionalPackageName).isEqualTo(PACKAGE_NAME)
+ assertThat(STATIC_BAREBONE.optionalPackageName).isNull()
+ assertThat(STATIC_ALL_OPTIONAL.optionalPackageName).isEqualTo(PACKAGE_NAME)
+ assertThat(ISSUE_ONLY_BAREBONE.optionalPackageName).isEqualTo(PACKAGE_NAME)
+ assertThat(issueOnlyAllOptional().optionalPackageName).isEqualTo(PACKAGE_NAME)
}
@Test
fun getTitleResId_returnsTitleResIdOrThrows() {
assertThat(DYNAMIC_BAREBONE.titleResId).isEqualTo(REFERENCE_RES_ID)
- assertThat(DYNAMIC_ALL_OPTIONAL.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(dynamicAllOptional().titleResId).isEqualTo(REFERENCE_RES_ID)
assertThat(DYNAMIC_DISABLED.titleResId).isEqualTo(REFERENCE_RES_ID)
assertThat(DYNAMIC_HIDDEN.titleResId).isEqualTo(Resources.ID_NULL)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.titleResId).isEqualTo(REFERENCE_RES_ID)
@@ -82,7 +99,7 @@ class SafetySourceTest {
assertThat(STATIC_ALL_OPTIONAL.titleResId).isEqualTo(REFERENCE_RES_ID)
assertThrows(UnsupportedOperationException::class.java) { ISSUE_ONLY_BAREBONE.titleResId }
assertThrows(UnsupportedOperationException::class.java) {
- ISSUE_ONLY_ALL_OPTIONAL.titleResId
+ issueOnlyAllOptional().titleResId
}
}
@@ -91,7 +108,7 @@ class SafetySourceTest {
assertThrows(UnsupportedOperationException::class.java) {
DYNAMIC_BAREBONE.titleForWorkResId
}
- assertThat(DYNAMIC_ALL_OPTIONAL.titleForWorkResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(dynamicAllOptional().titleForWorkResId).isEqualTo(REFERENCE_RES_ID)
assertThrows(UnsupportedOperationException::class.java) {
DYNAMIC_DISABLED.titleForWorkResId
}
@@ -105,14 +122,14 @@ class SafetySourceTest {
ISSUE_ONLY_BAREBONE.titleForWorkResId
}
assertThrows(UnsupportedOperationException::class.java) {
- ISSUE_ONLY_ALL_OPTIONAL.titleForWorkResId
+ issueOnlyAllOptional().titleForWorkResId
}
}
@Test
fun getSummaryResId_returnsSummaryResIdOrThrows() {
assertThat(DYNAMIC_BAREBONE.summaryResId).isEqualTo(REFERENCE_RES_ID)
- assertThat(DYNAMIC_ALL_OPTIONAL.summaryResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(dynamicAllOptional().summaryResId).isEqualTo(REFERENCE_RES_ID)
assertThat(DYNAMIC_DISABLED.summaryResId).isEqualTo(REFERENCE_RES_ID)
assertThat(DYNAMIC_HIDDEN.summaryResId).isEqualTo(Resources.ID_NULL)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.summaryResId).isEqualTo(REFERENCE_RES_ID)
@@ -120,14 +137,14 @@ class SafetySourceTest {
assertThat(STATIC_ALL_OPTIONAL.summaryResId).isEqualTo(REFERENCE_RES_ID)
assertThrows(UnsupportedOperationException::class.java) { ISSUE_ONLY_BAREBONE.summaryResId }
assertThrows(UnsupportedOperationException::class.java) {
- ISSUE_ONLY_ALL_OPTIONAL.summaryResId
+ issueOnlyAllOptional().summaryResId
}
}
@Test
fun getIntentAction_returnsIntentActionOrThrows() {
assertThat(DYNAMIC_BAREBONE.intentAction).isEqualTo(INTENT_ACTION)
- assertThat(DYNAMIC_ALL_OPTIONAL.intentAction).isEqualTo(INTENT_ACTION)
+ assertThat(dynamicAllOptional().intentAction).isEqualTo(INTENT_ACTION)
assertThat(DYNAMIC_DISABLED.intentAction).isNull()
assertThat(DYNAMIC_HIDDEN.intentAction).isNull()
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.intentAction).isEqualTo(INTENT_ACTION)
@@ -135,28 +152,28 @@ class SafetySourceTest {
assertThat(STATIC_ALL_OPTIONAL.intentAction).isEqualTo(INTENT_ACTION)
assertThrows(UnsupportedOperationException::class.java) { ISSUE_ONLY_BAREBONE.intentAction }
assertThrows(UnsupportedOperationException::class.java) {
- ISSUE_ONLY_ALL_OPTIONAL.intentAction
+ issueOnlyAllOptional().intentAction
}
}
@Test
fun getProfile_returnsProfile() {
assertThat(DYNAMIC_BAREBONE.profile).isEqualTo(SafetySource.PROFILE_PRIMARY)
- assertThat(DYNAMIC_ALL_OPTIONAL.profile).isEqualTo(SafetySource.PROFILE_ALL)
+ assertThat(dynamicAllOptional().profile).isEqualTo(SafetySource.PROFILE_ALL)
assertThat(DYNAMIC_DISABLED.profile).isEqualTo(SafetySource.PROFILE_PRIMARY)
assertThat(DYNAMIC_HIDDEN.profile).isEqualTo(SafetySource.PROFILE_ALL)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.profile).isEqualTo(SafetySource.PROFILE_ALL)
assertThat(STATIC_BAREBONE.profile).isEqualTo(SafetySource.PROFILE_PRIMARY)
assertThat(STATIC_ALL_OPTIONAL.profile).isEqualTo(SafetySource.PROFILE_ALL)
assertThat(ISSUE_ONLY_BAREBONE.profile).isEqualTo(SafetySource.PROFILE_PRIMARY)
- assertThat(ISSUE_ONLY_ALL_OPTIONAL.profile).isEqualTo(SafetySource.PROFILE_ALL)
+ assertThat(issueOnlyAllOptional().profile).isEqualTo(SafetySource.PROFILE_ALL)
}
@Test
fun getInitialDisplayState_returnsInitialDisplayStateOrThrows() {
assertThat(DYNAMIC_BAREBONE.initialDisplayState)
.isEqualTo(SafetySource.INITIAL_DISPLAY_STATE_ENABLED)
- assertThat(DYNAMIC_ALL_OPTIONAL.initialDisplayState)
+ assertThat(dynamicAllOptional().initialDisplayState)
.isEqualTo(SafetySource.INITIAL_DISPLAY_STATE_DISABLED)
assertThat(DYNAMIC_DISABLED.initialDisplayState)
.isEqualTo(SafetySource.INITIAL_DISPLAY_STATE_DISABLED)
@@ -174,14 +191,14 @@ class SafetySourceTest {
ISSUE_ONLY_BAREBONE.initialDisplayState
}
assertThrows(UnsupportedOperationException::class.java) {
- ISSUE_ONLY_ALL_OPTIONAL.initialDisplayState
+ issueOnlyAllOptional().initialDisplayState
}
}
@Test
fun getMaxSeverityLevel_returnsMaxSeverityLevelOrThrows() {
assertThat(DYNAMIC_BAREBONE.maxSeverityLevel).isEqualTo(Integer.MAX_VALUE)
- assertThat(DYNAMIC_ALL_OPTIONAL.maxSeverityLevel).isEqualTo(MAX_SEVERITY_LEVEL)
+ assertThat(dynamicAllOptional().maxSeverityLevel).isEqualTo(MAX_SEVERITY_LEVEL)
assertThat(DYNAMIC_DISABLED.maxSeverityLevel).isEqualTo(Integer.MAX_VALUE)
assertThat(DYNAMIC_HIDDEN.maxSeverityLevel).isEqualTo(Integer.MAX_VALUE)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.maxSeverityLevel).isEqualTo(Integer.MAX_VALUE)
@@ -190,13 +207,13 @@ class SafetySourceTest {
STATIC_ALL_OPTIONAL.maxSeverityLevel
}
assertThat(ISSUE_ONLY_BAREBONE.maxSeverityLevel).isEqualTo(Integer.MAX_VALUE)
- assertThat(ISSUE_ONLY_ALL_OPTIONAL.maxSeverityLevel).isEqualTo(MAX_SEVERITY_LEVEL)
+ assertThat(issueOnlyAllOptional().maxSeverityLevel).isEqualTo(MAX_SEVERITY_LEVEL)
}
@Test
fun getSearchTermsResId_returnsSearchTermsResIdOrThrows() {
assertThat(DYNAMIC_BAREBONE.searchTermsResId).isEqualTo(Resources.ID_NULL)
- assertThat(DYNAMIC_ALL_OPTIONAL.searchTermsResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(dynamicAllOptional().searchTermsResId).isEqualTo(REFERENCE_RES_ID)
assertThat(DYNAMIC_DISABLED.searchTermsResId).isEqualTo(Resources.ID_NULL)
assertThat(DYNAMIC_HIDDEN.searchTermsResId).isEqualTo(Resources.ID_NULL)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.searchTermsResId).isEqualTo(REFERENCE_RES_ID)
@@ -206,14 +223,14 @@ class SafetySourceTest {
ISSUE_ONLY_BAREBONE.searchTermsResId
}
assertThrows(UnsupportedOperationException::class.java) {
- ISSUE_ONLY_ALL_OPTIONAL.searchTermsResId
+ issueOnlyAllOptional().searchTermsResId
}
}
@Test
fun isLoggingAllowed_returnsLoggingAllowedOrThrows() {
assertThat(DYNAMIC_BAREBONE.isLoggingAllowed).isEqualTo(true)
- assertThat(DYNAMIC_ALL_OPTIONAL.isLoggingAllowed).isEqualTo(false)
+ assertThat(dynamicAllOptional().isLoggingAllowed).isEqualTo(false)
assertThat(DYNAMIC_DISABLED.isLoggingAllowed).isEqualTo(true)
assertThat(DYNAMIC_HIDDEN.isLoggingAllowed).isEqualTo(true)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.isLoggingAllowed).isEqualTo(true)
@@ -222,13 +239,13 @@ class SafetySourceTest {
STATIC_ALL_OPTIONAL.isLoggingAllowed
}
assertThat(ISSUE_ONLY_BAREBONE.isLoggingAllowed).isEqualTo(true)
- assertThat(ISSUE_ONLY_ALL_OPTIONAL.isLoggingAllowed).isEqualTo(false)
+ assertThat(issueOnlyAllOptional().isLoggingAllowed).isEqualTo(false)
}
@Test
fun isRefreshOnPageOpenAllowed_returnsRefreshOnPageOpenAllowedOrThrows() {
assertThat(DYNAMIC_BAREBONE.isRefreshOnPageOpenAllowed).isEqualTo(false)
- assertThat(DYNAMIC_ALL_OPTIONAL.isRefreshOnPageOpenAllowed).isEqualTo(true)
+ assertThat(dynamicAllOptional().isRefreshOnPageOpenAllowed).isEqualTo(true)
assertThat(DYNAMIC_DISABLED.isRefreshOnPageOpenAllowed).isEqualTo(false)
assertThat(DYNAMIC_HIDDEN.isRefreshOnPageOpenAllowed).isEqualTo(false)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.isRefreshOnPageOpenAllowed).isEqualTo(false)
@@ -239,41 +256,91 @@ class SafetySourceTest {
STATIC_ALL_OPTIONAL.isRefreshOnPageOpenAllowed
}
assertThat(ISSUE_ONLY_BAREBONE.isRefreshOnPageOpenAllowed).isEqualTo(false)
- assertThat(ISSUE_ONLY_ALL_OPTIONAL.isRefreshOnPageOpenAllowed).isEqualTo(true)
+ assertThat(issueOnlyAllOptional().isRefreshOnPageOpenAllowed).isEqualTo(true)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ fun areNotificationsAllowed_returnsNotificationsAllowed() {
+ assertThat(DYNAMIC_BAREBONE.areNotificationsAllowed()).isFalse()
+ assertThat(dynamicAllOptional().areNotificationsAllowed()).isTrue()
+ assertThat(DYNAMIC_DISABLED.areNotificationsAllowed()).isFalse()
+ assertThat(DYNAMIC_HIDDEN.areNotificationsAllowed()).isFalse()
+ assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.areNotificationsAllowed()).isFalse()
+ assertThat(STATIC_BAREBONE.areNotificationsAllowed()).isFalse()
+ assertThat(STATIC_ALL_OPTIONAL.areNotificationsAllowed()).isFalse()
+ assertThat(ISSUE_ONLY_BAREBONE.areNotificationsAllowed()).isFalse()
+ assertThat(issueOnlyAllOptional().areNotificationsAllowed()).isTrue()
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ fun getDeduplicationGroupsList_returnsDeduplicationGroups() {
+ assertThat(DYNAMIC_BAREBONE.deduplicationGroup).isNull()
+ assertThat(dynamicAllOptional().deduplicationGroup).isEqualTo(DEDUPLICATION_GROUP)
+ assertThat(DYNAMIC_DISABLED.deduplicationGroup).isNull()
+ assertThat(DYNAMIC_HIDDEN.deduplicationGroup).isNull()
+ assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.deduplicationGroup).isNull()
+ assertThat(STATIC_BAREBONE.deduplicationGroup).isNull()
+ assertThat(STATIC_ALL_OPTIONAL.deduplicationGroup).isNull()
+ assertThat(ISSUE_ONLY_BAREBONE.deduplicationGroup).isNull()
+ assertThat(issueOnlyAllOptional().deduplicationGroup).isEqualTo(DEDUPLICATION_GROUP)
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ fun getPackageCertificateHashes_returnsPackageCerts() {
+ assertThat(DYNAMIC_BAREBONE.packageCertificateHashes).isEmpty()
+ assertThat(dynamicAllOptional().packageCertificateHashes).containsExactly(HASH1)
+ assertThat(DYNAMIC_DISABLED.packageCertificateHashes).isEmpty()
+ assertThat(DYNAMIC_HIDDEN.packageCertificateHashes).isEmpty()
+ assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.packageCertificateHashes).isEmpty()
+ assertThat(STATIC_BAREBONE.packageCertificateHashes).isEmpty()
+ assertThat(STATIC_ALL_OPTIONAL.packageCertificateHashes).isEmpty()
+ assertThat(ISSUE_ONLY_BAREBONE.packageCertificateHashes).isEmpty()
+ assertThat(issueOnlyAllOptional().packageCertificateHashes).containsExactly(HASH1, HASH2)
}
@Test
fun describeContents_returns0() {
assertThat(DYNAMIC_BAREBONE.describeContents()).isEqualTo(0)
- assertThat(DYNAMIC_ALL_OPTIONAL.describeContents()).isEqualTo(0)
+ assertThat(dynamicAllOptional().describeContents()).isEqualTo(0)
assertThat(DYNAMIC_DISABLED.describeContents()).isEqualTo(0)
assertThat(DYNAMIC_HIDDEN.describeContents()).isEqualTo(0)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.describeContents()).isEqualTo(0)
assertThat(STATIC_BAREBONE.describeContents()).isEqualTo(0)
assertThat(STATIC_ALL_OPTIONAL.describeContents()).isEqualTo(0)
assertThat(ISSUE_ONLY_BAREBONE.describeContents()).isEqualTo(0)
- assertThat(ISSUE_ONLY_ALL_OPTIONAL.describeContents()).isEqualTo(0)
+ assertThat(issueOnlyAllOptional().describeContents()).isEqualTo(0)
}
@Test
fun parcelRoundTrip_recreatesEqual() {
assertThat(DYNAMIC_BAREBONE).recreatesEqual(SafetySource.CREATOR)
- assertThat(DYNAMIC_ALL_OPTIONAL).recreatesEqual(SafetySource.CREATOR)
+ assertThat(dynamicAllOptional()).recreatesEqual(SafetySource.CREATOR)
assertThat(DYNAMIC_DISABLED).recreatesEqual(SafetySource.CREATOR)
assertThat(DYNAMIC_HIDDEN).recreatesEqual(SafetySource.CREATOR)
assertThat(DYNAMIC_HIDDEN_WITH_SEARCH).recreatesEqual(SafetySource.CREATOR)
assertThat(STATIC_BAREBONE).recreatesEqual(SafetySource.CREATOR)
assertThat(STATIC_ALL_OPTIONAL).recreatesEqual(SafetySource.CREATOR)
assertThat(ISSUE_ONLY_BAREBONE).recreatesEqual(SafetySource.CREATOR)
- assertThat(ISSUE_ONLY_ALL_OPTIONAL).recreatesEqual(SafetySource.CREATOR)
+ assertThat(issueOnlyAllOptional()).recreatesEqual(SafetySource.CREATOR)
}
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetySource.CREATOR,
+ createCopy =
+ if (SdkLevel.isAtLeastU()) {
+ { SafetySource.Builder(it).build() }
+ } else {
+ null
+ }
+ )
.addEqualityGroup(DYNAMIC_BAREBONE)
.addEqualityGroup(
- DYNAMIC_ALL_OPTIONAL,
+ dynamicAllOptional(),
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
.setId(DYNAMIC_ALL_OPTIONAL_ID)
.setPackageName(PACKAGE_NAME)
@@ -287,6 +354,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(DYNAMIC_HIDDEN)
@@ -295,7 +369,7 @@ class SafetySourceTest {
.addEqualityGroup(STATIC_BAREBONE)
.addEqualityGroup(STATIC_ALL_OPTIONAL)
.addEqualityGroup(ISSUE_ONLY_BAREBONE)
- .addEqualityGroup(ISSUE_ONLY_ALL_OPTIONAL)
+ .addEqualityGroup(issueOnlyAllOptional())
.addEqualityGroup(
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
.setId("other")
@@ -310,6 +384,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(
@@ -326,6 +407,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(
@@ -342,6 +430,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(
@@ -358,6 +453,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(
@@ -374,6 +476,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(
@@ -390,6 +499,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(
@@ -414,6 +530,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(
@@ -430,6 +553,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(
@@ -446,6 +576,13 @@ class SafetySourceTest {
.setSearchTermsResId(-1)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(
@@ -462,6 +599,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(true)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
.addEqualityGroup(
@@ -478,8 +622,117 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(false)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ addEqualityGroup(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
+ .setId(DYNAMIC_ALL_OPTIONAL_ID)
+ .setPackageName(PACKAGE_NAME)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setTitleForWorkResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setIntentAction(INTENT_ACTION)
+ .setProfile(SafetySource.PROFILE_ALL)
+ .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_DISABLED)
+ .setMaxSeverityLevel(MAX_SEVERITY_LEVEL)
+ .setSearchTermsResId(REFERENCE_RES_ID)
+ .setLoggingAllowed(false)
+ .setRefreshOnPageOpenAllowed(true)
+ .setNotificationsAllowed(false)
+ .setDeduplicationGroup(DEDUPLICATION_GROUP)
+ .addPackageCertificateHash(HASH1)
+ .build()
+ )
+ addEqualityGroup(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
+ .setId(DYNAMIC_ALL_OPTIONAL_ID)
+ .setPackageName(PACKAGE_NAME)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setTitleForWorkResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setIntentAction(INTENT_ACTION)
+ .setProfile(SafetySource.PROFILE_ALL)
+ .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_DISABLED)
+ .setMaxSeverityLevel(MAX_SEVERITY_LEVEL)
+ .setSearchTermsResId(REFERENCE_RES_ID)
+ .setLoggingAllowed(false)
+ .setRefreshOnPageOpenAllowed(true)
+ .setNotificationsAllowed(true)
+ .setDeduplicationGroup("other_deduplication_group")
+ .addPackageCertificateHash(HASH1)
+ .build()
+ )
+ // With no package cert hashes provided
+ addEqualityGroup(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
+ .setId(DYNAMIC_ALL_OPTIONAL_ID)
+ .setPackageName(PACKAGE_NAME)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setTitleForWorkResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setIntentAction(INTENT_ACTION)
+ .setProfile(SafetySource.PROFILE_ALL)
+ .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_DISABLED)
+ .setMaxSeverityLevel(MAX_SEVERITY_LEVEL)
+ .setSearchTermsResId(REFERENCE_RES_ID)
+ .setLoggingAllowed(false)
+ .setRefreshOnPageOpenAllowed(true)
+ .setNotificationsAllowed(true)
+ .setDeduplicationGroup(DEDUPLICATION_GROUP)
+ .build()
+ )
+ // With longer package cert hash list
+ addEqualityGroup(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
+ .setId(DYNAMIC_ALL_OPTIONAL_ID)
+ .setPackageName(PACKAGE_NAME)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setTitleForWorkResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setIntentAction(INTENT_ACTION)
+ .setProfile(SafetySource.PROFILE_ALL)
+ .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_DISABLED)
+ .setMaxSeverityLevel(MAX_SEVERITY_LEVEL)
+ .setSearchTermsResId(REFERENCE_RES_ID)
+ .setLoggingAllowed(false)
+ .setRefreshOnPageOpenAllowed(true)
+ .setNotificationsAllowed(true)
+ .setDeduplicationGroup(DEDUPLICATION_GROUP)
+ .addPackageCertificateHash(HASH1)
+ .addPackageCertificateHash(HASH2)
+ .build()
+ )
+ // With package cert hash list with different value
+ addEqualityGroup(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
+ .setId(DYNAMIC_ALL_OPTIONAL_ID)
+ .setPackageName(PACKAGE_NAME)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setTitleForWorkResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setIntentAction(INTENT_ACTION)
+ .setProfile(SafetySource.PROFILE_ALL)
+ .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_DISABLED)
+ .setMaxSeverityLevel(MAX_SEVERITY_LEVEL)
+ .setSearchTermsResId(REFERENCE_RES_ID)
+ .setLoggingAllowed(false)
+ .setRefreshOnPageOpenAllowed(true)
+ .setNotificationsAllowed(true)
+ .setDeduplicationGroup(DEDUPLICATION_GROUP)
+ .addPackageCertificateHash(HASH2)
+ .build()
+ )
+ }
+ }
.test()
}
@@ -498,6 +751,9 @@ class SafetySourceTest {
private const val STATIC_ALL_OPTIONAL_ID = "static_all_optional"
private const val ISSUE_ONLY_BAREBONE_ID = "issue_only_barebone"
private const val ISSUE_ONLY_ALL_OPTIONAL_ID = "issue_only_all_optional"
+ private const val DEDUPLICATION_GROUP = "deduplication_group"
+ private const val HASH1 = "feed1"
+ private const val HASH2 = "feed2"
internal val DYNAMIC_BAREBONE =
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
@@ -509,7 +765,7 @@ class SafetySourceTest {
.setProfile(SafetySource.PROFILE_PRIMARY)
.build()
- private val DYNAMIC_ALL_OPTIONAL =
+ private fun dynamicAllOptional(): SafetySource =
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
.setId(DYNAMIC_ALL_OPTIONAL_ID)
.setPackageName(PACKAGE_NAME)
@@ -523,6 +779,13 @@ class SafetySourceTest {
.setSearchTermsResId(REFERENCE_RES_ID)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ }
+ }
.build()
private val DYNAMIC_DISABLED =
@@ -567,6 +830,7 @@ class SafetySourceTest {
private val STATIC_ALL_OPTIONAL =
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_STATIC)
.setId(STATIC_ALL_OPTIONAL_ID)
+ .apply { if (SdkLevel.isAtLeastU()) setPackageName(PACKAGE_NAME) }
.setTitleResId(REFERENCE_RES_ID)
.setTitleForWorkResId(REFERENCE_RES_ID)
.setSummaryResId(REFERENCE_RES_ID)
@@ -582,7 +846,7 @@ class SafetySourceTest {
.setProfile(SafetySource.PROFILE_PRIMARY)
.build()
- private val ISSUE_ONLY_ALL_OPTIONAL =
+ private fun issueOnlyAllOptional(): SafetySource =
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY)
.setId(ISSUE_ONLY_ALL_OPTIONAL_ID)
.setPackageName(PACKAGE_NAME)
@@ -590,6 +854,14 @@ class SafetySourceTest {
.setMaxSeverityLevel(MAX_SEVERITY_LEVEL)
.setLoggingAllowed(false)
.setRefreshOnPageOpenAllowed(true)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ addPackageCertificateHash(HASH2)
+ }
+ }
.build()
}
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt
index a77d8e2f8..015d18842 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourcesGroupTest.kt
@@ -17,9 +17,13 @@
package android.safetycenter.cts.config
import android.content.res.Resources
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.config.SafetySourcesGroup
+import androidx.annotation.RequiresApi
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.truth.os.ParcelableSubject.assertThat
+import androidx.test.filters.SdkSuppress
+import com.android.modules.utils.build.SdkLevel
import com.android.safetycenter.testing.EqualsHashCodeToStringTester
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
@@ -32,79 +36,159 @@ class SafetySourcesGroupTest {
@Test
fun getType_returnsType() {
- assertThat(COLLAPSIBLE_WITH_SUMMARY.type)
- .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE)
- assertThat(COLLAPSIBLE_WITH_ICON.type)
- .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE)
- assertThat(COLLAPSIBLE_WITH_BOTH.type)
- .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE)
- assertThat(RIGID.type).isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_RIGID)
- assertThat(HIDDEN.type).isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ assertThat(STATEFUL_INFERRED_WITH_SUMMARY.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL)
+ assertThat(STATEFUL_INFERRED_WITH_ICON.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL)
+ assertThat(STATEFUL_INFERRED_WITH_BOTH.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL)
+ assertThat(STATELESS_INFERRED.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS)
+ assertThat(HIDDEN_INFERRED.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ if (SdkLevel.isAtLeastU()) {
+ assertThat(STATEFUL_BAREBONE.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL)
+ assertThat(STATEFUL_ALL_OPTIONAL.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL)
+ assertThat(STATELESS_BAREBONE.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS)
+ assertThat(STATELESS_ALL_OPTIONAL.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS)
+ assertThat(HIDDEN_BAREBONE.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ assertThat(HIDDEN_ALL_OPTIONAL.type)
+ .isEqualTo(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ }
}
@Test
fun getId_returnsId() {
- assertThat(COLLAPSIBLE_WITH_SUMMARY.id).isEqualTo(COLLAPSIBLE_WITH_SUMMARY_ID)
- assertThat(COLLAPSIBLE_WITH_ICON.id).isEqualTo(COLLAPSIBLE_WITH_ICON_ID)
- assertThat(COLLAPSIBLE_WITH_BOTH.id).isEqualTo(COLLAPSIBLE_WITH_BOTH_ID)
- assertThat(RIGID.id).isEqualTo(RIGID_ID)
- assertThat(HIDDEN.id).isEqualTo(HIDDEN_ID)
+ assertThat(STATEFUL_INFERRED_WITH_SUMMARY.id).isEqualTo(STATEFUL_INFERRED_WITH_SUMMARY_ID)
+ assertThat(STATEFUL_INFERRED_WITH_ICON.id).isEqualTo(STATEFUL_INFERRED_WITH_ICON_ID)
+ assertThat(STATEFUL_INFERRED_WITH_BOTH.id).isEqualTo(STATEFUL_INFERRED_WITH_BOTH_ID)
+ assertThat(STATELESS_INFERRED.id).isEqualTo(STATELESS_INFERRED_ID)
+ assertThat(HIDDEN_INFERRED.id).isEqualTo(HIDDEN_INFERRED_ID)
+ if (SdkLevel.isAtLeastU()) {
+ assertThat(STATEFUL_BAREBONE.id).isEqualTo(STATEFUL_BAREBONE_ID)
+ assertThat(STATEFUL_ALL_OPTIONAL.id).isEqualTo(STATEFUL_ALL_OPTIONAL_ID)
+ assertThat(STATELESS_BAREBONE.id).isEqualTo(STATELESS_BAREBONE_ID)
+ assertThat(STATELESS_ALL_OPTIONAL.id).isEqualTo(STATELESS_ALL_OPTIONAL_ID)
+ assertThat(HIDDEN_BAREBONE.id).isEqualTo(HIDDEN_BAREBONE_ID)
+ assertThat(HIDDEN_ALL_OPTIONAL.id).isEqualTo(HIDDEN_ALL_OPTIONAL_ID)
+ }
}
@Test
fun getTitleResId_returnsTitleResId() {
- assertThat(COLLAPSIBLE_WITH_SUMMARY.titleResId).isEqualTo(REFERENCE_RES_ID)
- assertThat(COLLAPSIBLE_WITH_ICON.titleResId).isEqualTo(REFERENCE_RES_ID)
- assertThat(COLLAPSIBLE_WITH_BOTH.titleResId).isEqualTo(REFERENCE_RES_ID)
- assertThat(RIGID.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATEFUL_INFERRED_WITH_SUMMARY.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATEFUL_INFERRED_WITH_ICON.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATEFUL_INFERRED_WITH_BOTH.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATELESS_INFERRED.titleResId).isEqualTo(REFERENCE_RES_ID)
// This is not an enforced invariant, titleResId should just be ignored for hidden groups
- assertThat(HIDDEN.titleResId).isEqualTo(Resources.ID_NULL)
+ assertThat(HIDDEN_INFERRED.titleResId).isEqualTo(Resources.ID_NULL)
+ if (SdkLevel.isAtLeastU()) {
+ assertThat(STATEFUL_BAREBONE.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATEFUL_ALL_OPTIONAL.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATELESS_BAREBONE.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATELESS_ALL_OPTIONAL.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(HIDDEN_BAREBONE.titleResId).isEqualTo(Resources.ID_NULL)
+ assertThat(HIDDEN_ALL_OPTIONAL.titleResId).isEqualTo(REFERENCE_RES_ID)
+ }
}
@Test
fun getSummaryResId_returnsSummaryResId() {
- assertThat(COLLAPSIBLE_WITH_SUMMARY.summaryResId).isEqualTo(REFERENCE_RES_ID)
- assertThat(COLLAPSIBLE_WITH_ICON.summaryResId).isEqualTo(Resources.ID_NULL)
- assertThat(COLLAPSIBLE_WITH_BOTH.summaryResId).isEqualTo(REFERENCE_RES_ID)
- assertThat(RIGID.summaryResId).isEqualTo(Resources.ID_NULL)
+ assertThat(STATEFUL_INFERRED_WITH_SUMMARY.summaryResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATEFUL_INFERRED_WITH_ICON.summaryResId).isEqualTo(Resources.ID_NULL)
+ assertThat(STATEFUL_INFERRED_WITH_BOTH.summaryResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATELESS_INFERRED.summaryResId).isEqualTo(Resources.ID_NULL)
// This is not an enforced invariant, summaryResId should just be ignored for hidden groups
- assertThat(HIDDEN.summaryResId).isEqualTo(Resources.ID_NULL)
+ assertThat(HIDDEN_INFERRED.summaryResId).isEqualTo(Resources.ID_NULL)
+ if (SdkLevel.isAtLeastU()) {
+ assertThat(STATEFUL_BAREBONE.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATEFUL_ALL_OPTIONAL.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATELESS_BAREBONE.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(STATELESS_ALL_OPTIONAL.titleResId).isEqualTo(REFERENCE_RES_ID)
+ assertThat(HIDDEN_BAREBONE.titleResId).isEqualTo(Resources.ID_NULL)
+ assertThat(HIDDEN_ALL_OPTIONAL.titleResId).isEqualTo(REFERENCE_RES_ID)
+ }
}
@Test
fun getStatelessIconType_returnsStatelessIconType() {
- assertThat(COLLAPSIBLE_WITH_SUMMARY.statelessIconType)
+ assertThat(STATEFUL_INFERRED_WITH_SUMMARY.statelessIconType)
.isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_NONE)
- assertThat(COLLAPSIBLE_WITH_ICON.statelessIconType)
+ assertThat(STATEFUL_INFERRED_WITH_ICON.statelessIconType)
.isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
- assertThat(COLLAPSIBLE_WITH_BOTH.statelessIconType)
+ assertThat(STATEFUL_INFERRED_WITH_BOTH.statelessIconType)
.isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
- assertThat(RIGID.statelessIconType).isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_NONE)
+ assertThat(STATELESS_INFERRED.statelessIconType)
+ .isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_NONE)
// This is not an enforced invariant
// statelessIconType should just be ignored for hidden groups
- assertThat(HIDDEN.statelessIconType).isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_NONE)
+ assertThat(HIDDEN_INFERRED.statelessIconType)
+ .isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_NONE)
+ if (SdkLevel.isAtLeastU()) {
+ assertThat(STATEFUL_BAREBONE.statelessIconType)
+ .isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_NONE)
+ assertThat(STATEFUL_ALL_OPTIONAL.statelessIconType)
+ .isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ assertThat(STATELESS_BAREBONE.statelessIconType)
+ .isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_NONE)
+ assertThat(STATELESS_ALL_OPTIONAL.statelessIconType)
+ .isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ assertThat(HIDDEN_BAREBONE.statelessIconType)
+ .isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_NONE)
+ assertThat(HIDDEN_ALL_OPTIONAL.statelessIconType)
+ .isEqualTo(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ }
}
@Test
fun getSafetySources_returnsSafetySources() {
- assertThat(COLLAPSIBLE_WITH_SUMMARY.safetySources)
+ assertThat(STATEFUL_INFERRED_WITH_SUMMARY.safetySources)
.containsExactly(SafetySourceTest.DYNAMIC_BAREBONE)
- assertThat(COLLAPSIBLE_WITH_ICON.safetySources)
+ assertThat(STATEFUL_INFERRED_WITH_ICON.safetySources)
.containsExactly(SafetySourceTest.STATIC_BAREBONE)
- assertThat(COLLAPSIBLE_WITH_BOTH.safetySources)
+ assertThat(STATEFUL_INFERRED_WITH_BOTH.safetySources)
.containsExactly(
SafetySourceTest.DYNAMIC_BAREBONE,
SafetySourceTest.STATIC_BAREBONE,
SafetySourceTest.ISSUE_ONLY_BAREBONE
)
.inOrder()
- assertThat(RIGID.safetySources).containsExactly(SafetySourceTest.STATIC_BAREBONE)
- assertThat(HIDDEN.safetySources).containsExactly(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+ assertThat(STATELESS_INFERRED.safetySources)
+ .containsExactly(SafetySourceTest.STATIC_BAREBONE)
+ assertThat(HIDDEN_INFERRED.safetySources)
+ .containsExactly(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+ if (SdkLevel.isAtLeastU()) {
+ assertThat(STATEFUL_BAREBONE.safetySources)
+ .containsExactly(SafetySourceTest.DYNAMIC_BAREBONE)
+ assertThat(STATEFUL_ALL_OPTIONAL.safetySources)
+ .containsExactly(
+ SafetySourceTest.DYNAMIC_BAREBONE,
+ SafetySourceTest.STATIC_BAREBONE,
+ SafetySourceTest.ISSUE_ONLY_BAREBONE
+ )
+ assertThat(STATELESS_BAREBONE.safetySources)
+ .containsExactly(SafetySourceTest.STATIC_BAREBONE)
+ assertThat(STATELESS_ALL_OPTIONAL.safetySources)
+ .containsExactly(
+ SafetySourceTest.DYNAMIC_BAREBONE,
+ SafetySourceTest.STATIC_BAREBONE,
+ SafetySourceTest.ISSUE_ONLY_BAREBONE
+ )
+ assertThat(HIDDEN_BAREBONE.safetySources)
+ .containsExactly(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+ assertThat(HIDDEN_ALL_OPTIONAL.safetySources)
+ .containsExactly(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+ }
}
@Test
fun getSafetySources_mutationsAreNotAllowed() {
- val sources = COLLAPSIBLE_WITH_SUMMARY.safetySources
+ val sources = STATEFUL_INFERRED_WITH_SUMMARY.safetySources
assertFailsWith(UnsupportedOperationException::class) {
sources.add(SafetySourceTest.DYNAMIC_BAREBONE)
@@ -115,7 +199,7 @@ class SafetySourcesGroupTest {
fun builder_addSafetySource_doesNotMutatePreviouslyBuiltInstance() {
val safetySourcesGroupBuilder =
SafetySourcesGroup.Builder()
- .setId(COLLAPSIBLE_WITH_SUMMARY_ID)
+ .setId(STATEFUL_INFERRED_WITH_SUMMARY_ID)
.setTitleResId(REFERENCE_RES_ID)
.setSummaryResId(REFERENCE_RES_ID)
.addSafetySource(SafetySourceTest.DYNAMIC_BAREBONE)
@@ -127,42 +211,150 @@ class SafetySourcesGroupTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun build_hiddenGroupWithDynamicSource_throwsIllegalStateException() {
+ val builder =
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ .setId(HIDDEN_BAREBONE_ID)
+ .addSafetySource(SafetySourceTest.DYNAMIC_BAREBONE)
+
+ val exception = assertFailsWith(IllegalStateException::class) { builder.build() }
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(
+ "Safety sources groups of type hidden can only contain sources of type issue-only"
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun build_hiddenGroupWithStaticSource_throwsIllegalStateException() {
+ val builder =
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ .setId(HIDDEN_BAREBONE_ID)
+ .addSafetySource(SafetySourceTest.STATIC_BAREBONE)
+
+ val exception = assertFailsWith(IllegalStateException::class) { builder.build() }
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(
+ "Safety sources groups of type hidden can only contain sources of type issue-only"
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun build_statefulGroupWithIssueOnlySource_throwsIllegalStateException() {
+ val builder =
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL)
+ .setId(STATEFUL_BAREBONE_ID)
+ .setTitleResId(REFERENCE_RES_ID)
+ .addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+
+ val exception = assertFailsWith(IllegalStateException::class) { builder.build() }
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(
+ "Safety sources groups containing only sources of type issue-only must be of " +
+ "type hidden"
+ )
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun build_statelessGroupWithIssueOnlySource_throwsIllegalStateException() {
+ val builder =
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS)
+ .setId(STATELESS_BAREBONE_ID)
+ .setTitleResId(REFERENCE_RES_ID)
+ .addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+
+ val exception = assertFailsWith(IllegalStateException::class) { builder.build() }
+ assertThat(exception)
+ .hasMessageThat()
+ .isEqualTo(
+ "Safety sources groups containing only sources of type issue-only must be of " +
+ "type hidden"
+ )
+ }
+
+ @Test
fun describeContents_returns0() {
- assertThat(COLLAPSIBLE_WITH_SUMMARY.describeContents()).isEqualTo(0)
- assertThat(COLLAPSIBLE_WITH_ICON.describeContents()).isEqualTo(0)
- assertThat(COLLAPSIBLE_WITH_BOTH.describeContents()).isEqualTo(0)
- assertThat(RIGID.describeContents()).isEqualTo(0)
- assertThat(HIDDEN.describeContents()).isEqualTo(0)
+ assertThat(STATEFUL_INFERRED_WITH_SUMMARY.describeContents()).isEqualTo(0)
+ assertThat(STATEFUL_INFERRED_WITH_ICON.describeContents()).isEqualTo(0)
+ assertThat(STATEFUL_INFERRED_WITH_BOTH.describeContents()).isEqualTo(0)
+ assertThat(STATELESS_INFERRED.describeContents()).isEqualTo(0)
+ assertThat(HIDDEN_INFERRED.describeContents()).isEqualTo(0)
+ if (SdkLevel.isAtLeastU()) {
+ assertThat(STATEFUL_BAREBONE.describeContents()).isEqualTo(0)
+ assertThat(STATEFUL_ALL_OPTIONAL.describeContents()).isEqualTo(0)
+ assertThat(STATELESS_BAREBONE.describeContents()).isEqualTo(0)
+ assertThat(STATELESS_ALL_OPTIONAL.describeContents()).isEqualTo(0)
+ assertThat(HIDDEN_BAREBONE.describeContents()).isEqualTo(0)
+ assertThat(HIDDEN_ALL_OPTIONAL.describeContents()).isEqualTo(0)
+ }
}
@Test
fun parcelRoundTrip_recreatesEqual() {
- assertThat(COLLAPSIBLE_WITH_SUMMARY).recreatesEqual(SafetySourcesGroup.CREATOR)
- assertThat(COLLAPSIBLE_WITH_ICON).recreatesEqual(SafetySourcesGroup.CREATOR)
- assertThat(COLLAPSIBLE_WITH_BOTH).recreatesEqual(SafetySourcesGroup.CREATOR)
- assertThat(RIGID).recreatesEqual(SafetySourcesGroup.CREATOR)
- assertThat(HIDDEN).recreatesEqual(SafetySourcesGroup.CREATOR)
+ assertThat(STATEFUL_INFERRED_WITH_SUMMARY).recreatesEqual(SafetySourcesGroup.CREATOR)
+ assertThat(STATEFUL_INFERRED_WITH_ICON).recreatesEqual(SafetySourcesGroup.CREATOR)
+ assertThat(STATEFUL_INFERRED_WITH_BOTH).recreatesEqual(SafetySourcesGroup.CREATOR)
+ assertThat(STATELESS_INFERRED).recreatesEqual(SafetySourcesGroup.CREATOR)
+ assertThat(HIDDEN_INFERRED).recreatesEqual(SafetySourcesGroup.CREATOR)
+ if (SdkLevel.isAtLeastU()) {
+ assertThat(STATEFUL_BAREBONE).recreatesEqual(SafetySourcesGroup.CREATOR)
+ assertThat(STATEFUL_ALL_OPTIONAL).recreatesEqual(SafetySourcesGroup.CREATOR)
+ assertThat(STATELESS_BAREBONE).recreatesEqual(SafetySourcesGroup.CREATOR)
+ assertThat(STATELESS_ALL_OPTIONAL).recreatesEqual(SafetySourcesGroup.CREATOR)
+ assertThat(HIDDEN_BAREBONE).recreatesEqual(SafetySourcesGroup.CREATOR)
+ assertThat(HIDDEN_ALL_OPTIONAL).recreatesEqual(SafetySourcesGroup.CREATOR)
+ }
}
@Test
fun equalsHashCodeToString_usingEqualsHashCodeToStringTester() {
- EqualsHashCodeToStringTester()
- .addEqualityGroup(COLLAPSIBLE_WITH_SUMMARY)
- .addEqualityGroup(COLLAPSIBLE_WITH_ICON)
+ EqualsHashCodeToStringTester.ofParcelable(
+ parcelableCreator = SafetySourcesGroup.CREATOR,
+ createCopy =
+ if (SdkLevel.isAtLeastU()) {
+ { SafetySourcesGroup.Builder(it).build() }
+ } else {
+ null
+ }
+ )
+ .addEqualityGroup(STATEFUL_INFERRED_WITH_SUMMARY)
+ .addEqualityGroup(STATEFUL_INFERRED_WITH_ICON)
.addEqualityGroup(
- COLLAPSIBLE_WITH_BOTH,
- SafetySourcesGroup.Builder()
- .setId(COLLAPSIBLE_WITH_BOTH_ID)
- .setTitleResId(REFERENCE_RES_ID)
- .setSummaryResId(REFERENCE_RES_ID)
- .setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
- .addSafetySource(SafetySourceTest.DYNAMIC_BAREBONE)
- .addSafetySource(SafetySourceTest.STATIC_BAREBONE)
- .addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
- .build()
+ *mutableListOf(
+ STATEFUL_INFERRED_WITH_BOTH,
+ SafetySourcesGroup.Builder()
+ .setId(STATEFUL_INFERRED_WITH_BOTH_ID)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ .addSafetySource(SafetySourceTest.DYNAMIC_BAREBONE)
+ .addSafetySource(SafetySourceTest.STATIC_BAREBONE)
+ .addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+ .build()
+ )
+ .apply { if (SdkLevel.isAtLeastU()) add(STATEFUL_ALL_OPTIONAL) }
+ .toTypedArray()
+ )
+ .addEqualityGroup(
+ *mutableListOf(STATELESS_INFERRED)
+ .apply { if (SdkLevel.isAtLeastU()) add(STATELESS_BAREBONE) }
+ .toTypedArray()
+ )
+ .addEqualityGroup(
+ *mutableListOf(HIDDEN_INFERRED)
+ .apply { if (SdkLevel.isAtLeastU()) add(HIDDEN_BAREBONE) }
+ .toTypedArray()
)
- .addEqualityGroup(RIGID)
- .addEqualityGroup(HIDDEN)
.addEqualityGroup(
SafetySourcesGroup.Builder()
.setId("other")
@@ -174,7 +366,7 @@ class SafetySourcesGroupTest {
)
.addEqualityGroup(
SafetySourcesGroup.Builder()
- .setId(COLLAPSIBLE_WITH_BOTH_ID)
+ .setId(STATEFUL_INFERRED_WITH_BOTH_ID)
.setTitleResId(-1)
.setSummaryResId(REFERENCE_RES_ID)
.setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
@@ -183,7 +375,7 @@ class SafetySourcesGroupTest {
)
.addEqualityGroup(
SafetySourcesGroup.Builder()
- .setId(COLLAPSIBLE_WITH_BOTH_ID)
+ .setId(STATEFUL_INFERRED_WITH_BOTH_ID)
.setTitleResId(REFERENCE_RES_ID)
.setSummaryResId(-1)
.setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
@@ -192,7 +384,7 @@ class SafetySourcesGroupTest {
)
.addEqualityGroup(
SafetySourcesGroup.Builder()
- .setId(COLLAPSIBLE_WITH_BOTH_ID)
+ .setId(STATEFUL_INFERRED_WITH_BOTH_ID)
.setTitleResId(REFERENCE_RES_ID)
.setSummaryResId(REFERENCE_RES_ID)
.setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_NONE)
@@ -201,45 +393,58 @@ class SafetySourcesGroupTest {
)
.addEqualityGroup(
SafetySourcesGroup.Builder()
- .setId(COLLAPSIBLE_WITH_BOTH_ID)
+ .setId(STATEFUL_INFERRED_WITH_BOTH_ID)
.setTitleResId(REFERENCE_RES_ID)
.setSummaryResId(REFERENCE_RES_ID)
.setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
.addSafetySource(SafetySourceTest.STATIC_BAREBONE)
.build()
)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ addEqualityGroup(STATEFUL_BAREBONE)
+ addEqualityGroup(STATELESS_ALL_OPTIONAL)
+ addEqualityGroup(HIDDEN_ALL_OPTIONAL)
+ }
+ }
.test()
}
companion object {
private const val REFERENCE_RES_ID = 9999
- private const val COLLAPSIBLE_WITH_SUMMARY_ID = "collapsible_with_summary"
- private const val COLLAPSIBLE_WITH_ICON_ID = "collapsible_with_icon"
- private const val COLLAPSIBLE_WITH_BOTH_ID = "collapsible_with_both"
- private const val RIGID_ID = "rigid"
- private const val HIDDEN_ID = "hidden"
+ private const val STATEFUL_BAREBONE_ID = "stateful_barebone"
+ private const val STATEFUL_ALL_OPTIONAL_ID = "stateful_all_optional"
+ private const val STATELESS_BAREBONE_ID = "stateless_barebone"
+ private const val STATELESS_ALL_OPTIONAL_ID = "stateless_all_optional"
+ private const val HIDDEN_BAREBONE_ID = "hidden_barebone"
+ private const val HIDDEN_ALL_OPTIONAL_ID = "hidden_all_optional"
+ private const val STATEFUL_INFERRED_WITH_SUMMARY_ID = "stateful_inferred_with_summary"
+ private const val STATEFUL_INFERRED_WITH_ICON_ID = "stateful_inferred_with_icon"
+ private const val STATEFUL_INFERRED_WITH_BOTH_ID = STATEFUL_ALL_OPTIONAL_ID
+ private const val STATELESS_INFERRED_ID = STATELESS_BAREBONE_ID
+ private const val HIDDEN_INFERRED_ID = HIDDEN_BAREBONE_ID
// TODO(b/230078826): Consider extracting shared constants to a separate file.
- internal val COLLAPSIBLE_WITH_SUMMARY =
+ internal val STATEFUL_INFERRED_WITH_SUMMARY =
SafetySourcesGroup.Builder()
- .setId(COLLAPSIBLE_WITH_SUMMARY_ID)
+ .setId(STATEFUL_INFERRED_WITH_SUMMARY_ID)
.setTitleResId(REFERENCE_RES_ID)
.setSummaryResId(REFERENCE_RES_ID)
.addSafetySource(SafetySourceTest.DYNAMIC_BAREBONE)
.build()
- private val COLLAPSIBLE_WITH_ICON =
+ private val STATEFUL_INFERRED_WITH_ICON =
SafetySourcesGroup.Builder()
- .setId(COLLAPSIBLE_WITH_ICON_ID)
+ .setId(STATEFUL_INFERRED_WITH_ICON_ID)
.setTitleResId(REFERENCE_RES_ID)
.setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
.addSafetySource(SafetySourceTest.STATIC_BAREBONE)
.build()
- private val COLLAPSIBLE_WITH_BOTH =
+ private val STATEFUL_INFERRED_WITH_BOTH =
SafetySourcesGroup.Builder()
- .setId(COLLAPSIBLE_WITH_BOTH_ID)
+ .setId(STATEFUL_INFERRED_WITH_BOTH_ID)
.setTitleResId(REFERENCE_RES_ID)
.setSummaryResId(REFERENCE_RES_ID)
.setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
@@ -248,17 +453,86 @@ class SafetySourcesGroupTest {
.addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
.build()
- internal val RIGID =
+ private val STATEFUL_BAREBONE: SafetySourcesGroup
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ get() =
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL)
+ .setId(STATEFUL_BAREBONE_ID)
+ .setTitleResId(REFERENCE_RES_ID)
+ .addSafetySource(SafetySourceTest.DYNAMIC_BAREBONE)
+ .build()
+
+ private val STATEFUL_ALL_OPTIONAL: SafetySourcesGroup
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ get() =
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL)
+ .setId(STATEFUL_ALL_OPTIONAL_ID)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ .addSafetySource(SafetySourceTest.DYNAMIC_BAREBONE)
+ .addSafetySource(SafetySourceTest.STATIC_BAREBONE)
+ .addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+ .build()
+
+ internal val STATELESS_INFERRED =
SafetySourcesGroup.Builder()
- .setId(RIGID_ID)
+ .setId(STATELESS_INFERRED_ID)
.setTitleResId(REFERENCE_RES_ID)
.addSafetySource(SafetySourceTest.STATIC_BAREBONE)
.build()
- internal val HIDDEN =
+ private val STATELESS_BAREBONE: SafetySourcesGroup
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ get() =
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS)
+ .setId(STATELESS_BAREBONE_ID)
+ .setTitleResId(REFERENCE_RES_ID)
+ .addSafetySource(SafetySourceTest.STATIC_BAREBONE)
+ .build()
+
+ private val STATELESS_ALL_OPTIONAL: SafetySourcesGroup
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ get() =
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS)
+ .setId(STATELESS_ALL_OPTIONAL_ID)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ .addSafetySource(SafetySourceTest.DYNAMIC_BAREBONE)
+ .addSafetySource(SafetySourceTest.STATIC_BAREBONE)
+ .addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+ .build()
+
+ internal val HIDDEN_INFERRED =
SafetySourcesGroup.Builder()
- .setId(HIDDEN_ID)
+ .setId(HIDDEN_INFERRED_ID)
.addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
.build()
+
+ private val HIDDEN_BAREBONE: SafetySourcesGroup
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ get() =
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ .setId(HIDDEN_BAREBONE_ID)
+ .addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+ .build()
+
+ private val HIDDEN_ALL_OPTIONAL: SafetySourcesGroup
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ get() =
+ SafetySourcesGroup.Builder()
+ .setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ .setId(HIDDEN_ALL_OPTIONAL_ID)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ .addSafetySource(SafetySourceTest.ISSUE_ONLY_BAREBONE)
+ .build()
}
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/Coroutines.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/Coroutines.kt
index 63b3658dc..93999ccee 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/Coroutines.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/Coroutines.kt
@@ -17,6 +17,9 @@
package android.safetycenter.cts.testing
import java.time.Duration
+import kotlinx.coroutines.DEBUG_PROPERTY_NAME
+import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_AUTO
+import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
@@ -62,4 +65,19 @@ object Coroutines {
/** A short timeout, to be used for actions that are expected not to complete. */
val TIMEOUT_SHORT: Duration = Duration.ofMillis(750)
+
+ private val debugMode = System.getProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_AUTO)
+
+ /**
+ * Enables debug mode for coroutines, in particular this enables stack traces in case of
+ * failures.
+ */
+ fun enableDebugging() {
+ System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)
+ }
+
+ /** Resets the debug mode to its original state. */
+ fun resetDebugging() {
+ System.setProperty(DEBUG_PROPERTY_NAME, debugMode)
+ }
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/NotificationCharacteristics.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/NotificationCharacteristics.kt
index 499156f09..39d03722c 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/NotificationCharacteristics.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/NotificationCharacteristics.kt
@@ -20,16 +20,28 @@ import android.app.Notification
import android.service.notification.StatusBarNotification
/** The characteristic properties of a notification. */
-data class NotificationCharacteristics(val title: String, val text: String) {
+data class NotificationCharacteristics(
+ val title: String?,
+ val text: String?,
+ val actions: List<CharSequence> = emptyList()
+) {
companion object {
+ /** Gets the [NotificationCharacteristics] from an actual [StatusBarNotification]. */
+ private fun StatusBarNotification.toCharacteristics(): NotificationCharacteristics? {
+ return this.notification?.run {
+ NotificationCharacteristics(
+ title = extras.getString(Notification.EXTRA_TITLE),
+ text = extras.getString(Notification.EXTRA_TEXT),
+ actions = actions.orEmpty().map { it.title }
+ )
+ }
+ }
+
private fun isMatch(
statusBarNotification: StatusBarNotification,
- characteristic: NotificationCharacteristics
+ characteristics: NotificationCharacteristics
): Boolean {
- val notif = statusBarNotification.notification
- return notif != null &&
- notif.extras.getString(Notification.EXTRA_TITLE) == characteristic.title &&
- notif.extras.getString(Notification.EXTRA_TEXT) == characteristic.text
+ return statusBarNotification.toCharacteristics() == characteristics
}
fun areMatching(
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterApisWithShellPermissions.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterApisWithShellPermissions.kt
index 0c5445b42..814d75758 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterApisWithShellPermissions.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterApisWithShellPermissions.kt
@@ -80,9 +80,16 @@ object SafetyCenterApisWithShellPermissions {
* Calls [SafetyCenterManager.refreshSafetySources] adopting Shell's [MANAGE_SAFETY_CENTER]
* permission.
*/
- fun SafetyCenterManager.refreshSafetySourcesWithPermission(refreshReason: Int) {
+ fun SafetyCenterManager.refreshSafetySourcesWithPermission(
+ refreshReason: Int,
+ safetySourceIds: List<String>? = null
+ ) {
callWithShellPermissionIdentity(MANAGE_SAFETY_CENTER) {
- refreshSafetySources(refreshReason)
+ if (safetySourceIds != null) {
+ refreshSafetySources(refreshReason, safetySourceIds)
+ } else {
+ refreshSafetySources(refreshReason)
+ }
}
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsConfigs.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsConfigs.kt
index 39d4110bf..187348d8a 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsConfigs.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsConfigs.kt
@@ -18,6 +18,7 @@ package android.safetycenter.cts.testing
import android.content.Context
import android.content.res.Resources
+import android.os.Build
import android.safetycenter.SafetySourceData
import android.safetycenter.config.SafetyCenterConfig
import android.safetycenter.config.SafetySource
@@ -26,6 +27,8 @@ import android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY
import android.safetycenter.config.SafetySource.SAFETY_SOURCE_TYPE_STATIC
import android.safetycenter.config.SafetySourcesGroup
import android.safetycenter.cts.testing.SettingsPackage.getSettingsPackageName
+import androidx.annotation.RequiresApi
+import com.android.modules.utils.build.SdkLevel
/**
* A class that provides [SafetyCenterConfig] objects and associated constants to facilitate setting
@@ -79,6 +82,47 @@ object SafetyCenterCtsConfigs {
)
/**
+ * SHA256 hash of a package certificate.
+ *
+ * <p>This is a fake certificate, and can be used to test failure cases, or to test a list of
+ * certificates when only one match is required.
+ */
+ const val PACKAGE_CERT_HASH_FAKE = "feed12"
+
+ /** SHA256 hashes of the certificate(s) known to sign the CTS tests. */
+ private val PACKAGE_CERT_HASHES_CTS =
+ listOf(
+ "6cecc50e34ae31bfb5678986d6d6d3736c571ded2f2459527793e1f054eb0c9b",
+ "a40da80a59d170caa950cf15c18c454d47a39b26989d8b640ecd745ba71bf5dc"
+ )
+
+ /** An invalid SHA256 hash (not a byte string, not even number of chars). */
+ const val PACKAGE_CERT_HASH_INVALID = "0124ppl"
+
+ /** A simple [SafetyCenterConfig] for CTS tests with a fake/incorrect package cert hash. */
+ val SINGLE_SOURCE_WITH_FAKE_CERT: SafetyCenterConfig
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ get() =
+ singleSourceConfig(
+ dynamicSafetySourceBuilder(SINGLE_SOURCE_ID)
+ .addPackageCertificateHash(PACKAGE_CERT_HASH_FAKE)
+ .build()
+ )
+
+ /**
+ * A simple [SafetyCenterConfig] for CTS tests with a invalid package cert hash (not a
+ * hex-formatted byte string).
+ */
+ val SINGLE_SOURCE_WITH_INVALID_CERT: SafetyCenterConfig
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ get() =
+ singleSourceConfig(
+ dynamicSafetySourceBuilder(SINGLE_SOURCE_ID)
+ .addPackageCertificateHash(PACKAGE_CERT_HASH_INVALID)
+ .build()
+ )
+
+ /**
* A simple [SafetyCenterConfig] for CTS tests with a source that does not support refresh on
* page open.
*/
@@ -129,7 +173,7 @@ object SafetyCenterCtsConfigs {
/**
* ID of a [SafetySourcesGroup] provided by [SUMMARY_TEST_GROUP_CONFIG], containing sources:
* [SOURCE_ID_1], [SOURCE_ID_2], [SOURCE_ID_3], [SOURCE_ID_4], [SOURCE_ID_5], [SOURCE_ID_6],
- * [SOURCE_ID_7], [STATIC_IN_COLLAPSIBLE_ID].
+ * [SOURCE_ID_7], [STATIC_IN_STATEFUL_ID].
*/
const val SUMMARY_TEST_GROUP_ID = "summary_test_group_id"
@@ -156,15 +200,16 @@ object SafetyCenterCtsConfigs {
/**
* ID of a [SafetySourcesGroup] provided by [COMPLEX_CONFIG], containing sources:
- * [DYNAMIC_IN_COLLAPSIBLE_ID], [STATIC_IN_COLLAPSIBLE_ID].
+ * [DYNAMIC_IN_STATEFUL_ID], [STATIC_IN_STATEFUL_ID].
*/
- const val MIXED_COLLAPSIBLE_GROUP_ID = "mixed_collapsible"
+ const val MIXED_STATEFUL_GROUP_ID = "mixed_stateful"
/**
* ID of a [SafetySourcesGroup] provided by [COMPLEX_CONFIG] and [COMPLEX_ALL_PROFILE_CONFIG],
- * containing sources: [DYNAMIC_IN_RIGID_ID], [STATIC_IN_RIGID_ID], [ISSUE_ONLY_IN_RIGID_ID].
+ * containing sources: [DYNAMIC_IN_STATELESS_ID], [STATIC_IN_STATELESS_ID],
+ * [ISSUE_ONLY_IN_STATELESS_ID].
*/
- const val MIXED_RIGID_GROUP_ID = "mixed_rigid"
+ const val MIXED_STATELESS_GROUP_ID = "mixed_stateless"
/**
* ID of a source provided by [COMPLEX_CONFIG], [COMPLEX_ALL_PROFILE_CONFIG], and
@@ -236,35 +281,39 @@ object SafetyCenterCtsConfigs {
* ID of a source provided by [COMPLEX_CONFIG], this is a generic, dynamic, primary profile
* only, visible source.
*/
- const val DYNAMIC_IN_COLLAPSIBLE_ID = "dynamic_in_collapsible"
+ const val DYNAMIC_IN_STATEFUL_ID = "dynamic_in_stateful"
/**
* ID of a source provided by [COMPLEX_CONFIG] and [COMPLEX_ALL_PROFILE_CONFIG], this is a
* generic, dynamic, visible source.
*/
- const val DYNAMIC_IN_RIGID_ID = "dynamic_in_rigid"
+ const val DYNAMIC_IN_STATELESS_ID = "dynamic_in_stateless"
/**
* ID of a source provided by [COMPLEX_CONFIG] and [COMPLEX_ALL_PROFILE_CONFIG], this is an
* issue-only source.
*/
- const val ISSUE_ONLY_IN_RIGID_ID = "issue_only_in_rigid"
+ const val ISSUE_ONLY_IN_STATELESS_ID = "issue_only_in_stateless"
/**
* ID of a source provided by [COMPLEX_CONFIG] and [SUMMARY_TEST_CONFIG], this is a generic,
* static, primary profile only source.
*/
- const val STATIC_IN_COLLAPSIBLE_ID = "static_in_collapsible"
+ const val STATIC_IN_STATEFUL_ID = "static_in_stateful"
/**
* ID of a source provided by [COMPLEX_CONFIG] and [COMPLEX_ALL_PROFILE_CONFIG], this is a
* generic, static source.
*/
- const val STATIC_IN_RIGID_ID = "static_in_rigid"
+ const val STATIC_IN_STATELESS_ID = "static_in_stateless"
/** Package name for the [DYNAMIC_OTHER_PACKAGE_ID] source. */
const val OTHER_PACKAGE_NAME = "other_package_name"
+ private const val DEDUPLICATION_GROUP_1 = "deduplication_group_1"
+ private const val DEDUPLICATION_GROUP_2 = "deduplication_group_2"
+ private const val DEDUPLICATION_GROUP_3 = "deduplication_group_3"
+
/** A Simple [SafetyCenterConfig] with an issue only source. */
val ISSUE_ONLY_SOURCE_CONFIG =
singleSourceConfig(issueOnlySafetySourceBuilder(ISSUE_ONLY_ALL_OPTIONAL_ID).build())
@@ -275,6 +324,22 @@ object SafetyCenterCtsConfigs {
issueOnlyAllProfileSafetySourceBuilder(ISSUE_ONLY_ALL_PROFILE_SOURCE_ID).build()
)
+ /**
+ * A Simple [SafetyCenterConfig] with an issue only source inside a [SafetySourcesGroup] with
+ * null title.
+ */
+ val ISSUE_ONLY_SOURCE_NO_GROUP_TITLE_CONFIG =
+ SafetyCenterConfig.Builder()
+ .addSafetySourcesGroup(
+ safetySourcesGroupBuilder(SINGLE_SOURCE_GROUP_ID)
+ .setTitleResId(Resources.ID_NULL)
+ .addSafetySource(
+ issueOnlySafetySourceBuilder(ISSUE_ONLY_ALL_OPTIONAL_ID).build()
+ )
+ .build()
+ )
+ .build()
+
/** A dynamic source with [OTHER_PACKAGE_NAME] */
val DYNAMIC_OTHER_PACKAGE_SAFETY_SOURCE =
dynamicSafetySourceBuilder(DYNAMIC_OTHER_PACKAGE_ID)
@@ -315,6 +380,69 @@ object SafetyCenterCtsConfigs {
)
.build()
+ /**
+ * A simple [SafetyCenterConfig] for CTS tests with multiple sources with deduplication info.
+ */
+ val multipleSourcesWithDeduplicationInfoConfig: SafetyCenterConfig
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ get() =
+ SafetyCenterConfig.Builder()
+ .addSafetySourcesGroup(
+ safetySourcesGroupBuilder(MULTIPLE_SOURCES_GROUP_ID_1)
+ .addSafetySource(
+ issueOnlySafetySourceWithDuplicationInfo(
+ SOURCE_ID_1,
+ DEDUPLICATION_GROUP_1
+ )
+ )
+ .addSafetySource(
+ issueOnlySafetySourceWithDuplicationInfo(
+ SOURCE_ID_2,
+ DEDUPLICATION_GROUP_1
+ )
+ )
+ .addSafetySource(
+ issueOnlySafetySourceWithDuplicationInfo(
+ SOURCE_ID_3,
+ DEDUPLICATION_GROUP_2
+ )
+ )
+ .addSafetySource(
+ issueOnlySafetySourceWithDuplicationInfo(
+ SOURCE_ID_4,
+ DEDUPLICATION_GROUP_3
+ )
+ )
+ .build()
+ )
+ .addSafetySourcesGroup(
+ safetySourcesGroupBuilder(MULTIPLE_SOURCES_GROUP_ID_2)
+ .addSafetySource(
+ issueOnlySafetySourceWithDuplicationInfo(
+ SOURCE_ID_5,
+ DEDUPLICATION_GROUP_1
+ )
+ )
+ .addSafetySource(
+ issueOnlySafetySourceWithDuplicationInfo(
+ SOURCE_ID_6,
+ DEDUPLICATION_GROUP_3
+ )
+ )
+ .build()
+ )
+ .addSafetySourcesGroup(
+ safetySourcesGroupBuilder(MULTIPLE_SOURCES_GROUP_ID_3)
+ .addSafetySource(
+ issueOnlySafetySourceWithDuplicationInfo(
+ SOURCE_ID_7,
+ DEDUPLICATION_GROUP_3
+ )
+ )
+ .build()
+ )
+ .build()
+
/** Source included in [DYNAMIC_SOURCE_GROUP_1]. */
val DYNAMIC_SOURCE_1 = dynamicSafetySource(SOURCE_ID_1)
@@ -466,7 +594,7 @@ object SafetyCenterCtsConfigs {
SafetyCenterConfig.Builder()
.addSafetySourcesGroup(
safetySourcesGroupBuilder(ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID)
- // This is needed to have a collapsible group with an empty summary
+ // This is needed to have a stateful group with an empty summary
.setSummaryResId(Resources.ID_NULL)
.setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
.addSafetySource(
@@ -504,7 +632,7 @@ object SafetyCenterCtsConfigs {
.addSafetySource(dynamicSafetySource(SOURCE_ID_5))
.addSafetySource(dynamicSafetySource(SOURCE_ID_6))
.addSafetySource(dynamicSafetySource(SOURCE_ID_7))
- .addSafetySource(staticSafetySource(STATIC_IN_COLLAPSIBLE_ID))
+ .addSafetySource(staticSafetySource(STATIC_IN_STATEFUL_ID))
.build()
)
.build()
@@ -528,6 +656,16 @@ object SafetyCenterCtsConfigs {
.setMaxSeverityLevel(SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION)
.setSearchTermsResId(android.R.string.ok)
.setLoggingAllowed(false)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup("group")
+ addPackageCertificateHash(PACKAGE_CERT_HASH_FAKE)
+ PACKAGE_CERT_HASHES_CTS.forEach {
+ addPackageCertificateHash(it)
+ }
+ }
+ }
.build()
)
.addSafetySource(
@@ -557,7 +695,14 @@ object SafetyCenterCtsConfigs {
)
.addSafetySourcesGroup(
safetySourcesGroupBuilder(STATIC_GROUP_ID)
- .setSummaryResId(Resources.ID_NULL)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS)
+ setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ } else {
+ setSummaryResId(Resources.ID_NULL)
+ }
+ }
.addSafetySource(
staticSafetySourceBuilder(STATIC_BAREBONE_ID)
.setSummaryResId(Resources.ID_NULL)
@@ -566,6 +711,7 @@ object SafetyCenterCtsConfigs {
.addSafetySource(
staticSafetySourceBuilder(STATIC_ALL_OPTIONAL_ID)
.setSearchTermsResId(android.R.string.ok)
+ .apply { if (SdkLevel.isAtLeastU()) setPackageName(CTS_PACKAGE_NAME) }
.build()
)
.build()
@@ -573,6 +719,14 @@ object SafetyCenterCtsConfigs {
.addSafetySourcesGroup(
SafetySourcesGroup.Builder()
.setId(ISSUE_ONLY_GROUP_ID)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setType(SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN)
+ setTitleResId(android.R.string.ok)
+ setSummaryResId(android.R.string.ok)
+ setStatelessIconType(SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY)
+ }
+ }
.addSafetySource(
issueOnlySafetySourceBuilder(ISSUE_ONLY_BAREBONE_ID)
.setRefreshOnPageOpenAllowed(false)
@@ -582,22 +736,32 @@ object SafetyCenterCtsConfigs {
issueOnlySafetySourceBuilder(ISSUE_ONLY_ALL_OPTIONAL_ID)
.setMaxSeverityLevel(SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION)
.setLoggingAllowed(false)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup("group")
+ addPackageCertificateHash(PACKAGE_CERT_HASH_FAKE)
+ PACKAGE_CERT_HASHES_CTS.forEach {
+ addPackageCertificateHash(it)
+ }
+ }
+ }
.build()
)
.build()
)
.addSafetySourcesGroup(
- safetySourcesGroupBuilder(MIXED_COLLAPSIBLE_GROUP_ID)
- .addSafetySource(dynamicSafetySource(DYNAMIC_IN_COLLAPSIBLE_ID))
- .addSafetySource(staticSafetySource(STATIC_IN_COLLAPSIBLE_ID))
+ safetySourcesGroupBuilder(MIXED_STATEFUL_GROUP_ID)
+ .addSafetySource(dynamicSafetySource(DYNAMIC_IN_STATEFUL_ID))
+ .addSafetySource(staticSafetySource(STATIC_IN_STATEFUL_ID))
.build()
)
.addSafetySourcesGroup(
- safetySourcesGroupBuilder(MIXED_RIGID_GROUP_ID)
+ safetySourcesGroupBuilder(MIXED_STATELESS_GROUP_ID)
.setSummaryResId(Resources.ID_NULL)
- .addSafetySource(dynamicSafetySource(DYNAMIC_IN_RIGID_ID))
- .addSafetySource(staticSafetySource(STATIC_IN_RIGID_ID))
- .addSafetySource(issueOnlySafetySource(ISSUE_ONLY_IN_RIGID_ID))
+ .addSafetySource(dynamicSafetySource(DYNAMIC_IN_STATELESS_ID))
+ .addSafetySource(staticSafetySource(STATIC_IN_STATELESS_ID))
+ .addSafetySource(issueOnlySafetySource(ISSUE_ONLY_IN_STATELESS_ID))
.build()
)
.build()
@@ -643,6 +807,7 @@ object SafetyCenterCtsConfigs {
.addSafetySource(
staticAllProfileSafetySourceBuilder(STATIC_ALL_OPTIONAL_ID)
.setSearchTermsResId(android.R.string.ok)
+ .apply { if (SdkLevel.isAtLeastU()) setPackageName(CTS_PACKAGE_NAME) }
.build()
)
.build()
@@ -659,21 +824,31 @@ object SafetyCenterCtsConfigs {
issueOnlyAllProfileSafetySourceBuilder(ISSUE_ONLY_ALL_OPTIONAL_ID)
.setMaxSeverityLevel(SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION)
.setLoggingAllowed(false)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setNotificationsAllowed(true)
+ setDeduplicationGroup("group")
+ addPackageCertificateHash(PACKAGE_CERT_HASH_FAKE)
+ PACKAGE_CERT_HASHES_CTS.forEach {
+ addPackageCertificateHash(it)
+ }
+ }
+ }
.build()
)
.build()
)
.addSafetySourcesGroup(
- safetySourcesGroupBuilder(MIXED_RIGID_GROUP_ID)
+ safetySourcesGroupBuilder(MIXED_STATELESS_GROUP_ID)
.setSummaryResId(Resources.ID_NULL)
.addSafetySource(
- dynamicAllProfileSafetySourceBuilder(DYNAMIC_IN_RIGID_ID).build()
+ dynamicAllProfileSafetySourceBuilder(DYNAMIC_IN_STATELESS_ID).build()
)
.addSafetySource(
- staticAllProfileSafetySourceBuilder(STATIC_IN_RIGID_ID).build()
+ staticAllProfileSafetySourceBuilder(STATIC_IN_STATELESS_ID).build()
)
.addSafetySource(
- issueOnlyAllProfileSafetySourceBuilder(ISSUE_ONLY_IN_RIGID_ID).build()
+ issueOnlyAllProfileSafetySourceBuilder(ISSUE_ONLY_IN_STATELESS_ID).build()
)
.build()
)
@@ -705,7 +880,7 @@ object SafetyCenterCtsConfigs {
private fun dynamicSafetySource(id: String) = dynamicSafetySourceBuilder(id).build()
- private fun dynamicSafetySourceBuilder(id: String) =
+ fun dynamicSafetySourceBuilder(id: String) =
SafetySource.Builder(SAFETY_SOURCE_TYPE_DYNAMIC)
.setId(id)
.setPackageName(CTS_PACKAGE_NAME)
@@ -735,6 +910,10 @@ object SafetyCenterCtsConfigs {
.setProfile(SafetySource.PROFILE_ALL)
.setTitleForWorkResId(android.R.string.paste)
+ @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private fun issueOnlySafetySourceWithDuplicationInfo(id: String, deduplicationGroup: String) =
+ issueOnlySafetySourceBuilder(id).setDeduplicationGroup(deduplicationGroup).build()
+
private fun issueOnlySafetySource(id: String) = issueOnlySafetySourceBuilder(id).build()
private fun issueOnlySafetySourceBuilder(id: String) =
@@ -753,7 +932,7 @@ object SafetyCenterCtsConfigs {
.setTitleResId(android.R.string.ok)
.setSummaryResId(android.R.string.ok)
- private fun singleSourceConfig(safetySource: SafetySource) =
+ fun singleSourceConfig(safetySource: SafetySource) =
SafetyCenterConfig.Builder()
.addSafetySourcesGroup(
safetySourcesGroupBuilder(SINGLE_SOURCE_GROUP_ID)
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsData.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsData.kt
index 9cb1fc961..78729adbd 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsData.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsData.kt
@@ -19,6 +19,8 @@ package android.safetycenter.cts.testing
import android.app.PendingIntent
import android.content.Context
import android.icu.text.MessageFormat
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.os.Bundle
import android.os.UserHandle
import android.safetycenter.SafetyCenterData
import android.safetycenter.SafetyCenterEntry
@@ -36,6 +38,7 @@ import android.safetycenter.SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATIO
import android.safetycenter.SafetyCenterStatus
import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING
import android.safetycenter.SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_GROUP_ID
import android.safetycenter.cts.testing.SafetySourceCtsData.Companion.CRITICAL_ISSUE_ACTION_ID
import android.safetycenter.cts.testing.SafetySourceCtsData.Companion.CRITICAL_ISSUE_ID
import android.safetycenter.cts.testing.SafetySourceCtsData.Companion.INFORMATION_ISSUE_ACTION_ID
@@ -44,6 +47,8 @@ import android.safetycenter.cts.testing.SafetySourceCtsData.Companion.ISSUE_TYPE
import android.safetycenter.cts.testing.SafetySourceCtsData.Companion.RECOMMENDATION_ISSUE_ACTION_ID
import android.safetycenter.cts.testing.SafetySourceCtsData.Companion.RECOMMENDATION_ISSUE_ID
import android.util.ArrayMap
+import androidx.annotation.RequiresApi
+import com.android.modules.utils.build.SdkLevel
import com.android.safetycenter.internaldata.SafetyCenterEntryId
import com.android.safetycenter.internaldata.SafetyCenterIds
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId
@@ -77,6 +82,21 @@ class SafetyCenterCtsData(context: Context) {
.build()
/**
+ * Returns a [SafetyCenterStatus] with one alert and the given [statusResource] and
+ * [overallSeverityLevel].
+ */
+ fun safetyCenterStatusOneAlert(
+ statusResource: String,
+ overallSeverityLevel: Int
+ ): SafetyCenterStatus =
+ SafetyCenterStatus.Builder(
+ safetyCenterResourcesContext.getStringByName(statusResource),
+ getAlertString(1)
+ )
+ .setSeverityLevel(overallSeverityLevel)
+ .build()
+
+ /**
* Returns the [SafetyCenterStatus] used when the overall status is critical and no scan is in
* progress for the given number of alerts.
*/
@@ -217,7 +237,12 @@ class SafetyCenterCtsData(context: Context) {
* Returns an information [SafetyCenterIssue] for the given source and user id that is
* consistent with information [SafetySourceIssue]s used in [SafetySourceCtsData].
*/
- fun safetyCenterIssueInformation(sourceId: String, userId: Int = UserHandle.myUserId()) =
+ fun safetyCenterIssueInformation(
+ sourceId: String,
+ userId: Int = UserHandle.myUserId(),
+ attributionTitle: String? = "OK",
+ groupId: String? = SINGLE_SOURCE_GROUP_ID
+ ) =
SafetyCenterIssue.Builder(
issueId(sourceId, INFORMATION_ISSUE_ID, userId = userId),
"Information issue title",
@@ -240,13 +265,24 @@ class SafetyCenterCtsData(context: Context) {
.build()
)
)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setAttributionTitle(attributionTitle)
+ setGroupId(groupId)
+ }
+ }
.build()
/**
* Returns a recommendation [SafetyCenterIssue] for the given source and user id that is
* consistent with recommendation [SafetySourceIssue]s used in [SafetySourceCtsData].
*/
- fun safetyCenterIssueRecommendation(sourceId: String, userId: Int = UserHandle.myUserId()) =
+ fun safetyCenterIssueRecommendation(
+ sourceId: String,
+ userId: Int = UserHandle.myUserId(),
+ attributionTitle: String? = "OK",
+ groupId: String? = SINGLE_SOURCE_GROUP_ID
+ ) =
SafetyCenterIssue.Builder(
issueId(sourceId, RECOMMENDATION_ISSUE_ID, userId = userId),
"Recommendation issue title",
@@ -268,6 +304,12 @@ class SafetyCenterCtsData(context: Context) {
.build()
)
)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setAttributionTitle(attributionTitle)
+ setGroupId(groupId)
+ }
+ }
.build()
/**
@@ -277,7 +319,9 @@ class SafetyCenterCtsData(context: Context) {
fun safetyCenterIssueCritical(
sourceId: String,
isActionInFlight: Boolean = false,
- userId: Int = UserHandle.myUserId()
+ userId: Int = UserHandle.myUserId(),
+ attributionTitle: String? = "OK",
+ groupId: String? = SINGLE_SOURCE_GROUP_ID
) =
SafetyCenterIssue.Builder(
issueId(sourceId, CRITICAL_ISSUE_ID, userId = userId),
@@ -302,6 +346,12 @@ class SafetyCenterCtsData(context: Context) {
.build()
)
)
+ .apply {
+ if (SdkLevel.isAtLeastU()) {
+ setAttributionTitle(attributionTitle)
+ setGroupId(groupId)
+ }
+ }
.build()
/**
@@ -386,5 +436,57 @@ class SafetyCenterCtsData(context: Context) {
.setSafetySourceIssueActionId(sourceIssueActionId)
.build()
)
+
+ /**
+ * On U+, returns a new [SafetyCenterData] with the dismissed issues set. Prior to U,
+ * returns the passed in [SafetyCenterData].
+ */
+ fun SafetyCenterData.withDismissedIssuesIfAtLeastU(
+ dismissedIssues: List<SafetyCenterIssue>
+ ): SafetyCenterData =
+ if (SdkLevel.isAtLeastU()) {
+ copy(dismissedIssues = dismissedIssues)
+ } else this
+
+ /**
+ * On U+, returns a new [SafetyCenterData] with [SafetyCenterIssue]s having the
+ * [attributionTitle]. Prior to U, returns the passed in [SafetyCenterData].
+ */
+ fun SafetyCenterData.withAttributionTitleInIssuesIfAtLeastU(
+ attributionTitle: String?
+ ): SafetyCenterData {
+ return if (SdkLevel.isAtLeastU()) {
+ val issuesWithAttributionTitle =
+ this.issues.map {
+ SafetyCenterIssue.Builder(it).setAttributionTitle(attributionTitle).build()
+ }
+ copy(issues = issuesWithAttributionTitle)
+ } else this
+ }
+
+ /**
+ * On U+, returns a new [SafetyCenterData] with the extras set. Prior to U, returns the
+ * passed in [SafetyCenterData].
+ */
+ fun SafetyCenterData.withExtrasIfAtLeastU(extras: Bundle): SafetyCenterData =
+ if (SdkLevel.isAtLeastU()) {
+ copy(extras = extras)
+ } else this
+
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ private fun SafetyCenterData.copy(
+ issues: List<SafetyCenterIssue> = this.issues,
+ dismissedIssues: List<SafetyCenterIssue> = this.dismissedIssues,
+ extras: Bundle = this.extras
+ ): SafetyCenterData =
+ SafetyCenterData.Builder(status)
+ .apply {
+ issues.forEach(::addIssue)
+ entriesOrGroups.forEach(::addEntryOrGroup)
+ staticEntryGroups.forEach(::addStaticEntryGroup)
+ dismissedIssues.forEach(::addDismissedIssue)
+ }
+ .setExtras(extras)
+ .build()
}
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsHelper.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsHelper.kt
index 58f1127a7..e998c39fb 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsHelper.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsHelper.kt
@@ -48,6 +48,7 @@ class SafetyCenterCtsHelper(private val context: Context) {
* values. To be called before each test.
*/
fun setup() {
+ Coroutines.enableDebugging()
SafetyCenterFlags.setup()
setEnabled(true)
}
@@ -64,6 +65,7 @@ class SafetyCenterCtsHelper(private val context: Context) {
safetyCenterManager.clearSafetyCenterConfigForTestsWithPermission()
resetFlags()
SafetySourceReceiver.reset()
+ Coroutines.resetDebugging()
}
/** Enables or disables SafetyCenter based on [value]. */
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterEnabledChangedReceiver.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterEnabledChangedReceiver.kt
index 0af6c8c0f..8d9331718 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterEnabledChangedReceiver.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterEnabledChangedReceiver.kt
@@ -24,7 +24,7 @@ import android.content.IntentFilter
import android.safetycenter.SafetyCenterManager.ACTION_SAFETY_CENTER_ENABLED_CHANGED
import android.safetycenter.cts.testing.Coroutines.TIMEOUT_LONG
import android.safetycenter.cts.testing.Coroutines.runBlockingWithTimeout
-import android.safetycenter.cts.testing.WaitForBroadcastIdle.waitForBroadcastIdle
+import com.android.compatibility.common.util.SystemUtil
import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
import java.time.Duration
@@ -63,7 +63,7 @@ class SafetyCenterEnabledChangedReceiver(private val context: Context) : Broadca
): Boolean {
SafetyCenterFlags.isEnabled = value
if (timeout < TIMEOUT_LONG) {
- context.waitForBroadcastIdle()
+ SystemUtil.waitForBroadcasts()
}
return receiveSafetyCenterEnabledChanged(timeout)
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterFlags.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterFlags.kt
index 38419764e..bd9af123e 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterFlags.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterFlags.kt
@@ -18,9 +18,12 @@ package android.safetycenter.cts.testing
import android.Manifest.permission.READ_DEVICE_CONFIG
import android.Manifest.permission.WRITE_DEVICE_CONFIG
+import android.annotation.TargetApi
+import android.app.job.JobInfo
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.provider.DeviceConfig
import android.provider.DeviceConfig.NAMESPACE_PRIVACY
import android.provider.DeviceConfig.Properties
@@ -28,6 +31,7 @@ import android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_LOCALE_CHA
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_DEVICE_REBOOT
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_OTHER
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN
+import android.safetycenter.SafetyCenterManager.REFRESH_REASON_PERIODIC
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED
import android.safetycenter.SafetySourceData
@@ -187,6 +191,54 @@ object SafetyCenterFlags {
IntParser()
)
+ /**
+ * Flag that determines whether to show subpages in the Safety Center UI instead of the
+ * expand-and-collapse list.
+ */
+ private val showSubpagesFlag =
+ Flag("safety_center_show_subpages", defaultValue = false, BooleanParser())
+
+ private val overrideRefreshOnPageOpenSourcesFlag =
+ Flag(
+ "safety_center_override_refresh_on_page_open_sources",
+ defaultValue = setOf(),
+ SetParser(StringParser())
+ )
+
+ /**
+ * Flag that enables both one-off and periodic background refreshes in
+ * [SafetyCenterBackgroundRefreshJobService].
+ */
+ private val backgroundRefreshIsEnabledFlag =
+ Flag(
+ "safety_center_background_refresh_is_enabled",
+ // do not set defaultValue to true, do not want background refreshes running
+ // during other tests
+ defaultValue = false,
+ BooleanParser()
+ )
+
+ /**
+ * Flag that determines how often periodic background refreshes are run in
+ * [SafetyCenterBackgroundRefreshJobService]. See [JobInfo.setPeriodic] for details.
+ *
+ * Note that jobs may take longer than this to be scheduled, or may possibly never run,
+ * depending on whether the other constraints on the job get satisfied.
+ */
+ private val periodicBackgroundRefreshIntervalFlag =
+ Flag(
+ "safety_center_periodic_background_interval_millis",
+ defaultValue = Duration.ofDays(1),
+ DurationParser()
+ )
+
+ /**
+ * Flag that determines whether background refreshes require charging in
+ * [SafetyCenterBackgroundRefreshJobService]. See [JobInfo.setRequiresCharging] for details.
+ */
+ private val backgroundRefreshRequiresChargingFlag =
+ Flag("safety_center_background_requires_charging", defaultValue = false, BooleanParser())
+
/** Every Safety Center flag. */
private val FLAGS: List<Flag<*>> =
listOf(
@@ -204,7 +256,12 @@ object SafetyCenterFlags {
issueCategoryAllowlistsFlag,
backgroundRefreshDeniedSourcesFlag,
allowStatsdLoggingInTestsFlag,
- qsTileComponentSettingFlag
+ qsTileComponentSettingFlag,
+ showSubpagesFlag,
+ overrideRefreshOnPageOpenSourcesFlag,
+ backgroundRefreshIsEnabledFlag,
+ periodicBackgroundRefreshIntervalFlag,
+ backgroundRefreshRequiresChargingFlag
)
/** Returns whether the device supports Safety Center. */
@@ -255,6 +312,21 @@ object SafetyCenterFlags {
/** A property that allows getting and setting the [allowStatsdLoggingInTestsFlag]. */
var allowStatsdLoggingInTests: Boolean by allowStatsdLoggingInTestsFlag
+ /** A property that allows getting and setting the [showSubpagesFlag]. */
+ var showSubpages: Boolean by showSubpagesFlag
+
+ /** A property that allows getting and setting the [overrideRefreshOnPageOpenSourcesFlag]. */
+ var overrideRefreshOnPageOpenSources: Set<String> by overrideRefreshOnPageOpenSourcesFlag
+
+ /** A property that allows getting and settings the [backgroundRefreshIsEnabledFlag]. */
+ var backgroundRefreshIsEnabled: Boolean by backgroundRefreshIsEnabledFlag
+
+ /** A property that allows getting and settings the [periodicBackgroundRefreshIntervalFlag]. */
+ var periodicBackgroundRefreshInterval: Duration by periodicBackgroundRefreshIntervalFlag
+
+ /** A property that allows getting and settings the [backgroundRefreshRequiresChargingFlag]. */
+ var backgroundRefreshRequiresCharging: Boolean by backgroundRefreshRequiresChargingFlag
+
/**
* Returns a snapshot of all the Safety Center flags.
*
@@ -310,14 +382,16 @@ object SafetyCenterFlags {
fun Properties.isSafetyCenterEnabled() =
getBoolean(isEnabledFlag.name, /* defaultValue */ false)
- private fun getAllRefreshTimeoutsMap(refreshTimeout: Duration) =
+ @TargetApi(UPSIDE_DOWN_CAKE)
+ private fun getAllRefreshTimeoutsMap(refreshTimeout: Duration): Map<Int, Duration> =
mapOf(
REFRESH_REASON_PAGE_OPEN to refreshTimeout,
REFRESH_REASON_RESCAN_BUTTON_CLICK to refreshTimeout,
REFRESH_REASON_DEVICE_REBOOT to refreshTimeout,
REFRESH_REASON_DEVICE_LOCALE_CHANGE to refreshTimeout,
REFRESH_REASON_SAFETY_CENTER_ENABLED to refreshTimeout,
- REFRESH_REASON_OTHER to refreshTimeout
+ REFRESH_REASON_OTHER to refreshTimeout,
+ REFRESH_REASON_PERIODIC to refreshTimeout
)
private interface Parser<T> {
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceCtsData.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceCtsData.kt
index 0ff04ca8d..eedc84820 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceCtsData.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceCtsData.kt
@@ -20,6 +20,8 @@ import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_RECEIVER_FOREGROUND
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.safetycenter.SafetyEvent
import android.safetycenter.SafetySourceData
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
@@ -30,6 +32,7 @@ import android.safetycenter.SafetySourceIssue
import android.safetycenter.SafetySourceIssue.Action
import android.safetycenter.SafetySourceStatus
import android.safetycenter.SafetySourceStatus.IconAction
+import android.safetycenter.SafetySourceStatus.IconAction.ICON_TYPE_GEAR
import android.safetycenter.SafetySourceStatus.IconAction.ICON_TYPE_INFO
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ACTION_TEST_ACTIVITY
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_ID
@@ -38,6 +41,8 @@ import android.safetycenter.cts.testing.SafetySourceIntentHandler.Companion.ACTI
import android.safetycenter.cts.testing.SafetySourceIntentHandler.Companion.EXTRA_SOURCE_ID
import android.safetycenter.cts.testing.SafetySourceIntentHandler.Companion.EXTRA_SOURCE_ISSUE_ACTION_ID
import android.safetycenter.cts.testing.SafetySourceIntentHandler.Companion.EXTRA_SOURCE_ISSUE_ID
+import androidx.annotation.RequiresApi
+import java.lang.IllegalStateException
import kotlin.math.max
/**
@@ -50,6 +55,13 @@ class SafetySourceCtsData(private val context: Context) {
val testActivityRedirectPendingIntent =
createRedirectPendingIntent(context, Intent(ACTION_TEST_ACTIVITY))
+ /**
+ * A [PendingIntent] that redirects to the [TestActivity] page, the [Intent] is constructed with
+ * the given [identifier].
+ */
+ fun testActivityRedirectPendingIntent(identifier: String? = null) =
+ createRedirectPendingIntent(context, Intent(ACTION_TEST_ACTIVITY).setIdentifier(identifier))
+
/** A [SafetySourceData] with a [SEVERITY_LEVEL_UNSPECIFIED] [SafetySourceStatus]. */
val unspecified =
SafetySourceData.Builder()
@@ -83,7 +95,12 @@ class SafetySourceCtsData(private val context: Context) {
.build()
/** A [SafetySourceIssue] with a [SEVERITY_LEVEL_INFORMATION] and a redirecting [Action]. */
- val informationIssue =
+ val informationIssue = defaultInformationIssueBuilder().build()
+
+ /**
+ * A [SafetySourceIssue.Builder] with a [SEVERITY_LEVEL_INFORMATION] and a redirecting [Action].
+ */
+ private fun defaultInformationIssueBuilder() =
SafetySourceIssue.Builder(
INFORMATION_ISSUE_ID,
"Information issue title",
@@ -99,7 +116,6 @@ class SafetySourceCtsData(private val context: Context) {
)
.build()
)
- .build()
/**
* A [SafetySourceIssue] with a [SEVERITY_LEVEL_INFORMATION] and a redirecting [Action]. With
@@ -198,6 +214,20 @@ class SafetySourceCtsData(private val context: Context) {
.build()
/**
+ * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] [SafetySourceStatus] and an
+ * [IconAction] having a [ICON_TYPE_GEAR].
+ */
+ val informationWithGearIconAction =
+ SafetySourceData.Builder()
+ .setStatus(
+ SafetySourceStatus.Builder("Ok title", "Ok summary", SEVERITY_LEVEL_INFORMATION)
+ .setPendingIntent(testActivityRedirectPendingIntent)
+ .setIconAction(IconAction(ICON_TYPE_GEAR, testActivityRedirectPendingIntent))
+ .build()
+ )
+ .build()
+
+ /**
* A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
* [SafetySourceStatus].
*/
@@ -212,6 +242,26 @@ class SafetySourceCtsData(private val context: Context) {
.build()
/**
+ * A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting a [SafetySourceIssue]
+ * having a [SafetySourceIssue.attributionTitle] and [SafetySourceStatus].
+ */
+ val informationWithIssueWithAttributionTitle: SafetySourceData
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ get() =
+ SafetySourceData.Builder()
+ .setStatus(
+ SafetySourceStatus.Builder("Ok title", "Ok summary", SEVERITY_LEVEL_INFORMATION)
+ .setPendingIntent(testActivityRedirectPendingIntent)
+ .build()
+ )
+ .addIssue(
+ defaultInformationIssueBuilder()
+ .setAttributionTitle("Attribution Title")
+ .build()
+ )
+ .build()
+
+ /**
* A [SafetySourceData] with a [SEVERITY_LEVEL_INFORMATION] redirecting [SafetySourceIssue] and
* [SafetySourceStatus], to be used for a managed profile entry.
*/
@@ -274,6 +324,14 @@ class SafetySourceCtsData(private val context: Context) {
val recommendationGeneralIssue = defaultRecommendationIssueBuilder().build()
/**
+ * A [SafetySourceIssue] with a [SEVERITY_LEVEL_RECOMMENDATION], general category, redirecting
+ * [Action] and with deduplication id.
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ fun recommendationIssueWithDeduplicationId(deduplicationId: String) =
+ defaultRecommendationIssueBuilder().setDeduplicationId(deduplicationId).build()
+
+ /**
* A [SafetySourceIssue] with a [SEVERITY_LEVEL_RECOMMENDATION], account category and a
* redirecting [Action].
*/
@@ -439,6 +497,14 @@ class SafetySourceCtsData(private val context: Context) {
val criticalResolvingGeneralIssue = defaultCriticalResolvingIssueBuilder().build()
/**
+ * General [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and with deduplication
+ * info and a resolving [Action].
+ */
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ fun criticalIssueWithDeduplicationId(deduplicationId: String) =
+ defaultCriticalResolvingIssueBuilder().setDeduplicationId(deduplicationId).build()
+
+ /**
* Account related [SafetySourceIssue] with a [SEVERITY_LEVEL_CRITICAL_WARNING] and a resolving
* [Action].
*/
@@ -470,6 +536,23 @@ class SafetySourceCtsData(private val context: Context) {
)
/**
+ * A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] having a resolving
+ * [SafetySourceIssue] with a [SafetySourceIssue.attributionTitle] and success message.
+ */
+ val criticalWithIssueWithAttributionTitle: SafetySourceData
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ get() =
+ defaultCriticalDataBuilder()
+ .addIssue(
+ defaultCriticalResolvingIssueBuilder()
+ .setAttributionTitle("Attribution Title")
+ .clearActions()
+ .addAction(criticalResolvingActionWithSuccessMessage)
+ .build()
+ )
+ .build()
+
+ /**
* A [SafetySourceData] with a [SEVERITY_LEVEL_CRITICAL_WARNING] resolving general
* [SafetySourceIssue] and [SafetySourceStatus].
*/
@@ -632,12 +715,27 @@ class SafetySourceCtsData(private val context: Context) {
}
/** Returns a [PendingIntent] that redirects to [intent]. */
- fun createRedirectPendingIntent(context: Context, intent: Intent): PendingIntent =
- PendingIntent.getActivity(
+ fun createRedirectPendingIntent(context: Context, intent: Intent): PendingIntent {
+ val explicitIntent = Intent(intent).setPackage(context.packageName)
+ val redirectIntent =
+ if (intentResolves(context, intent)) {
+ intent
+ } else if (intentResolves(context, explicitIntent)) {
+ explicitIntent
+ } else {
+ throw IllegalStateException("Intent doesn't resolve")
+ }
+ return PendingIntent.getActivity(
context,
0 /* requestCode */,
- intent,
+ redirectIntent,
PendingIntent.FLAG_IMMUTABLE
)
+ }
+
+ private fun intentResolves(context: Context, intent: Intent): Boolean =
+ context.packageManager
+ .queryIntentActivities(intent, ResolveInfoFlags.of(0))
+ .isNotEmpty()
}
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceReceiver.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceReceiver.kt
index 2fcefb236..8533f62df 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceReceiver.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetySourceReceiver.kt
@@ -36,8 +36,8 @@ import android.safetycenter.cts.testing.SafetyCenterApisWithShellPermissions.exe
import android.safetycenter.cts.testing.SafetyCenterApisWithShellPermissions.refreshSafetySourcesWithPermission
import android.safetycenter.cts.testing.SafetySourceIntentHandler.Request
import android.safetycenter.cts.testing.SafetySourceIntentHandler.Response
-import android.safetycenter.cts.testing.WaitForBroadcastIdle.waitForBroadcastIdle
import androidx.test.core.app.ApplicationProvider
+import com.android.compatibility.common.util.SystemUtil
import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity
import java.time.Duration
import kotlinx.coroutines.CoroutineScope
@@ -145,19 +145,25 @@ class SafetySourceReceiver : BroadcastReceiver() {
fun SafetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(
refreshReason: Int,
- timeout: Duration = TIMEOUT_LONG
+ timeout: Duration = TIMEOUT_LONG,
+ safetySourceIds: List<String>? = null
) =
callWithShellPermissionIdentity(SEND_SAFETY_CENTER_UPDATE) {
- refreshSafetySourcesWithoutReceiverPermissionAndWait(refreshReason, timeout)
+ refreshSafetySourcesWithoutReceiverPermissionAndWait(
+ refreshReason,
+ timeout,
+ safetySourceIds
+ )
}
fun SafetyCenterManager.refreshSafetySourcesWithoutReceiverPermissionAndWait(
refreshReason: Int,
- timeout: Duration
+ timeout: Duration,
+ safetySourceIds: List<String>? = null
): String {
- refreshSafetySourcesWithPermission(refreshReason)
+ refreshSafetySourcesWithPermission(refreshReason, safetySourceIds)
if (timeout < TIMEOUT_LONG) {
- getApplicationContext().waitForBroadcastIdle()
+ SystemUtil.waitForBroadcasts()
}
return receiveRefreshSafetySources(timeout)
}
@@ -176,7 +182,7 @@ class SafetySourceReceiver : BroadcastReceiver() {
): Boolean {
SafetyCenterFlags.isEnabled = value
if (timeout < TIMEOUT_LONG) {
- getApplicationContext().waitForBroadcastIdle()
+ SystemUtil.waitForBroadcasts()
}
return receiveSafetyCenterEnabledChanged(timeout)
}
@@ -216,7 +222,8 @@ class SafetySourceReceiver : BroadcastReceiver() {
safetySourceIntentHandler.receiveSafetyCenterEnabledChanged()
}
- private fun receiveResolveAction(timeout: Duration = TIMEOUT_LONG) {
+ /** Waits for this receiver to resolve an action within the given [timeout]. */
+ fun receiveResolveAction(timeout: Duration = TIMEOUT_LONG) {
runBlockingWithTimeout(timeout) { safetySourceIntentHandler.receiveResolveAction() }
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/UiTestHelper.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/UiTestHelper.kt
index df6dad0c8..8032d20d1 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/UiTestHelper.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/UiTestHelper.kt
@@ -22,6 +22,7 @@ import android.safetycenter.SafetySourceIssue
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
import androidx.test.uiautomator.StaleObjectException
+import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import android.util.Log
import com.android.compatibility.common.util.SystemUtil.runShellCommand
@@ -131,6 +132,26 @@ object UiTestHelper {
runShellCommand("settings put global animator_duration_scale $scale")
}
+ internal fun UiDevice.rotate() {
+ unfreezeRotation()
+ if (isNaturalOrientation) {
+ setOrientationLeft()
+ } else {
+ setOrientationNatural()
+ }
+ freezeRotation()
+ waitForIdle()
+ }
+
+ internal fun UiDevice.resetRotation() {
+ if (!isNaturalOrientation) {
+ unfreezeRotation()
+ setOrientationNatural()
+ freezeRotation()
+ waitForIdle()
+ }
+ }
+
private fun buttonSelector(label: CharSequence): BySelector {
return By.clickable(true).text(Pattern.compile("$label|${label.toString().uppercase()}"))
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt
index df1561e17..d8b199fdc 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterActivityTest.kt
@@ -17,6 +17,9 @@
package android.safetycenter.cts.ui
import android.content.Context
+import android.os.Build.VERSION.CODENAME
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.Bundle
import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ID
import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID
@@ -32,6 +35,8 @@ import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_SOURCE_3
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_SOURCE_GROUP_1
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_SOURCE_GROUP_2
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.DYNAMIC_SOURCE_GROUP_3
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_ALL_OPTIONAL_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.ISSUE_ONLY_SOURCE_NO_GROUP_TITLE_CONFIG
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MULTIPLE_SOURCES_CONFIG
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MULTIPLE_SOURCES_CONFIG_WITH_SOURCE_WITH_INVALID_INTENT
import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MULTIPLE_SOURCE_GROUPS_CONFIG
@@ -55,6 +60,8 @@ import android.safetycenter.cts.testing.SafetySourceReceiver
import android.safetycenter.cts.testing.SettingsPackage.getSettingsPackageName
import android.safetycenter.cts.testing.UiTestHelper.RESCAN_BUTTON_LABEL
import android.safetycenter.cts.testing.UiTestHelper.expandMoreIssuesCard
+import android.safetycenter.cts.testing.UiTestHelper.resetRotation
+import android.safetycenter.cts.testing.UiTestHelper.rotate
import android.safetycenter.cts.testing.UiTestHelper.setAnimationsEnabled
import android.safetycenter.cts.testing.UiTestHelper.waitAllTextDisplayed
import android.safetycenter.cts.testing.UiTestHelper.waitAllTextNotDisplayed
@@ -64,16 +71,17 @@ import android.safetycenter.cts.testing.UiTestHelper.waitNotDisplayed
import android.safetycenter.cts.testing.UiTestHelper.waitSourceDataDisplayed
import android.safetycenter.cts.testing.UiTestHelper.waitSourceIssueDisplayed
import android.safetycenter.cts.testing.UiTestHelper.waitSourceIssueNotDisplayed
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
import com.android.compatibility.common.util.DisableAnimationRule
import com.android.compatibility.common.util.FreezeRotationRule
import com.android.compatibility.common.util.UiAutomatorUtils2.getUiDevice
import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity
import java.time.Duration
import org.junit.After
+import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Before
import org.junit.Rule
@@ -801,6 +809,52 @@ class SafetyCenterActivityTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun issueCard_withAttributionTitleSetBySource_displaysAttributionTitle() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+
+ val data = safetySourceCtsData.informationWithIssueWithAttributionTitle
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data)
+
+ context.launchSafetyCenterActivity { waitAllTextDisplayed("Attribution Title") }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun issueCard_attributionNotSetBySource_displaysGroupTitleAsAttribution() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+
+ val data = safetySourceCtsData.recommendationWithGeneralIssue
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data)
+
+ context.launchSafetyCenterActivity { waitAllTextDisplayed("OK") }
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ fun issueCard_attributionNotSetBySourceAndGroupTitleNull_doesNotDisplayAttributionTitle() {
+ safetyCenterCtsHelper.setConfig(ISSUE_ONLY_SOURCE_NO_GROUP_TITLE_CONFIG)
+
+ val data = SafetySourceCtsData.issuesOnly(safetySourceCtsData.recommendationGeneralIssue)
+ safetyCenterCtsHelper.setData(ISSUE_ONLY_ALL_OPTIONAL_ID, data)
+
+ context.launchSafetyCenterActivity { waitAllTextNotDisplayed("Attribution Title", "OK") }
+ }
+
+ @Test
+ @SdkSuppress(maxSdkVersion = TIRAMISU)
+ fun issueCard_attributionNotSetBySourceOnTiramisu_doesNotDisplayAttributionTitle() {
+ // TODO(b/258228790): Remove after U is no longer in pre-release
+ assumeFalse(CODENAME == "UpsideDownCake")
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+
+ val data = safetySourceCtsData.recommendationWithGeneralIssue
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, data)
+
+ context.launchSafetyCenterActivity { waitAllTextNotDisplayed("Attribution title", "OK") }
+ }
+
+ @Test
fun launchActivity_fromQuickSettings_issuesExpanded() {
safetyCenterCtsHelper.setConfig(MULTIPLE_SOURCES_CONFIG)
safetyCenterCtsHelper.setData(
@@ -1275,25 +1329,5 @@ class SafetyCenterActivityTest {
private const val SAFETY_SOURCE_4_SUMMARY = "Safety Source 4 Summary"
private const val SAFETY_SOURCE_5_TITLE = "Safety Source 5 Title"
private const val SAFETY_SOURCE_5_SUMMARY = "Safety Source 5 Summary"
-
- private fun UiDevice.rotate() {
- unfreezeRotation()
- if (isNaturalOrientation) {
- setOrientationLeft()
- } else {
- setOrientationNatural()
- }
- freezeRotation()
- waitForIdle()
- }
-
- private fun UiDevice.resetRotation() {
- if (!isNaturalOrientation) {
- unfreezeRotation()
- setOrientationNatural()
- freezeRotation()
- waitForIdle()
- }
- }
}
}
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterSubpagesTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterSubpagesTest.kt
index e69de29bb..b7b84fd30 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterSubpagesTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/ui/SafetyCenterSubpagesTest.kt
@@ -0,0 +1,602 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts.ui
+
+import android.content.Context
+import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
+import android.os.Bundle
+import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCES_GROUP_ID
+import android.safetycenter.SafetySourceData
+import android.safetycenter.config.SafetySource
+import android.safetycenter.config.SafetySourcesGroup
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MULTIPLE_SOURCES_CONFIG
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MULTIPLE_SOURCES_GROUP_ID_1
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.MULTIPLE_SOURCE_GROUPS_CONFIG
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_CONFIG
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_ID
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SINGLE_SOURCE_INVALID_INTENT_CONFIG
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_1
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_2
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_3
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_4
+import android.safetycenter.cts.testing.SafetyCenterCtsConfigs.SOURCE_ID_5
+import android.safetycenter.cts.testing.SafetyCenterCtsHelper
+import android.safetycenter.cts.testing.SafetyCenterFlags
+import android.safetycenter.cts.testing.SafetyCenterFlags.deviceSupportsSafetyCenter
+import android.safetycenter.cts.testing.SafetySourceCtsData
+import android.safetycenter.cts.testing.SafetySourceIntentHandler.Request
+import android.safetycenter.cts.testing.SafetySourceIntentHandler.Response
+import android.safetycenter.cts.testing.SafetySourceReceiver
+import android.safetycenter.cts.testing.UiTestHelper.expandMoreIssuesCard
+import android.safetycenter.cts.testing.UiTestHelper.resetRotation
+import android.safetycenter.cts.testing.UiTestHelper.rotate
+import android.safetycenter.cts.testing.UiTestHelper.waitAllTextDisplayed
+import android.safetycenter.cts.testing.UiTestHelper.waitAllTextNotDisplayed
+import android.safetycenter.cts.testing.UiTestHelper.waitButtonDisplayed
+import android.safetycenter.cts.testing.UiTestHelper.waitDisplayed
+import android.safetycenter.cts.testing.UiTestHelper.waitNotDisplayed
+import android.safetycenter.cts.testing.UiTestHelper.waitSourceIssueDisplayed
+import android.safetycenter.cts.testing.UiTestHelper.waitSourceIssueNotDisplayed
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.uiautomator.By
+import com.android.compatibility.common.util.DisableAnimationRule
+import com.android.compatibility.common.util.FreezeRotationRule
+import com.android.compatibility.common.util.UiAutomatorUtils2
+import com.android.safetycenter.testing.SafetyCenterActivityLauncher.launchSafetyCenterActivity
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for generic subpages in Safety Center. */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+class SafetyCenterSubpagesTest {
+
+ @get:Rule val disableAnimationRule = DisableAnimationRule()
+
+ @get:Rule val freezeRotationRule = FreezeRotationRule()
+
+ private val context: Context = getApplicationContext()
+ private val safetyCenterCtsHelper = SafetyCenterCtsHelper(context)
+ private val safetySourceCtsData = SafetySourceCtsData(context)
+
+ // JUnit's Assume is not supported in @BeforeClass by the CTS tests runner, so this is used to
+ // manually skip the setup and teardown methods.
+ private val shouldRunTests = context.deviceSupportsSafetyCenter()
+
+ @Before
+ fun assumeDeviceSupportsSafetyCenterToRunTests() {
+ assumeTrue(shouldRunTests)
+ }
+
+ @Before
+ fun enableSafetyCenterBeforeTest() {
+ if (!shouldRunTests) {
+ return
+ }
+ safetyCenterCtsHelper.setup()
+ SafetyCenterFlags.showSubpages = true
+ }
+
+ @After
+ fun clearDataAfterTest() {
+ if (!shouldRunTests) {
+ return
+ }
+ safetyCenterCtsHelper.reset()
+ UiAutomatorUtils2.getUiDevice().resetRotation()
+ }
+
+ @Test
+ fun launchSafetyCenter_withSubpagesIntentExtra_showsSubpageTitle() {
+ safetyCenterCtsHelper.setConfig(MULTIPLE_SOURCE_GROUPS_CONFIG)
+ val extras = Bundle()
+ extras.putString(EXTRA_SAFETY_SOURCES_GROUP_ID, MULTIPLE_SOURCES_GROUP_ID_1)
+
+ context.launchSafetyCenterActivity(extras) {
+ // CollapsingToolbar title can't be found by text, so using description instead.
+ waitDisplayed(
+ By.desc(
+ context.getString(
+ MULTIPLE_SOURCE_GROUPS_CONFIG.safetySourcesGroups.first()!!.titleResId
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ fun launchSafetyCenter_withSubpagesIntentExtraButFlagDisabled_showsHomepageTitle() {
+ SafetyCenterFlags.showSubpages = false
+ safetyCenterCtsHelper.setConfig(MULTIPLE_SOURCE_GROUPS_CONFIG)
+ val extras = Bundle()
+ extras.putString(EXTRA_SAFETY_SOURCES_GROUP_ID, MULTIPLE_SOURCES_GROUP_ID_1)
+
+ context.launchSafetyCenterActivity(extras) {
+ // CollapsingToolbar title can't be found by text, so using description instead.
+ waitDisplayed(By.desc("Security & privacy"))
+ }
+ }
+
+ @Test
+ fun launchSafetyCenter_withNonExistingGroupID_displaysNothing() {
+ safetyCenterCtsHelper.setConfig(MULTIPLE_SOURCE_GROUPS_CONFIG)
+ val extras = Bundle()
+ extras.putString(EXTRA_SAFETY_SOURCES_GROUP_ID, "non_existing_group_id")
+
+ context.launchSafetyCenterActivity(extras) {
+ waitNotDisplayed(
+ By.desc(
+ context.getString(
+ MULTIPLE_SOURCE_GROUPS_CONFIG.safetySourcesGroups.first()!!.titleResId
+ )
+ )
+ )
+ }
+ }
+
+ @Test
+ fun launchSafetyCenter_withMultipleGroups_showsHomepageEntries() {
+ val sourceCtsData = safetySourceCtsData.information
+ with(safetyCenterCtsHelper) {
+ setConfig(MULTIPLE_SOURCE_GROUPS_CONFIG)
+ setData(SOURCE_ID_1, sourceCtsData)
+ setData(SOURCE_ID_2, sourceCtsData)
+ setData(SOURCE_ID_3, sourceCtsData)
+ setData(SOURCE_ID_4, sourceCtsData)
+ setData(SOURCE_ID_5, sourceCtsData)
+ }
+ val firstGroup: SafetySourcesGroup =
+ MULTIPLE_SOURCE_GROUPS_CONFIG.safetySourcesGroups.first()
+ val lastGroup: SafetySourcesGroup = MULTIPLE_SOURCE_GROUPS_CONFIG.safetySourcesGroups.last()
+
+ context.launchSafetyCenterActivity {
+ waitAllTextDisplayed(
+ context.getString(firstGroup.titleResId),
+ context.getString(firstGroup.summaryResId),
+ context.getString(lastGroup.titleResId),
+ context.getString(lastGroup.summaryResId)
+ )
+
+ openSubpageAndExit(lastGroup) {
+ // Verifying that the subpage is opened with collapsing toolbar title
+ waitDisplayed(By.desc(context.getString(lastGroup.titleResId)))
+ waitAllTextNotDisplayed(context.getString(lastGroup.summaryResId))
+ }
+ }
+ }
+
+ @Test
+ fun launchSafetyCenter_withMultipleGroupsButFlagDisabled_showsExpandAndCollapseEntries() {
+ SafetyCenterFlags.showSubpages = false
+ val sourceCtsData = safetySourceCtsData.information
+ with(safetyCenterCtsHelper) {
+ setConfig(MULTIPLE_SOURCE_GROUPS_CONFIG)
+ setData(SOURCE_ID_1, sourceCtsData)
+ setData(SOURCE_ID_2, sourceCtsData)
+ setData(SOURCE_ID_3, sourceCtsData)
+ setData(SOURCE_ID_4, sourceCtsData)
+ setData(SOURCE_ID_5, sourceCtsData)
+ }
+ val firstGroup: SafetySourcesGroup =
+ MULTIPLE_SOURCE_GROUPS_CONFIG.safetySourcesGroups.first()
+ val lastGroup: SafetySourcesGroup = MULTIPLE_SOURCE_GROUPS_CONFIG.safetySourcesGroups.last()
+
+ context.launchSafetyCenterActivity {
+ waitAllTextDisplayed(
+ context.getString(firstGroup.titleResId),
+ context.getString(firstGroup.summaryResId),
+ context.getString(lastGroup.titleResId)
+ )
+ waitDisplayed(By.text(context.getString(lastGroup.summaryResId))) { it.click() }
+
+ // Verifying that the group is expanded and sources are displayed
+ waitAllTextDisplayed(sourceCtsData.status!!.title, sourceCtsData.status!!.summary)
+ waitAllTextNotDisplayed(context.getString(lastGroup.summaryResId))
+ }
+ }
+
+ @Test
+ fun launchSafetyCenter_redirectBackFromSubpage_showsHomepageEntries() {
+ with(safetyCenterCtsHelper) {
+ setConfig(MULTIPLE_SOURCE_GROUPS_CONFIG)
+ setData(SOURCE_ID_1, safetySourceCtsData.information)
+ setData(SOURCE_ID_2, safetySourceCtsData.information)
+ }
+ val firstGroup: SafetySourcesGroup =
+ MULTIPLE_SOURCE_GROUPS_CONFIG.safetySourcesGroups.first()
+
+ context.launchSafetyCenterActivity {
+ // Verifying that both entry title and summary are displayed on homepage
+ waitAllTextDisplayed(
+ context.getString(firstGroup.titleResId),
+ context.getString(firstGroup.summaryResId)
+ )
+
+ openSubpageAndExit(firstGroup) {
+ // Verifying that only collapsing toolbar title is displayed for subpage
+ waitDisplayed(By.desc(context.getString(firstGroup.titleResId)))
+ waitAllTextNotDisplayed(context.getString(firstGroup.summaryResId))
+ }
+
+ // Verifying that the homepage is opened again
+ waitAllTextDisplayed(
+ context.getString(firstGroup.titleResId),
+ context.getString(firstGroup.summaryResId)
+ )
+ }
+ }
+
+ @Test
+ fun entryListWithMultipleSources_clickingOnHomepageEntry_showsSubpageEntries() {
+ with(safetyCenterCtsHelper) {
+ setConfig(MULTIPLE_SOURCES_CONFIG)
+ setData(
+ SOURCE_ID_1,
+ safetySourceCtsData.buildSafetySourceDataWithSummary(
+ severityLevel = SafetySourceData.SEVERITY_LEVEL_INFORMATION,
+ entryTitle = SAFETY_SOURCE_1_TITLE,
+ entrySummary = SAFETY_SOURCE_1_SUMMARY
+ )
+ )
+ setData(
+ SOURCE_ID_2,
+ safetySourceCtsData.buildSafetySourceDataWithSummary(
+ severityLevel = SafetySourceData.SEVERITY_LEVEL_INFORMATION,
+ entryTitle = SAFETY_SOURCE_2_TITLE,
+ entrySummary = SAFETY_SOURCE_2_SUMMARY
+ )
+ )
+ setData(
+ SOURCE_ID_3,
+ safetySourceCtsData.buildSafetySourceDataWithSummary(
+ severityLevel = SafetySourceData.SEVERITY_LEVEL_INFORMATION,
+ entryTitle = SAFETY_SOURCE_3_TITLE,
+ entrySummary = SAFETY_SOURCE_3_SUMMARY
+ )
+ )
+ }
+ val firstGroup: SafetySourcesGroup = MULTIPLE_SOURCES_CONFIG.safetySourcesGroups[0]
+ val secondGroup: SafetySourcesGroup = MULTIPLE_SOURCES_CONFIG.safetySourcesGroups[1]
+
+ context.launchSafetyCenterActivity {
+ // Verifying that subpage entries of the first group are displayed
+ openSubpageAndExit(firstGroup) {
+ waitAllTextNotDisplayed(context.getString(firstGroup.summaryResId))
+ waitAllTextDisplayed(
+ SAFETY_SOURCE_1_TITLE,
+ SAFETY_SOURCE_1_SUMMARY,
+ SAFETY_SOURCE_2_TITLE,
+ SAFETY_SOURCE_2_SUMMARY
+ )
+ }
+
+ // Verifying that subpage entries of the second group are displayed
+ openSubpageAndExit(secondGroup) {
+ waitAllTextNotDisplayed(context.getString(secondGroup.summaryResId))
+ waitAllTextDisplayed(SAFETY_SOURCE_3_TITLE, SAFETY_SOURCE_3_SUMMARY)
+ }
+ }
+ }
+
+ @Test
+ fun entryListWithSingleSource_clickingOnSubpageEntry_redirectsToDifferentScreen() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ val sourcesGroup: SafetySourcesGroup = SINGLE_SOURCE_CONFIG.safetySourcesGroups.first()
+ val source: SafetySource = sourcesGroup.safetySources.first()
+
+ context.launchSafetyCenterActivity {
+ openSubpageAndExit(sourcesGroup) {
+ waitDisplayed(By.text(context.getString(source.titleResId))) { it.click() }
+ waitButtonDisplayed("Exit test activity") { it.click() }
+ waitAllTextDisplayed(
+ context.getString(source.titleResId),
+ context.getString(source.summaryResId)
+ )
+ }
+ }
+ }
+
+ @Test
+ fun entryListWithSingleSource_clickingTheInfoIcon_redirectsToDifferentScreen() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ val sourceCtsData = safetySourceCtsData.informationWithIconAction
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, sourceCtsData)
+ val sourcesGroup: SafetySourcesGroup = SINGLE_SOURCE_CONFIG.safetySourcesGroups.first()
+
+ context.launchSafetyCenterActivity {
+ openSubpageAndExit(sourcesGroup) {
+ waitDisplayed(By.desc("Information")) { it.click() }
+ waitButtonDisplayed("Exit test activity") { it.click() }
+ waitAllTextDisplayed(sourceCtsData.status!!.title, sourceCtsData.status!!.summary)
+ }
+ }
+ }
+
+ @Test
+ fun entryListWithSingleSource_clickingTheGearIcon_redirectsToDifferentScreen() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ val sourceCtsData = safetySourceCtsData.informationWithGearIconAction
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, sourceCtsData)
+ val sourcesGroup: SafetySourcesGroup = SINGLE_SOURCE_CONFIG.safetySourcesGroups.first()
+
+ context.launchSafetyCenterActivity {
+ openSubpageAndExit(sourcesGroup) {
+ waitDisplayed(By.desc("Settings")) { it.click() }
+ waitButtonDisplayed("Exit test activity") { it.click() }
+ waitAllTextDisplayed(sourceCtsData.status!!.title, sourceCtsData.status!!.summary)
+ }
+ }
+ }
+
+ @Test
+ fun entryListWithSingleSource_clickingSourceWithNullPendingIntent_doesNothing() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_INVALID_INTENT_CONFIG)
+ val sourceCtsData = safetySourceCtsData.informationWithNullIntent
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, sourceCtsData)
+ val sourcesGroup: SafetySourcesGroup =
+ SINGLE_SOURCE_INVALID_INTENT_CONFIG.safetySourcesGroups.first()
+
+ context.launchSafetyCenterActivity {
+ openSubpageAndExit(sourcesGroup) {
+ waitDisplayed(By.text(sourceCtsData.status!!.title.toString())) { it.click() }
+
+ // Verifying that clicking on the entry doesn't redirect to any other screen
+ waitAllTextDisplayed(sourceCtsData.status!!.title, sourceCtsData.status!!.summary)
+ }
+ }
+ }
+
+ @Test
+ fun entryListWithSingleSource_updateSafetySourceData_displayedDataIsUpdated() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ val sourcesGroup: SafetySourcesGroup = SINGLE_SOURCE_CONFIG.safetySourcesGroups.first()
+ val source: SafetySource = sourcesGroup.safetySources.first()
+
+ context.launchSafetyCenterActivity(withReceiverPermission = true) {
+ openSubpageAndExit(sourcesGroup) {
+ waitAllTextDisplayed(
+ context.getString(source.titleResId),
+ context.getString(source.summaryResId)
+ )
+ }
+
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(
+ safetySourceCtsData.buildSafetySourceDataWithSummary(
+ severityLevel = SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION,
+ entryTitle = "Updated title",
+ entrySummary = "Updated summary"
+ )
+ )
+ )
+
+ openSubpageAndExit(sourcesGroup) {
+ waitAllTextNotDisplayed(
+ context.getString(source.titleResId),
+ context.getString(source.summaryResId)
+ )
+ waitAllTextDisplayed("Updated title", "Updated summary")
+ }
+ }
+ }
+
+ @Test
+ fun entryListWithSingleSource_updateSafetySourceDataAndRotate_displayedDataIsNotUpdated() {
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ val sourcesGroup: SafetySourcesGroup = SINGLE_SOURCE_CONFIG.safetySourcesGroups.first()
+ val source: SafetySource = sourcesGroup.safetySources.first()
+
+ context.launchSafetyCenterActivity(withReceiverPermission = true) {
+ openSubpageAndExit(sourcesGroup) {
+ waitAllTextDisplayed(
+ context.getString(source.titleResId),
+ context.getString(source.summaryResId)
+ )
+
+ SafetySourceReceiver.setResponse(
+ Request.Refresh(SINGLE_SOURCE_ID),
+ Response.SetData(
+ safetySourceCtsData.buildSafetySourceDataWithSummary(
+ severityLevel = SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION,
+ entryTitle = "Updated title",
+ entrySummary = "Updated summary"
+ )
+ )
+ )
+ UiAutomatorUtils2.getUiDevice().rotate()
+
+ waitAllTextDisplayed(
+ context.getString(source.titleResId),
+ context.getString(source.summaryResId)
+ )
+ waitAllTextNotDisplayed("Updated title", "Updated summary")
+ }
+ }
+ }
+
+ @Test
+ fun issueCard_withMultipleGroups_onlyRelevantSubpageHasIssueCard() {
+ /* The default attribution title for an issue card is same as the entry group title on the
+ * homepage. This causes test flakiness as UiAutomator is unable to choose from duplicate
+ * strings. To address that, an issue with a different attribution title is used here. */
+ val sourceData = safetySourceCtsData.informationWithIssueWithAttributionTitle
+ val issue = sourceData.issues[0]
+
+ safetyCenterCtsHelper.setConfig(MULTIPLE_SOURCES_CONFIG)
+ val firstGroup: SafetySourcesGroup = MULTIPLE_SOURCES_CONFIG.safetySourcesGroups[0]
+ val secondGroup: SafetySourcesGroup = MULTIPLE_SOURCES_CONFIG.safetySourcesGroups[1]
+ safetyCenterCtsHelper.setData(SOURCE_ID_3, sourceData)
+
+ context.launchSafetyCenterActivity {
+ // Verify that homepage has the issue card
+ waitSourceIssueDisplayed(issue)
+
+ // Verify that irrelevant subpage doesn't have the issue card
+ openSubpageAndExit(firstGroup) { waitSourceIssueNotDisplayed(issue) }
+ // Verify that relevant subpage has the issue card
+ openSubpageAndExit(secondGroup) { waitSourceIssueDisplayed(issue) }
+ }
+ }
+
+ @Test
+ fun issueCard_updateSafetySourceData_subpageDisplaysUpdatedIssue() {
+ val initialDataToDisplay = safetySourceCtsData.informationWithIssueWithAttributionTitle
+ val updatedDataToDisplay = safetySourceCtsData.criticalWithIssueWithAttributionTitle
+
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ val sourcesGroup: SafetySourcesGroup = SINGLE_SOURCE_CONFIG.safetySourcesGroups.first()
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, initialDataToDisplay)
+
+ context.launchSafetyCenterActivity {
+ openSubpageAndExit(sourcesGroup) {
+ waitSourceIssueDisplayed(initialDataToDisplay.issues[0])
+
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, updatedDataToDisplay)
+
+ waitSourceIssueDisplayed(updatedDataToDisplay.issues[0])
+ }
+ }
+ }
+
+ @Test
+ fun issueCard_resolveIssueOnSubpage_issueDismisses() {
+ val sourceData = safetySourceCtsData.criticalWithIssueWithAttributionTitle
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, sourceData)
+ val sourcesGroup: SafetySourcesGroup = SINGLE_SOURCE_CONFIG.safetySourcesGroups.first()
+ val issue = sourceData.issues[0]
+ val action = issue.actions[0]
+
+ // Clear the data when action is triggered to simulate resolution.
+ SafetySourceReceiver.setResponse(
+ Request.ResolveAction(SINGLE_SOURCE_ID),
+ Response.ClearData
+ )
+
+ context.launchSafetyCenterActivity(withReceiverPermission = true) {
+ openSubpageAndExit(sourcesGroup) {
+ waitSourceIssueDisplayed(issue)
+ waitButtonDisplayed(action.label) { it.click() }
+
+ // Wait for success message to go away, verify issue no longer displayed
+ waitAllTextNotDisplayed(action.successMessage)
+ waitSourceIssueNotDisplayed(issue)
+ }
+ }
+ }
+
+ @Test
+ fun issueCard_confirmDismissalOnSubpage_dismissesIssue() {
+ val sourceData = safetySourceCtsData.criticalWithIssueWithAttributionTitle
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, sourceData)
+ val sourcesGroup: SafetySourcesGroup = SINGLE_SOURCE_CONFIG.safetySourcesGroups.first()
+ val issue = sourceData.issues[0]
+
+ context.launchSafetyCenterActivity {
+ openSubpageAndExit(sourcesGroup) {
+ waitSourceIssueDisplayed(issue)
+ waitDisplayed(By.desc("Dismiss")) { it.click() }
+
+ waitAllTextDisplayed("Dismiss this alert?")
+ waitButtonDisplayed("Dismiss") { it.click() }
+
+ waitSourceIssueNotDisplayed(issue)
+ }
+ }
+ }
+
+ @Test
+ fun issueCard_dismissOnSubpageWithRotation_cancellationPersistsIssue() {
+ val sourceData = safetySourceCtsData.criticalWithIssueWithAttributionTitle
+ safetyCenterCtsHelper.setConfig(SINGLE_SOURCE_CONFIG)
+ safetyCenterCtsHelper.setData(SINGLE_SOURCE_ID, sourceData)
+ val sourcesGroup: SafetySourcesGroup = SINGLE_SOURCE_CONFIG.safetySourcesGroups.first()
+ val issue = sourceData.issues[0]
+
+ context.launchSafetyCenterActivity {
+ openSubpageAndExit(sourcesGroup) {
+ waitSourceIssueDisplayed(issue)
+ waitDisplayed(By.desc("Dismiss")) { it.click() }
+ waitAllTextDisplayed("Dismiss this alert?")
+
+ UiAutomatorUtils2.getUiDevice().rotate()
+
+ waitAllTextDisplayed("Dismiss this alert?")
+ waitButtonDisplayed("Cancel") { it.click() }
+ waitSourceIssueDisplayed(issue)
+ }
+ }
+ }
+
+ @Test
+ fun moreIssuesCard_expandOnSubpage_showsAdditionalCard() {
+ safetyCenterCtsHelper.setConfig(MULTIPLE_SOURCES_CONFIG)
+ val sourcesGroup: SafetySourcesGroup = MULTIPLE_SOURCES_CONFIG.safetySourcesGroups.first()
+ val firstSourceData = safetySourceCtsData.criticalWithIssueWithAttributionTitle
+ val secondSourceData = safetySourceCtsData.informationWithIssueWithAttributionTitle
+ safetyCenterCtsHelper.setData(SOURCE_ID_1, firstSourceData)
+ safetyCenterCtsHelper.setData(SOURCE_ID_2, secondSourceData)
+
+ context.launchSafetyCenterActivity {
+ openSubpageAndExit(sourcesGroup) {
+ waitSourceIssueDisplayed(firstSourceData.issues[0])
+ waitAllTextDisplayed("See all alerts")
+ waitSourceIssueNotDisplayed(secondSourceData.issues[0])
+
+ expandMoreIssuesCard()
+
+ waitSourceIssueDisplayed(firstSourceData.issues[0])
+ waitAllTextNotDisplayed("See all alerts")
+ waitSourceIssueDisplayed(secondSourceData.issues[0])
+ }
+ }
+ }
+
+ private fun openSubpageAndExit(group: SafetySourcesGroup, block: () -> Unit) {
+ val uiDevice = UiAutomatorUtils2.getUiDevice()
+ uiDevice.waitForIdle()
+
+ // Opens subpage by clicking on the group title
+ waitDisplayed(By.text(context.getString(group.titleResId))) { it.click() }
+ uiDevice.waitForIdle()
+
+ // Executes the required verifications
+ block()
+ uiDevice.waitForIdle()
+
+ // Exits subpage by pressing the back button
+ uiDevice.pressBack()
+ uiDevice.waitForIdle()
+ }
+
+ companion object {
+ private const val SAFETY_SOURCE_1_TITLE = "Safety Source 1 Title"
+ private const val SAFETY_SOURCE_1_SUMMARY = "Safety Source 1 Summary"
+ private const val SAFETY_SOURCE_2_TITLE = "Safety Source 2 Title"
+ private const val SAFETY_SOURCE_2_SUMMARY = "Safety Source 2 Summary"
+ private const val SAFETY_SOURCE_3_TITLE = "Safety Source 3 Title"
+ private const val SAFETY_SOURCE_3_SUMMARY = "Safety Source 3 Summary"
+ }
+}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/EqualsHashCodeToStringTester.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/EqualsHashCodeToStringTester.kt
index 4a720f2e6..13a86e4e6 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/EqualsHashCodeToStringTester.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/EqualsHashCodeToStringTester.kt
@@ -16,61 +16,129 @@
package com.android.safetycenter.testing
+import android.os.Bundle
+import android.os.Parcelable
+import androidx.test.core.os.Parcelables.forceParcel
import com.google.common.base.Equivalence
import com.google.common.testing.EqualsTester
import com.google.common.testing.EquivalenceTester
/**
- * A class similar to [EqualsTester] that also checks that the [Object.hashCode] and
- * [Object.toString] implementations are consistent with equality groups.
+ * A class similar to [EqualsTester] that also optionally checks that the [Object.hashCode],
+ * [Object.toString] and [Parcelable] implementations are consistent with equality groups.
*
* Note: this class assumes that [Object.hashCode] does not create a collision for equality groups,
- * however this can be disabled by setting [hashCodeCanCollide] to `true`.
+ * however this can be disabled by setting [ignoreHashCode] to `true`.
+ *
+ * Note: this class assumes that [Object.toString] only represents the state that is used in
+ * [Object.equals] and [Object.hashCode] implementation. Objects with [Bundle] fields may break it.
+ * This can be disabled by setting [ignoreToString] to `true`.
+ *
+ * @param parcelRoundTripEqualsEquivalence optionally provide an equivalence that also checks that
+ * the [Parcelable] implementation is consistent with equality groups by recreating equal items
+ * from their [Parcelable] implementation
+ * @param createCopy optionally provide a custom method to create an equal copy that will be applied
+ * to all the items in provided in an equality group
*/
-class EqualsHashCodeToStringTester(private val hashCodeCanCollide: Boolean = false) {
+class EqualsHashCodeToStringTester<T>
+private constructor(
+ private val ignoreHashCode: Boolean = false,
+ private val ignoreToString: Boolean = false,
+ private val parcelRoundTripEqualsEquivalence: Equivalence<T>? = null,
+ private val createCopy: ((T) -> T)? = null
+) {
+
private val equalsTester = EqualsTester()
- private val toStringTester = EquivalenceTester.of(TO_STRING_EQUIVALENCE)
- private val hashCodeTester = EquivalenceTester.of(HASH_CODE_EQUIVALENCE)
-
- fun addEqualityGroup(vararg groups: Any): EqualsHashCodeToStringTester {
- equalsTester.addEqualityGroup(*groups)
- toStringTester.addEquivalenceGroupAsArray(groups)
- if (!hashCodeCanCollide) {
- hashCodeTester.addEquivalenceGroupAsArray(groups)
- }
- return this
- }
+ private val hashCodeTester =
+ EquivalenceTester.of<T>(hashCodeEquivalence()).takeIf { !ignoreHashCode }
+ private val toStringTester =
+ EquivalenceTester.of<T>(toStringEquivalence()).takeIf { !ignoreToString }
+ private val parcelableTester =
+ parcelRoundTripEqualsEquivalence?.let { EquivalenceTester.of(it) }
- private fun EquivalenceTester<Any>.addEquivalenceGroupAsArray(input: Array<out Any>) {
- when (val size = input.size) {
- 0 -> return
- 1 -> addEquivalenceGroup(input[0])
- else -> addEquivalenceGroup(input[0], *input.copyOfRange(1, size))
- }
+ fun addEqualityGroup(vararg equalItems: T): EqualsHashCodeToStringTester<T> {
+ val equalItemsWithCopiesIfNeeded = equalItems.toList().withCopiesIfNeeded(createCopy)
+ equalsTester.addEqualityGroup(*equalItemsWithCopiesIfNeeded.toAnyArray())
+ hashCodeTester?.addEquivalenceGroup(equalItemsWithCopiesIfNeeded)
+ toStringTester?.addEquivalenceGroup(equalItemsWithCopiesIfNeeded)
+ parcelableTester?.addEquivalenceGroup(equalItemsWithCopiesIfNeeded)
+ return this
}
fun test() {
equalsTester.testEquals()
- toStringTester.test()
- if (!hashCodeCanCollide) {
- hashCodeTester.test()
- }
+ hashCodeTester?.test()
+ toStringTester?.test()
+ parcelableTester?.test()
}
companion object {
/**
+ * Returns an [EqualsHashCodeToStringTester] that also checks that the [Parcelable]
+ * implementation: i.e. recreating an instance from its [Parcelable] implementation returns
+ * an object that's consistent with its equality group.
+ *
+ * @see EqualsHashCodeToStringTester
+ */
+ fun <T : Parcelable> ofParcelable(
+ parcelableCreator: Parcelable.Creator<T>,
+ ignoreHashCode: Boolean = false,
+ ignoreToString: Boolean = false,
+ createCopy: ((T) -> T)? = null
+ ): EqualsHashCodeToStringTester<T> =
+ EqualsHashCodeToStringTester(
+ ignoreHashCode,
+ ignoreToString,
+ parcelRoundTripEqualsEquivalence(parcelableCreator),
+ createCopy)
+
+ /**
+ * Returns an [EqualsHashCodeToStringTester] that does not check the [Parcelable]
+ * implementation of the class, typically if the class doesn't implement [Parcelable].
+ *
+ * @see EqualsHashCodeToStringTester
+ */
+ fun <T> of(
+ ignoreHashCode: Boolean = false,
+ ignoreToString: Boolean = false,
+ createCopy: ((T) -> T)? = null
+ ): EqualsHashCodeToStringTester<T> =
+ EqualsHashCodeToStringTester(
+ ignoreHashCode, ignoreToString, parcelRoundTripEqualsEquivalence = null, createCopy)
+
+ /**
+ * An [Equivalence] that considers two instances of a class equivalent iff they are still
+ * equal when one of them is recreated from their [Parcelable] implementation.
+ */
+ private fun <T : Parcelable> parcelRoundTripEqualsEquivalence(
+ parcelableCreator: Parcelable.Creator<T>
+ ) =
+ object : Equivalence<T>() {
+
+ override fun doEquivalent(a: T, b: T): Boolean {
+ return a.recreateFromParcel() == b && a == b.recreateFromParcel()
+ }
+
+ override fun doHash(o: T): Int {
+ return o.recreateFromParcel().hashCode()
+ }
+
+ private fun T.recreateFromParcel(): T = forceParcel(this, parcelableCreator)
+ }
+
+ /**
* An [Equivalence] that considers two instances of a class equivalent iff [Object.toString]
* return the same value.
*/
- private val TO_STRING_EQUIVALENCE =
- object : Equivalence<Any>() {
+ private fun <T> toStringEquivalence() =
+ object : Equivalence<T>() {
- override fun doEquivalent(a: Any, b: Any): Boolean {
+ override fun doEquivalent(a: T, b: T): Boolean {
return a.toString() == b.toString()
}
- override fun doHash(o: Any): Int {
+ override fun doHash(o: T): Int {
return o.toString().hashCode()
}
}
@@ -79,16 +147,21 @@ class EqualsHashCodeToStringTester(private val hashCodeCanCollide: Boolean = fal
* An [Equivalence] that considers two instances of a class equivalent iff [Object.hashCode]
* return the same value.
*/
- private val HASH_CODE_EQUIVALENCE =
- object : Equivalence<Any>() {
+ private fun <T> hashCodeEquivalence() =
+ object : Equivalence<T>() {
- override fun doEquivalent(a: Any, b: Any): Boolean {
+ override fun doEquivalent(a: T, b: T): Boolean {
return a.hashCode() == b.hashCode()
}
- override fun doHash(o: Any): Int {
+ override fun doHash(o: T): Int {
return o.hashCode()
}
}
+
+ private fun <T> List<T>.toAnyArray(): Array<*> = Array(size) { this[it] as Any }
+
+ private fun <T> List<T>.withCopiesIfNeeded(createCopy: ((T) -> T)? = null): List<T> =
+ createCopy?.let { this + this.map(it) } ?: this
}
}