summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/OWNERS6
-rw-r--r--PermissionController/WEAR_OWNERS3
-rw-r--r--PermissionController/res/drawable/ic_more_horizontal.xml26
-rw-r--r--PermissionController/res/navigation-watch/nav_graph.xml4
-rw-r--r--PermissionController/res/values-am/strings.xml46
-rw-r--r--PermissionController/res/values-ar-v33/strings.xml2
-rw-r--r--PermissionController/res/values-ar/strings.xml4
-rw-r--r--PermissionController/res/values-in/strings.xml2
-rw-r--r--PermissionController/res/values-it/strings.xml2
-rw-r--r--PermissionController/res/values-ja/strings.xml2
-rw-r--r--PermissionController/res/values-pt-rPT/strings.xml34
-rw-r--r--PermissionController/res/values-ur/strings.xml2
-rw-r--r--PermissionController/res/values-zh-rCN/strings.xml2
-rw-r--r--PermissionController/res/values/colors.xml3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/AppPermissionsFragmentWear.java407
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsFragment.kt113
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsHelper.kt337
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt133
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt159
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionsFragment.kt74
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt14
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/AppPermissionGroupsRevokeDialogViewModel.kt53
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java9
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt21
-rw-r--r--PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/v31/PermissionUsageFragmentTest.kt7
-rw-r--r--SafetyCenter/Resources/shared_res/values-af/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-am/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ar/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-as/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-az/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-be/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-bg/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-bn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-bs/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ca/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-cs/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-da/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-de/strings.xml3
-rw-r--r--SafetyCenter/Resources/shared_res/values-el/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml3
-rw-r--r--SafetyCenter/Resources/shared_res/values-es/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-et/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-eu/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fa/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fi/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-fr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-gl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-gu/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hi/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hu/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-hy/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-in/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-is/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-it/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-iw/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ja/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ka/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-kk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-km/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-kn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ko/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ky/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-lo/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-lt/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-lv/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-mk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ml/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-mn/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-mr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ms/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-my/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-nb/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ne/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-nl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-or/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pa/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-pt/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ro/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ru/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-si/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sq/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sv/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-sw/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ta/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-te/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-th/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-tl/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-tr/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-uk/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-ur/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-uz/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-vi/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml5
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml1
-rw-r--r--SafetyCenter/Resources/shared_res/values-zu/strings.xml1
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java11
-rw-r--r--service/java/com/android/safetycenter/SafetySourceIssues.java87
-rw-r--r--service/java/com/android/safetycenter/data/SafetyCenterDataManager.java54
-rw-r--r--service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java16
-rw-r--r--service/java/com/android/safetycenter/data/SafetySourceDataRepository.java51
-rw-r--r--service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java13
-rw-r--r--service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java9
-rw-r--r--tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt10
-rw-r--r--tests/cts/permission/Android.bp2
-rw-r--r--tests/cts/permission/AndroidTest.xml1
-rw-r--r--tests/cts/permission/AppThatRequestDevicePermissions/Android.bp31
-rw-r--r--tests/cts/permission/AppThatRequestDevicePermissions/AndroidManifest.xml26
-rw-r--r--tests/cts/permission/nativeTests/AndroidTest.xml2
-rw-r--r--tests/cts/permission/sdk28/AndroidTest.xml2
-rw-r--r--tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt113
-rw-r--r--tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java63
-rw-r--r--tests/cts/permissionmultiuser/AndroidTest.xml2
-rw-r--r--tests/cts/permissionpolicy/AndroidTest.xml2
-rw-r--r--tests/cts/permissionpolicy/res/raw/android_manifest.xml8
-rw-r--r--tests/cts/permissionui/AndroidTest.xml2
-rw-r--r--tests/cts/permissionui/PermissionPolicyApp25/src/android/permissionui/cts/permissionpolicy/TestProtectionFlagsActivity.kt2
-rw-r--r--tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt2
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt8
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt19
-rw-r--r--tests/cts/role/Android.bp49
-rw-r--r--tests/cts/role/AndroidManifest.xml38
-rw-r--r--tests/cts/role/AndroidTest.xml51
-rw-r--r--tests/cts/role/CtsRoleTestApp/Android.bp26
-rw-r--r--tests/cts/role/CtsRoleTestApp/AndroidManifest.xml131
-rw-r--r--tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultDialerActivity.java52
-rw-r--r--tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultSmsActivity.java52
-rw-r--r--tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java46
-rw-r--r--tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java53
-rw-r--r--tests/cts/role/CtsRoleTestApp28/Android.bp27
-rw-r--r--tests/cts/role/CtsRoleTestApp28/AndroidManifest.xml79
-rw-r--r--tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultDialerActivity.java40
-rw-r--r--tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultSmsActivity.java41
-rw-r--r--tests/cts/role/CtsRoleTestApp33WithoutInCallService/Android.bp23
-rw-r--r--tests/cts/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml37
-rw-r--r--tests/cts/role/OWNERS3
-rw-r--r--tests/cts/role/TEST_MAPPING48
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleControllerManagerTest.kt154
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleManagerTest.java1181
-rw-r--r--tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt202
-rw-r--r--tests/cts/role/src/android/app/role/cts/WaitForResultActivity.java62
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt16
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt67
-rw-r--r--tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt33
-rw-r--r--tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterSystemEventReportedLoggingHostTest.kt33
-rw-r--r--tests/hostside/safetycenter/src/android/safetycenter/hostside/rules/RequireSafetyCenterRule.kt19
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt21
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt30
165 files changed, 3996 insertions, 736 deletions
diff --git a/PermissionController/OWNERS b/PermissionController/OWNERS
index 5cd46d618..66cd836f2 100644
--- a/PermissionController/OWNERS
+++ b/PermissionController/OWNERS
@@ -12,3 +12,9 @@ joeo@google.com
# for SafetyCenter UI changes
per-file res/** = file:platform/packages/modules/Permission:/SafetyCenter/OWNERS
+
+# For Wear related changes
+per-file WEAR_OWNERS = file:/PermissionController/WEAR_OWNERS
+per-file src/com/android/permissioncontroller/permission/ui/wear/** = file:/PermissionController/WEAR_OWNERS
+per-file src/com/android/permissioncontroller/role/ui/wear/** = file:/PermissionController/WEAR_OWNERS
+per-file res/*-watch/* = file:/PermissionController/WEAR_OWNERS
diff --git a/PermissionController/WEAR_OWNERS b/PermissionController/WEAR_OWNERS
new file mode 100644
index 000000000..da9486f1c
--- /dev/null
+++ b/PermissionController/WEAR_OWNERS
@@ -0,0 +1,3 @@
+adsule@google.com
+sadrul@google.com
+youngjoonyang@google.com
diff --git a/PermissionController/res/drawable/ic_more_horizontal.xml b/PermissionController/res/drawable/ic_more_horizontal.xml
new file mode 100644
index 000000000..c770e08ef
--- /dev/null
+++ b/PermissionController/res/drawable/ic_more_horizontal.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ 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.
+ -->
+
+<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="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/PermissionController/res/navigation-watch/nav_graph.xml b/PermissionController/res/navigation-watch/nav_graph.xml
index 86c6458fa..c3ebe1587 100644
--- a/PermissionController/res/navigation-watch/nav_graph.xml
+++ b/PermissionController/res/navigation-watch/nav_graph.xml
@@ -25,7 +25,7 @@
<fragment
android:id="@+id/manage_standard"
- android:name="com.android.permissioncontroller.permission.ui.handheld.ManageStandardPermissionsWrapperFragment"
+ android:name="com.android.permissioncontroller.permission.ui.wear.WearManageStandardPermissionsFragment"
android:label="ManageStandard">
<action
@@ -81,7 +81,7 @@
<fragment
android:id="@+id/app_permission_groups"
- android:name="com.android.permissioncontroller.permission.ui.handheld.AppPermissionGroupsWrapperFragment"
+ android:name="com.android.permissioncontroller.permission.ui.wear.WearAppPermissionGroupsFragment"
android:label="AppPermissionGroups">
<action
diff --git a/PermissionController/res/values-am/strings.xml b/PermissionController/res/values-am/strings.xml
index 144de7866..aa5edc983 100644
--- a/PermissionController/res/values-am/strings.xml
+++ b/PermissionController/res/values-am/strings.xml
@@ -161,9 +161,9 @@
<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_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_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>
<string name="app_permission_usage_remove_filter" msgid="2926157607436428207">"ማጣሪያን አስወግድ"</string>
@@ -200,10 +200,10 @@
<string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"ሁሉንም <xliff:g id="APP">%1$s</xliff:g> ፈቃዶች ይመልከቱ"</string>
<string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"ከዚህ መተግበሪያ ጋር ሁሉንም መተግበሪያዎች ይመልከቱ"</string>
<string name="assistant_mic_label" msgid="1011432357152323896">"የረዳት ማይክሮፎን አጠቃቀምን አሳይ"</string>
- <string name="unused_apps_category_title" msgid="2988455616845243901">"ስራ ላይ ያልዋሉ የመተግበሪያ ቅንብሮች"</string>
+ <string name="unused_apps_category_title" msgid="2988455616845243901">"ሥራ ላይ ያልዋሉ የመተግበሪያ ቅንብሮች"</string>
<string name="auto_revoke_label" msgid="5068393642936571656">"መተግበሪያ ጥቅም ላይ ካልዋለ ፈቃዶችን አስወግድ"</string>
<string name="unused_apps_label" msgid="2595428768404901064">"ፈቃዶችን ያስወግዱ እና ቦታ ያስለቅቁ"</string>
- <string name="unused_apps_label_v2" msgid="7058776770056517980">"የመተግበሪያ እንቅስቃሴ ስራ ላይ ካልዋለ ባለበት አቁም"</string>
+ <string name="unused_apps_label_v2" msgid="7058776770056517980">"የመተግበሪያ እንቅስቃሴ ሥራ ላይ ካልዋለ ባለበት አቁም"</string>
<string name="unused_apps_summary" msgid="8839466950318403115">"ፈቃዶችን አስወግድ፣ ጊዜያዊ ፋይሎችን ሰርዝ እና ማሳወቂያዎችን አቁም"</string>
<string name="auto_revoke_summary" msgid="5867548789805911683">"ለእርስዎ ውሂብ ጥበቃ ለማድረግ፣ ለዚህ መተግበሪያ የተሰጡ ፈቃዶች መተግበሪያው ለጥቂት ወራት ጥቅም ላይ ካልዋለ ይህ መተግበሪያ ይወገዳል።"</string>
<string name="auto_revoke_summary_with_permissions" msgid="389712086597285013">"የእርስዎን ውሂብ ለመጠበቅ፣ መተግበሪያው ለጥቂት ወራት ጥቅም ላይ ካልዋለ፣ የሚከተሉት ፈቃዶች ይወገዳሉ፦ <xliff:g id="PERMS">%1$s</xliff:g>"</string>
@@ -218,8 +218,8 @@
<string name="auto_revoked_app_summary_one" msgid="7093213590301252970">"የ<xliff:g id="PERMISSION_NAME">%s</xliff:g> ፈቃድ ተወግዷል"</string>
<string name="auto_revoked_app_summary_two" msgid="1910545340763709389">"የ<xliff:g id="PERMISSION_NAME_0">%1$s</xliff:g> እና <xliff:g id="PERMISSION_NAME_1">%2$s</xliff:g> ፈቃዶች ተወግደዋል"</string>
<string name="auto_revoked_app_summary_many" msgid="5930976230827378798">"<xliff:g id="PERMISSION_NAME">%1$s</xliff:g> እና <xliff:g id="NUMBER">%2$s</xliff:g> ሌሎች ፈቃዶች ተወግደዋል"</string>
- <string name="unused_apps_page_title" msgid="6986983535677572559">"ስራ ላይ ያልዋሉ መተግበሪያዎች"</string>
- <string name="unused_apps_page_summary" msgid="1867593913217272155">"አንድ መተግበሪያ ለጥቂት ወራት ስራ ላይ ካልዋለ፦\n\n• ውሂብዎን ለመጠበቅ ፈቃዶች ይወገዳሉ\n• ባትሪን ለመቆጠብ ማሳወቂያዎች ይቆማሉ\n• ባዶ ቦታ ለማስለቀቅ ጊዜያዊ ፋይሎች ይወገዳሉ\n\nፈቃዶችን እና ማሳወቂያዎችን እንደገና ለመፍቀድ መተግበሪያውን ይክፈቱት።"</string>
+ <string name="unused_apps_page_title" msgid="6986983535677572559">"ሥራ ላይ ያልዋሉ መተግበሪያዎች"</string>
+ <string name="unused_apps_page_summary" msgid="1867593913217272155">"አንድ መተግበሪያ ለጥቂት ወራት ሥራ ላይ ካልዋለ፦\n\n• ውሂብዎን ለመጠበቅ ፈቃዶች ይወገዳሉ\n• ባትሪን ለመቆጠብ ማሳወቂያዎች ይቆማሉ\n• ባዶ ቦታ ለማስለቀቅ ጊዜያዊ ፋይሎች ይወገዳሉ\n\nፈቃዶችን እና ማሳወቂያዎችን እንደገና ለመፍቀድ መተግበሪያውን ይክፈቱት።"</string>
<string name="unused_apps_page_tv_summary" msgid="2624911608663778308">"መተግበሪያ ለአንድ ወር ጥቅም ላይ ካልዋለ፦\n\n• ውሂብዎን ለመጠበቅ ፈቃዶች ይወገዳሉ\n• ጊዜያዊ ፋይሎች ቦታ ለማስለቀቅ ይወገዳሉ\n\nፈቃዶችን ዳግም ለመፍቀድ መተግበሪያውን ይክፈቱ።"</string>
<string name="last_opened_category_title" msgid="8796557894614236128">"{count,plural, =1{መጨረሻ የተከፈተው ከ# ወር በፊት}one{መጨረሻ የተከፈተው ከ# ወር በፊት}other{መጨረሻ የተከፈተው ከ# ወራት በፊት}}"</string>
<string name="last_opened_summary" msgid="5248984030024968808">"መተግበሪያ ለመጨረሻ ጊዜ በ<xliff:g id="DATE">%s</xliff:g> ላይ ተከፍቷል"</string>
@@ -247,7 +247,7 @@
<string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"ተከልክሏል / በጭራሽ አልተደረሰበትም"</string>
<string name="allowed_header" msgid="7769277978004790414">"ይፈቀዳል"</string>
<string name="allowed_always_header" msgid="6455903312589013545">"ሁልጊዜ የተፈቀደ"</string>
- <string name="allowed_foreground_header" msgid="6845655788447833353">"ስራ ላይ ሲውል ብቻ የሚፈቀድ"</string>
+ <string name="allowed_foreground_header" msgid="6845655788447833353">"ሥራ ላይ ሲውል ብቻ የሚፈቀድ"</string>
<string name="allowed_storage_scoped" msgid="5383645873719086975">"ለሚዲያ ብቻ መዳረሻ ተፈቅዷል"</string>
<string name="allowed_storage_full" msgid="5356699280625693530">"ሁሉንም ፋይሎች ማስተዳደር ተፈቀዷል"</string>
<string name="ask_header" msgid="2633816846459944376">"ሁልጊዜ ጠይቅ"</string>
@@ -262,8 +262,8 @@
<string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> ጥቅም ላይ ያልዋሉ መተግበሪያዎች"</string>
<string name="auto_revoke_permission_reminder_notification_content" msgid="4492228990462107487">"ፈቃዶች የእርስዎን ግላዊነት ለመጠበቅ ተወግደዋል። ለመገምገም መታ ያድርጉ"</string>
<string name="auto_revoke_permission_notification_title" msgid="2629844160853454657">"ጥቅም ላይ ላልዋሉ መተግበሪያዎች ፈቃዶች ተወግደዋል"</string>
- <string name="auto_revoke_permission_notification_content" msgid="5125990886047799375">"አንዳንድ መተግበሪያዎች ለጥቂት ወሮች ስራ ላይ አልዋሉም። ለመገምገም መታ ያድርጉ።"</string>
- <string name="unused_apps_notification_title" msgid="4314832015894238019">"{count,plural, =1{# ስራ ላይ ያልዋለ መተግበሪያ}one{# ስራ ላይ ያልዋሉ መተግበሪያዎች}other{# ስራ ላይ ያልዋሉ መተግበሪያዎች}}"</string>
+ <string name="auto_revoke_permission_notification_content" msgid="5125990886047799375">"አንዳንድ መተግበሪያዎች ለጥቂት ወሮች ሥራ ላይ አልዋሉም። ለመገምገም መታ ያድርጉ።"</string>
+ <string name="unused_apps_notification_title" msgid="4314832015894238019">"{count,plural, =1{# ሥራ ላይ ያልዋለ መተግበሪያ}one{# ሥራ ላይ ያልዋሉ መተግበሪያዎች}other{# ሥራ ላይ ያልዋሉ መተግበሪያዎች}}"</string>
<string name="unused_apps_notification_content" msgid="9195026773244581246">"ፈቃዶች እና ጊዜያዊ ፋይሎች ተወግደዋል እንዲሁም ማሳወቂያዎች ቆመዋል። ለመገምገም መታ ያድርጉ።"</string>
<string name="unused_apps_safety_center_card_title" msgid="5638409355530099149">"ፈቃዶቻቸው የተወገዱባቸው መተግበሪያዎችን ይገምግሙ"</string>
<string name="unused_apps_safety_center_card_content" msgid="1088557243627427820">"ለሆነ ያህል ጊዜ ላልተጠቀሙባቸው መተግበሪያዎች ፈቃዶች እና ጊዜያዊ ፋይሎች ተወግደዋል እና ማሳወቂያዎች ቆመዋል።"</string>
@@ -274,7 +274,7 @@
<string name="post_drive_permission_decision_reminder_summary_1_app_multi_permission" msgid="4080701771111456927">"እየነዱ ሳለ <xliff:g id="COUNT">%1$d</xliff:g> ፈቃዶችን ለ<xliff:g id="APP">%2$s</xliff:g> ሰጥተዋል"</string>
<string name="post_drive_permission_decision_reminder_summary_multi_apps" msgid="5253882771252863902">"{count,plural, =1{እየነዱ ሳለ የ<xliff:g id="APP_0">%1$s</xliff:g> &amp; # ሌላ መተግበሪያ መዳረሻ ሰጥተዋል}one{እየነዱ ሳለ የ<xliff:g id="APP_1">%1$s</xliff:g> &amp; # ሌላ መተግበሪያ መዳረሻ ሰጥተዋል}other{እየነዱ ሳለ የ<xliff:g id="APP_1">%1$s</xliff:g> &amp; # ሌላ መተግበሪያ መዳረሻ ሰጥተዋል}}"</string>
<string name="go_to_settings" msgid="1053735612211228335">"ወደ ቅንብሮች ሂድ"</string>
- <string name="auto_revoke_setting_subtitle" msgid="8631720570723050460">"አንዳንድ መተግበሪያዎች ለጥቂት ወሮች ስራ ላይ አልዋሉም።"</string>
+ <string name="auto_revoke_setting_subtitle" msgid="8631720570723050460">"አንዳንድ መተግበሪያዎች ለጥቂት ወሮች ሥራ ላይ አልዋሉም።"</string>
<string name="permissions_removed_category_title" msgid="1064754271178447643">"ፈቃዶች የተወገዱባቸው"</string>
<string name="permission_removed_page_title" msgid="2627436155091001209">"ፈቃዶች የተወገዱባቸው"</string>
<string name="all_unused_apps_category_title" msgid="755663524704745414">"ሁሉም ጥቅም ላይ ያልዋሉ መተግበሪያዎች"</string>
@@ -404,9 +404,9 @@
<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>
- <string name="phone_call_uses_microphone" msgid="233569591461187177">"ማይክሮፎን በ&lt;b&gt;ስልክ ጥሪ&lt;/b&gt; ላይ ስራ ላይ ውሏል"</string>
- <string name="phone_call_uses_microphone_and_camera" msgid="6291898755681748189">"ካሜራ እና ማይክሮፎን በ&lt;b&gt;ቪዲዮ ጥሪ&lt;/b&gt; ላይ ስራ ላይ ውለዋል"</string>
- <string name="phone_call_uses_camera" msgid="2048417022147857418">"ካሜራ በ&lt;b&gt;ቪዲዮ ጥሪ&lt;/b&gt; ላይ ስራ ላይ ውሏል"</string>
+ <string name="phone_call_uses_microphone" msgid="233569591461187177">"ማይክሮፎን በ&lt;b&gt;ስልክ ጥሪ&lt;/b&gt; ላይ ሥራ ላይ ውሏል"</string>
+ <string name="phone_call_uses_microphone_and_camera" msgid="6291898755681748189">"ካሜራ እና ማይክሮፎን በ&lt;b&gt;ቪዲዮ ጥሪ&lt;/b&gt; ላይ ሥራ ላይ ውለዋል"</string>
+ <string name="phone_call_uses_camera" msgid="2048417022147857418">"ካሜራ በ&lt;b&gt;ቪዲዮ ጥሪ&lt;/b&gt; ላይ ሥራ ላይ ውሏል"</string>
<string name="system_uses_microphone" msgid="576672130318877143">"ማይክሮፎን የሥርዓት አገልግሎትን በመጠቀም ተደርሶበታል"</string>
<string name="system_uses_microphone_and_camera" msgid="5124478304275138804">"ካሜራ እና ማይክሮፎን የሥርዓት አገልግሎትን በመጠቀም ተደርሶበታል"</string>
<string name="system_uses_camera" msgid="1911223105234441470">"ካሜራ የሥርዓት አገልግሎትን በመጠቀም ተደርሶበታል"</string>
@@ -495,7 +495,7 @@
<string name="permgroupupgraderequestdetail_sensors" msgid="6651914048792092835">"መተግበሪያውን በማይጠቀሙበት ጊዜም እንኳ ይህ መተግበሪያ የእርስዎን የመሠረታዊ ምልክቶች የዳሳሽ ውሂብን ሁልጊዜ መድረስ ይፈልጋል። ይህን ለውጥ ለማድረግ "<annotation id="link">"ወደ ቅንብሮች ይሂዱ።"</annotation></string>
<string name="permgroupbackgroundrequest_sensors" msgid="5661924322018503886">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; የሰውነትዎ መሠረታዊ ምልክቶች የዳሳሽ ውሂብ እንዲደርስ ይፈቀድለት?"</string>
<string name="permgroupbackgroundrequestdetail_sensors" msgid="7726767635834043501">"መተግበሪያውን በማይጠቀሙበት ጊዜ እንኳን ይህ መተግበሪያ የሰውነት ዳሳሽ ውሂብን ሁልጊዜ እንዲደርስ ለመፍቀድ "<annotation id="link">"ወደ ቅንብሮች ይሂዱ።"</annotation></string>
- <string name="permgroupupgraderequest_sensors" msgid="7576527638411370468">"መተግበሪያ ስራ ላይ በሚውልበት ጊዜ የሰውነት ዳሳሽ ውሂብን እንዲደርስ ለ&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; መፍቀድ ይቀጥሉ?"</string>
+ <string name="permgroupupgraderequest_sensors" msgid="7576527638411370468">"መተግበሪያ ሥራ ላይ በሚውልበት ጊዜ የሰውነት ዳሳሽ ውሂብን እንዲደርስ ለ&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; መፍቀድ ይቀጥሉ?"</string>
<string name="permgrouprequest_notifications" msgid="6396739062335106181">"&lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; ለእርስዎ ማሳወቂያዎች እንዲልክ ይፈቀድለት?"</string>
<string name="auto_granted_permissions" msgid="6009452264824455892">"ቁጥጥር የሚደረግባችድው ፈቃዶች"</string>
<string name="auto_granted_location_permission_notification_title" msgid="7570818224669050377">"<xliff:g id="APP_NAME">%1$s</xliff:g> የአካባቢ መዳረሻ አለው"</string>
@@ -544,14 +544,14 @@
<string name="remove_microphone_qs" msgid="1276551965129953198">"ለዚህ መተግበሪያ ፈቃድን አስወግድ"</string>
<string name="manage_service_qs" msgid="7862555549364153805">"አገልግሎት ያስተዳድሩ"</string>
<string name="manage_permissions_qs" msgid="3780541819763475434">"ፈቃዶችን ያስተዳድሩ"</string>
- <string name="active_call_usage_qs" msgid="8559974395932523391">"በስልክ ጥሪ ስራ ላይ እየዋለ ነው"</string>
- <string name="recent_call_usage_qs" msgid="743044899599410935">"በቅርብ ጊዜ በስልክ ጥሪ ውስጥ ስራ ላይ ውሏል"</string>
- <string name="active_app_usage_qs" msgid="4063912870936464727">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> ስራ ላይ እየዋለ ነው"</string>
- <string name="recent_app_usage_qs" msgid="6650259601306212327">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> ስራ ላይ ውሏል"</string>
- <string name="active_app_usage_1_qs" msgid="4325136375823357052">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ስራ ላይ እየዋለ ነው"</string>
- <string name="recent_app_usage_1_qs" msgid="261450184773310741">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ስራ ላይ ውሏል"</string>
- <string name="active_app_usage_2_qs" msgid="6107866785243565283">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ስራ ላይ እየዋለ ነው"</string>
- <string name="recent_app_usage_2_qs" msgid="3591205954235694403">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ስራ ላይ ውሏል"</string>
+ <string name="active_call_usage_qs" msgid="8559974395932523391">"በስልክ ጥሪ ሥራ ላይ እየዋለ ነው"</string>
+ <string name="recent_call_usage_qs" msgid="743044899599410935">"በቅርብ ጊዜ በስልክ ጥሪ ውስጥ ሥራ ላይ ውሏል"</string>
+ <string name="active_app_usage_qs" msgid="4063912870936464727">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> ሥራ ላይ እየዋለ ነው"</string>
+ <string name="recent_app_usage_qs" msgid="6650259601306212327">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> ሥራ ላይ ውሏል"</string>
+ <string name="active_app_usage_1_qs" msgid="4325136375823357052">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ሥራ ላይ እየዋለ ነው"</string>
+ <string name="recent_app_usage_1_qs" msgid="261450184773310741">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) ሥራ ላይ ውሏል"</string>
+ <string name="active_app_usage_2_qs" msgid="6107866785243565283">"በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ሥራ ላይ እየዋለ ነው"</string>
+ <string name="recent_app_usage_2_qs" msgid="3591205954235694403">"በቅርብ ጊዜ በ<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) ሥራ ላይ ውሏል"</string>
<string name="media_confirm_dialog_positive_button" msgid="9020793594051526399">"አረጋግጥ"</string>
<string name="media_confirm_dialog_negative_button" msgid="226987376924861785">"ተመለስ"</string>
<string name="media_confirm_dialog_title_a_to_p_aural_allow" msgid="8560601114044699903">"የሌሎች ፋይሎች መዳረሻም ይፈቀዳል"</string>
diff --git a/PermissionController/res/values-ar-v33/strings.xml b/PermissionController/res/values-ar-v33/strings.xml
index 9bfe9dc63..6b881f064 100644
--- a/PermissionController/res/values-ar-v33/strings.xml
+++ b/PermissionController/res/values-ar-v33/strings.xml
@@ -20,7 +20,7 @@
<string name="role_sms_request_description" msgid="1506966389698625395">"سيُسمح لهذا التطبيق بإرسال إشعارات إليك، وسيُمنح إذن الوصول إلى الكاميرا وجهات الاتصال والملفات والميكروفون والهاتف والرسائل القصيرة."</string>
<string name="permission_description_summary_storage" msgid="1917071243213043858">"يمكن للتطبيقات التي لديها هذا الإذن الوصول إلى جميع الملفات على هذا الجهاز."</string>
<string name="work_policy_title" msgid="832967780713677409">"معلومات سياسة العمل"</string>
- <string name="work_policy_summary" msgid="3886113358084963931">"يتولى مشرف تكنولوجيا المعلومات إدارة الإعدادات."</string>
+ <string name="work_policy_summary" msgid="3886113358084963931">"يتولى مشرف تكنولوجيا المعلومات إدارة الإعدادات"</string>
<string name="safety_center_entry_group_expand_action" msgid="5358289574941779652">"توسيع القائمة وعرضها"</string>
<string name="safety_center_entry_group_collapse_action" msgid="1525710152244405656">"تصغير القائمة وإخفاء الإعدادات"</string>
<string name="safety_center_entry_group_content_description" msgid="7048420958214443333">"قائمة <xliff:g id="ENTRY_TITLE">%1$s</xliff:g>. <xliff:g id="ENTRY_SUMMARY">%2$s</xliff:g>"</string>
diff --git a/PermissionController/res/values-ar/strings.xml b/PermissionController/res/values-ar/strings.xml
index dd77e27dc..92353fe85 100644
--- a/PermissionController/res/values-ar/strings.xml
+++ b/PermissionController/res/values-ar/strings.xml
@@ -246,7 +246,7 @@
<string name="app_permission_never_accessed_summary" msgid="401346181461975090">"لم يستخدم الإذن مطلقًا"</string>
<string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"تم الرفض / لم يسبق الحصول على الإذن"</string>
<string name="allowed_header" msgid="7769277978004790414">"التطبيقات المسموح لها"</string>
- <string name="allowed_always_header" msgid="6455903312589013545">"التطبيقات المسموح لها بالوصول طوال الوقت"</string>
+ <string name="allowed_always_header" msgid="6455903312589013545">"تطبيقات مسموح لها بالوصول طوال الوقت"</string>
<string name="allowed_foreground_header" msgid="6845655788447833353">"تطبيقات يمكنها الوصول عند استخدامها فقط"</string>
<string name="allowed_storage_scoped" msgid="5383645873719086975">"التطبيقات المسموح لها بالوصول إلى الوسائط فقط"</string>
<string name="allowed_storage_full" msgid="5356699280625693530">"التطبيقات المسموح لها بإدارة كل الملفات"</string>
@@ -340,7 +340,7 @@
<string name="no_apps_allowed" msgid="7718822655254468631">"لم يتم السماح لأي تطبيقات."</string>
<string name="no_apps_allowed_full" msgid="8011716991498934104">"ما من تطبيقات تم منحها إذن الوصول إلى جميع الملفات."</string>
<string name="no_apps_allowed_scoped" msgid="4908850477787659501">"ما من تطبيقات تم منحها إذن الوصول إلى الوسائط فقط."</string>
- <string name="no_apps_denied" msgid="7663435886986784743">"لم يتم رفض أي تطبيقات."</string>
+ <string name="no_apps_denied" msgid="7663435886986784743">"لم يتم رفض أي تطبيقات"</string>
<string name="car_permission_selected" msgid="180837028920791596">"مُختار"</string>
<string name="settings" msgid="5409109923158713323">"الإعدادات"</string>
<string name="accessibility_service_dialog_title_single" msgid="7956432823014102366">"تحظى خدمة <xliff:g id="SERVICE_NAME">%s</xliff:g> بوصول كامل إلى جهازك."</string>
diff --git a/PermissionController/res/values-in/strings.xml b/PermissionController/res/values-in/strings.xml
index 6bc772926..777933785 100644
--- a/PermissionController/res/values-in/strings.xml
+++ b/PermissionController/res/values-in/strings.xml
@@ -191,7 +191,7 @@
<string name="app_permission_button_always_allow_all" msgid="4905699259378428855">"Selalu izinkan semua"</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>
+ <string name="precise_image_description" msgid="6349638632303619872">"Lokasi presisi"</string>
<string name="approximate_image_description" msgid="938803699637069884">"Perkiraan lokasi"</string>
<string name="app_permission_location_accuracy" msgid="7166912915040018669">"Gunakan lokasi presisi"</string>
<string name="app_permission_location_accuracy_subtitle" msgid="2654077606404987210">"Saat lokasi presisi dinonaktifkan, aplikasi dapat mengakses perkiraan lokasi"</string>
diff --git a/PermissionController/res/values-it/strings.xml b/PermissionController/res/values-it/strings.xml
index 0e001aef4..5aaf447b0 100644
--- a/PermissionController/res/values-it/strings.xml
+++ b/PermissionController/res/values-it/strings.xml
@@ -338,7 +338,7 @@
<string name="no_permissions_allowed" msgid="6081976856354669209">"Nessuna autorizzazione consentita"</string>
<string name="no_permissions_denied" msgid="8159923922804043282">"Nessuna autorizzazione rifiutata"</string>
<string name="no_apps_allowed" msgid="7718822655254468631">"Nessuna app autorizzata"</string>
- <string name="no_apps_allowed_full" msgid="8011716991498934104">"Nessuna app consentita per tutti i file"</string>
+ <string name="no_apps_allowed_full" msgid="8011716991498934104">"Nessuna app autorizzata per tutti i file"</string>
<string name="no_apps_allowed_scoped" msgid="4908850477787659501">"Nessuna app autorizzata solo per i contenuti multimediali"</string>
<string name="no_apps_denied" msgid="7663435886986784743">"A nessuna app è stata negata l\'autorizzazione"</string>
<string name="car_permission_selected" msgid="180837028920791596">"Selezionato"</string>
diff --git a/PermissionController/res/values-ja/strings.xml b/PermissionController/res/values-ja/strings.xml
index b9016d2c4..06ee27a95 100644
--- a/PermissionController/res/values-ja/strings.xml
+++ b/PermissionController/res/values-ja/strings.xml
@@ -71,7 +71,7 @@
<string name="days_ago" msgid="6650359081551335629">"{count,plural, =0{今日}=1{1 日前}other{# 日前}}"</string>
<string name="app_disable_dlg_positive" msgid="7418444149981904940">"アプリを無効にする"</string>
<string name="app_disable_dlg_text" msgid="3126943217146120240">"このアプリを無効にすると、Android などの他のアプリが正しく動作しなくなるおそれがあります。このアプリはデバイスにプリインストールされているため、削除できません。無効にするには、このアプリをオフにし、デバイスにアプリが表示されないようにします。"</string>
- <string name="app_permission_manager" msgid="3903811137630909550">"権限マネージャー"</string>
+ <string name="app_permission_manager" msgid="3903811137630909550">"権限マネージャ"</string>
<string name="never_ask_again" msgid="4728762438198560329">"今後表示しない"</string>
<string name="no_permissions" msgid="3881676756371148563">"権限がありません"</string>
<string name="additional_permissions" msgid="5801285469338873430">"その他の権限"</string>
diff --git a/PermissionController/res/values-pt-rPT/strings.xml b/PermissionController/res/values-pt-rPT/strings.xml
index f2974b845..012608eee 100644
--- a/PermissionController/res/values-pt-rPT/strings.xml
+++ b/PermissionController/res/values-pt-rPT/strings.xml
@@ -353,41 +353,41 @@
<string name="role_browser_label" msgid="2877796144554070207">"App navegador predefinida"</string>
<string name="role_browser_short_label" msgid="6745009127123292296">"App de navegador"</string>
<string name="role_browser_description" msgid="3465253637499842671">"Apps que lhe dão acesso à Internet e apresentam links em que pode tocar."</string>
- <string name="role_browser_request_title" msgid="2895200507835937192">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de navegador predefinida?"</string>
+ <string name="role_browser_request_title" msgid="2895200507835937192">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de navegador predefinida?"</string>
<string name="role_browser_request_description" msgid="5888803407905985941">"Não são necessárias autorizações."</string>
<string name="role_dialer_label" msgid="1100224146343237968">"App de telefone predefinida"</string>
<string name="role_dialer_short_label" msgid="7186888549465352489">"App Telefone"</string>
<string name="role_dialer_description" msgid="8768708633696539612">"Apps que permitem efetuar e receber chamadas no seu dispositivo."</string>
- <string name="role_dialer_request_title" msgid="5959618560705912058">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de telefone predefinida?"</string>
+ <string name="role_dialer_request_title" msgid="5959618560705912058">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de telefone predefinida?"</string>
<string name="role_dialer_request_description" msgid="6288839625724909320">"Esta app fica com acesso à sua Câmara, Contactos, Microfone, Telefone e SMS"</string>
<string name="role_dialer_search_keywords" msgid="3324448983559188087">"telefone"</string>
<string name="role_sms_label" msgid="8456999857547686640">"App de SMS predefinida"</string>
<string name="role_sms_short_label" msgid="4371444488034692243">"App de SMS"</string>
<string name="role_sms_description" msgid="3424020199148153513">"Apps que permitem utilizar o seu número de telefone para enviar e receber mensagens de texto, fotos, vídeos e muito mais."</string>
- <string name="role_sms_request_title" msgid="7953552109601185602">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como app SMS predefinida?"</string>
+ <string name="role_sms_request_title" msgid="7953552109601185602">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como app SMS predefinida?"</string>
<string name="role_sms_request_description" msgid="2691004766132144886">"Esta app fica com acesso à sua Câmara, Contactos, Ficheiros e multimédia, Microfone, Telefone e SMS"</string>
<string name="role_sms_search_keywords" msgid="8022048144395047352">"mensagem de texto, enviar mensagens de texto, mensagens"</string>
<string name="role_emergency_label" msgid="7028825857206842366">"Aplicação de emergência pred."</string>
<string name="role_emergency_short_label" msgid="2388431453335350348">"Aplicação de emergência"</string>
<string name="role_emergency_description" msgid="5051840234887686630">"Apps que permitem registar as suas informações médicas e disponibilizá-las aos contactos de resposta a emergências, receber alertas acerca de eventos atmosféricos e desastres graves, bem como notificar outras pessoas quando precisar de ajuda."</string>
- <string name="role_emergency_request_title" msgid="8469579020654348567">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de emergência predefinida?"</string>
+ <string name="role_emergency_request_title" msgid="8469579020654348567">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de emergência predefinida?"</string>
<string name="role_emergency_request_description" msgid="131645948770262850">"Não são necessárias autorizações."</string>
<string name="role_emergency_search_keywords" msgid="1920007722599213358">"em caso de emergência"</string>
<string name="role_home_label" msgid="3871847846649769412">"App página inicial predefinida"</string>
<string name="role_home_short_label" msgid="8544733747952272337">"App Página inicial"</string>
<string name="role_home_description" msgid="7997371519626556675">"Apps, frequentemente denominadas iniciadores, que substituem os ecrãs principais no dispositivo Android e dão acesso aos conteúdos e às funcionalidades do seu dispositivo."</string>
- <string name="role_home_request_title" msgid="738136983453341081">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app Página inicial predefinida?"</string>
+ <string name="role_home_request_title" msgid="738136983453341081">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app Página inicial predefinida?"</string>
<string name="role_home_request_description" msgid="2658833966716057673">"Não são necessárias autorizações."</string>
<string name="role_home_search_keywords" msgid="3830755001192666285">"iniciador"</string>
<string name="role_call_redirection_label" msgid="5785304207206147590">"Aplic. redirec. chamadas pred."</string>
<string name="role_call_redirection_short_label" msgid="7568143419571217757">"Aplic. de redirec. de chamadas"</string>
<string name="role_call_redirection_description" msgid="6091669882014664420">"Apps que permitem encaminhar chamadas efetuadas para outro número de telefone."</string>
- <string name="role_call_redirection_request_title" msgid="2816244455003562925">"Pretende definir <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de redirecionamento de chamadas predefinida?"</string>
+ <string name="role_call_redirection_request_title" msgid="2816244455003562925">"Quer definir <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de redirecionamento de chamadas predefinida?"</string>
<string name="role_call_redirection_request_description" msgid="3118895714178527164">"Não são necessárias autorizações."</string>
<string name="role_call_screening_label" msgid="883935222060878724">"App de filtro de chamadas e spam"</string>
<string name="role_call_screening_short_label" msgid="2048465565063130834">"App de ID de chamada e spam"</string>
<string name="role_call_screening_description" msgid="2349431420497468981">"Apps que lhe permitem identificar chamadas e bloquear spam, chamadas automáticas ou números indesejados."</string>
- <string name="role_call_screening_request_title" msgid="7358309224566977290">"Pretende definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de identificação de chamadas e spam predefinida?"</string>
+ <string name="role_call_screening_request_title" msgid="7358309224566977290">"Quer definir o <xliff:g id="APP_NAME">%1$s</xliff:g> como a app de identificação de chamadas e spam predefinida?"</string>
<string name="role_call_screening_request_description" msgid="7338511921032446006">"Não são necessárias autorizações."</string>
<string name="role_automotive_navigation_label" msgid="2701890757955474751">"App de navegação predefinida"</string>
<string name="role_automotive_navigation_short_label" msgid="5165823092506922457">"App de navegação"</string>
@@ -437,11 +437,11 @@
<string name="special_app_access_no_apps" msgid="4102911722787886970">"Sem apps"</string>
<string name="home_missing_work_profile_support" msgid="1756855847669387977">"Não suporta o perfil de trabalho."</string>
<string name="encryption_unaware_confirmation_message" msgid="8274491794636402484">"Nota: se reiniciar o dispositivo e tiver um bloqueio de ecrã definido, só é possível iniciar esta app quando o dispositivo for desbloqueado."</string>
- <string name="assistant_confirmation_message" msgid="7476540402884416212">"O assistente pode ler informações sobre aplicações em utilização no seu sistema, incluindo informações visíveis no ecrã ou acessíveis nas aplicações."</string>
+ <string name="assistant_confirmation_message" msgid="7476540402884416212">"O assistente pode ler informações sobre as apps em utilização no seu sistema, incluindo informações visíveis no ecrã ou acessíveis nas apps."</string>
<string name="incident_report_channel_name" msgid="3144954065936288440">"Partilhar dados de depuração"</string>
- <string name="incident_report_notification_title" msgid="4635984625656519773">"Pretende partilhar dados de depuração detalhados?"</string>
- <string name="incident_report_notification_text" msgid="3376480583513587923">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> pretende carregar informações de depuração."</string>
- <string name="incident_report_dialog_title" msgid="669104389325204095">"Pretende partilhar dados de depuração?"</string>
+ <string name="incident_report_notification_title" msgid="4635984625656519773">"Quer partilhar dados de depuração detalhados?"</string>
+ <string name="incident_report_notification_text" msgid="3376480583513587923">"A app <xliff:g id="APP_NAME">%1$s</xliff:g> quer carregar informações de depuração."</string>
+ <string name="incident_report_dialog_title" msgid="669104389325204095">"Quer partilhar dados de depuração?"</string>
<string name="incident_report_dialog_intro" msgid="5897733669850951832">"O sistema detetou um problema."</string>
<string name="incident_report_dialog_text" msgid="5675553296891757523">"A app <xliff:g id="APP_NAME_0">%1$s</xliff:g> está a solicitar o carregamento de um relatório de erro a partir deste dispositivo realizado a <xliff:g id="DATE">%2$s</xliff:g> à(s) <xliff:g id="TIME">%3$s</xliff:g>. Os relatórios de erros incluem informações pessoais acerca do seu dispositivo ou registadas por app, por exemplo, nomes de utilizador, dados de localização, identificadores do dispositivo e informações da rede. Apenas partilhe relatórios de erros com pessoas e apps nas quais confia. Permite que a app <xliff:g id="APP_NAME_1">%4$s</xliff:g> carregue um relatório de erro?"</string>
<string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Ocorreu um erro ao processar o relatório de erro para a app <xliff:g id="APP_NAME">%1$s</xliff:g>. Como tal, a partilha dos dados de depuração detalhados foi negada. Pedimos desculpa pela interrupção."</string>
@@ -460,8 +460,8 @@
<string name="permgrouprequestdetail_location" msgid="2635935335778429894">"A app tem acesso à localização apenas enquanto a estiver a utilizar"</string>
<string name="permgroupbackgroundrequest_location" msgid="1085680897265734809">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda à localização deste dispositivo?"</string>
<string name="permgroupbackgroundrequestdetail_location" msgid="8021219324989662957">"Esta app poderá pretender aceder sempre à sua localização, mesmo quando não a estiver a utilizar. "<annotation id="link">"Permita-o nas definições."</annotation></string>
- <string name="permgroupupgraderequest_location" msgid="8328408946822691636">"Pretende alterar o acesso à localização para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupupgraderequestdetail_location" msgid="1550899076845189165">"Esta app pretende aceder sempre à sua localização, mesmo quando não a estiver a utilizar. "<annotation id="link">"Permita-o nas definições."</annotation></string>
+ <string name="permgroupupgraderequest_location" msgid="8328408946822691636">"Quer alterar o acesso à localização para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
+ <string name="permgroupupgraderequestdetail_location" msgid="1550899076845189165">"Esta app quer aceder sempre à sua localização, mesmo quando não a estiver a utilizar. "<annotation id="link">"Permita-o nas definições."</annotation></string>
<string name="permgrouprequest_nearby_devices" msgid="2272829282660436700">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; encontre, determine a posição relativa dos dispositivos próximos e se ligue aos mesmos?"</string>
<string name="permgroupupgraderequestdetail_nearby_devices" msgid="6877531270654738614">"Permitir que &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; encontre, determine a posição relativa dos dispositivos próximos e se ligue aos mesmos? "<annotation id="link">"Permita nas Definições."</annotation></string>
<string name="permgrouprequest_fineupgrade" msgid="2334242928821697672">"Alterar o acesso à localização da app <xliff:g id="APP_NAME">&lt;b&gt;%1$s&lt;/b&gt;</xliff:g> de aproximada para exata?"</string>
@@ -480,15 +480,15 @@
<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>
<string name="permgroupbackgroundrequestdetail_microphone" msgid="553702902263681838">"Esta app pode pretender gravar áudio sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
- <string name="permgroupupgraderequest_microphone" msgid="1362781696161233341">"Pretende alterar o acesso ao microfone para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupupgraderequestdetail_microphone" msgid="2870497719571464239">"Esta app pretende gravar áudio sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
+ <string name="permgroupupgraderequest_microphone" msgid="1362781696161233341">"Quer alterar o acesso ao microfone para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
+ <string name="permgroupupgraderequestdetail_microphone" msgid="2870497719571464239">"Esta app quer gravar áudio sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
<string name="permgrouprequest_activityRecognition" msgid="5415121592794230330">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda à sua atividade física?"</string>
<string name="permgrouprequest_camera" msgid="5123097035410002594">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tire fotos e grave vídeo?"</string>
<string name="permgrouprequestdetail_camera" msgid="9085323239764667883">"A app apenas poderá tirar fotos e gravar vídeos enquanto a estiver a utilizar."</string>
<string name="permgroupbackgroundrequest_camera" msgid="1274286575704213875">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; tire fotos e grave vídeo?"</string>
<string name="permgroupbackgroundrequestdetail_camera" msgid="4458783509089859078">"Esta app pode pretender tirar fotos e gravar vídeos sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
- <string name="permgroupupgraderequest_camera" msgid="640758449200241582">"Pretende alterar o acesso à câmara para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
- <string name="permgroupupgraderequestdetail_camera" msgid="6642747548010962597">"Esta app pretende tirar fotos e gravar vídeos sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
+ <string name="permgroupupgraderequest_camera" msgid="640758449200241582">"Quer alterar o acesso à câmara para a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt;?"</string>
+ <string name="permgroupupgraderequestdetail_camera" msgid="6642747548010962597">"Esta app quer tirar fotos e gravar vídeos sempre, mesmo quando não a está a utilizar. "<annotation id="link">"Permita-o nas Definições."</annotation></string>
<string name="permgrouprequest_calllog" msgid="2065327180175371397">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda aos registos de chamadas do seu telemóvel?"</string>
<string name="permgrouprequest_phone" msgid="1829234136997316752">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; faça e gira chamadas telefónicas?"</string>
<string name="permgrouprequest_sensors" msgid="4397358316850652235">"Permitir que a app &lt;b&gt;<xliff:g id="APP_NAME">%1$s</xliff:g>&lt;/b&gt; aceda aos dados de sensores acerca dos seus sinais vitais?"</string>
diff --git a/PermissionController/res/values-ur/strings.xml b/PermissionController/res/values-ur/strings.xml
index b54333835..4cb926518 100644
--- a/PermissionController/res/values-ur/strings.xml
+++ b/PermissionController/res/values-ur/strings.xml
@@ -547,7 +547,7 @@
<string name="active_call_usage_qs" msgid="8559974395932523391">"فون کال کے ذریعے استعمال کیا جا رہا ہے"</string>
<string name="recent_call_usage_qs" msgid="743044899599410935">"فون کال میں حال ہی میں استعمال کیا گیا"</string>
<string name="active_app_usage_qs" msgid="4063912870936464727">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے ذریعے استعمال کیا جا رہا ہے"</string>
- <string name="recent_app_usage_qs" msgid="6650259601306212327">"<xliff:g id="APP_NAME">%1$s</xliff:g> کے ذریعے حال ہی میں استعمال کیا گیا"</string>
+ <string name="recent_app_usage_qs" msgid="6650259601306212327">"حال ہی میں <xliff:g id="APP_NAME">%1$s</xliff:g> نے استعمال کیا"</string>
<string name="active_app_usage_1_qs" msgid="4325136375823357052">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) کے ذریعے استعمال کیا جا رہا ہے"</string>
<string name="recent_app_usage_1_qs" msgid="261450184773310741">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g>) کے ذریعے حال ہی میں استعمال کیا گیا"</string>
<string name="active_app_usage_2_qs" msgid="6107866785243565283">"<xliff:g id="APP_NAME">%1$s</xliff:g> (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>) کے ذریعے استعمال کیا جا رہا ہے"</string>
diff --git a/PermissionController/res/values-zh-rCN/strings.xml b/PermissionController/res/values-zh-rCN/strings.xml
index e5e37f7e6..46a66b59e 100644
--- a/PermissionController/res/values-zh-rCN/strings.xml
+++ b/PermissionController/res/values-zh-rCN/strings.xml
@@ -603,7 +603,7 @@
<string name="permission_rationale_purpose_advertising" msgid="7156966429245180236">"广告或营销"</string>
<string name="permission_rationale_purpose_fraud_prevention_security" msgid="4262104770357031902">"欺诈防范、安全和法规遵从"</string>
<string name="permission_rationale_purpose_personalization" msgid="1589973273682238708">"个性化"</string>
- <string name="permission_rationale_purpose_account_management" msgid="2985772421946688879">"帐号管理"</string>
+ <string name="permission_rationale_purpose_account_management" msgid="2985772421946688879">"账号管理"</string>
<string name="app_permission_rationale_message" msgid="8511466916077100713">"数据安全"</string>
<string name="app_location_permission_rationale_title" msgid="925420340572401350">"可能会分享位置数据"</string>
<string name="app_location_permission_rationale_subtitle" msgid="6986985722752868692">"此应用已声明它可能会与第三方分享您的位置数据"</string>
diff --git a/PermissionController/res/values/colors.xml b/PermissionController/res/values/colors.xml
index e54edb506..b1d285104 100644
--- a/PermissionController/res/values/colors.xml
+++ b/PermissionController/res/values/colors.xml
@@ -32,4 +32,7 @@
<!-- overviewBackground is not visible from mainline, so UX provided this alternative.
system_neutral2_200 is v31+ so use this placeholder provided by ux -->
<color name="permission_rationale_overview_background">#DADCE0</color>
+
+ <!-- Wear related colors -->
+ <color name="wear_material_gray_600">#FF80868B</color>
</resources>
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
index cd1431eb5..39d127cfd 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
@@ -78,7 +78,6 @@ import com.android.permissioncontroller.permission.ui.handheld.v31.PermissionUsa
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;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.PermissionMapping;
import com.android.permissioncontroller.permission.utils.Utils;
@@ -376,8 +375,6 @@ public final class ManagePermissionsActivity extends SettingsActivity {
androidXFragment = AutoAppPermissionsFragment.newInstance(packageName,
userHandle, sessionId, /* isSystemPermsScreen= */ true);
}
- } else if (DeviceUtils.isWear(this)) {
- androidXFragment = AppPermissionsFragmentWear.newInstance(packageName);
} else if (DeviceUtils.isTelevision(this)) {
androidXFragment = com.android.permissioncontroller.permission.ui.television
.AppPermissionsFragment.newInstance(packageName, userHandle);
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/AppPermissionsFragmentWear.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/AppPermissionsFragmentWear.java
deleted file mode 100644
index 843514b4a..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/AppPermissionsFragmentWear.java
+++ /dev/null
@@ -1,407 +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 com.android.permissioncontroller.permission.ui.wear;
-
-import android.app.Activity;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.View;
-import android.widget.Toast;
-
-import androidx.fragment.app.Fragment;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceFragmentCompat;
-import androidx.preference.SwitchPreference;
-import androidx.wear.ble.view.WearableDialogHelper;
-
-import com.android.permissioncontroller.R;
-import com.android.permissioncontroller.permission.model.AppPermissionGroup;
-import com.android.permissioncontroller.permission.model.AppPermissions;
-import com.android.permissioncontroller.permission.model.Permission;
-import com.android.permissioncontroller.permission.utils.ArrayUtils;
-import com.android.permissioncontroller.permission.utils.LocationUtils;
-import com.android.permissioncontroller.permission.utils.Utils;
-import com.android.permissioncontroller.permission.utils.legacy.LegacySafetyNetLogger;
-import com.android.settingslib.RestrictedLockUtils;
-import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public final class AppPermissionsFragmentWear extends PreferenceFragmentCompat {
- private static final String LOG_TAG = "AppPermFragWear";
-
- private static final String KEY_NO_PERMISSIONS = "no_permissions";
-
- public static AppPermissionsFragmentWear newInstance(String packageName) {
- return setPackageName(new AppPermissionsFragmentWear(), packageName);
- }
-
- private static <T extends Fragment> T setPackageName(T fragment, String packageName) {
- Bundle arguments = new Bundle();
- arguments.putString(Intent.EXTRA_PACKAGE_NAME, packageName);
- fragment.setArguments(arguments);
- return fragment;
- }
-
- private PackageManager mPackageManager;
- private ArraySet<AppPermissionGroup> mToggledGroups;
- private AppPermissions mAppPermissions;
-
- private boolean mHasConfirmedRevoke;
-
- /**
- * Provides click behavior for disabled preferences.
- */
- private static class PermissionSwitchPreference extends SwitchPreference {
-
- private final Activity mActivity;
-
- public PermissionSwitchPreference(Activity activity) {
- super(activity);
- this.mActivity = activity;
- }
-
- @Override
- protected void performClick(View view) {
- super.performClick(view);
-
- if (!isEnabled()) {
- // If setting the permission is disabled, it must have been locked
- // by the device or profile owner. So get that info and pass it to
- // the support details dialog.
- EnforcedAdmin deviceOrProfileOwner = RestrictedLockUtils.getProfileOrDeviceOwner(
- mActivity, UserHandle.of(UserHandle.myUserId()));
- RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
- mActivity, deviceOrProfileOwner);
- }
- }
- }
-
- @Override
- public void onCreatePreferences(Bundle bundle, String s) {
- // empty
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- String packageName = getArguments().getString(Intent.EXTRA_PACKAGE_NAME);
- Activity activity = getActivity();
- mPackageManager = activity.getPackageManager();
-
- PackageInfo packageInfo;
-
- try {
- packageInfo = mPackageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
- } catch (PackageManager.NameNotFoundException e) {
- Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e);
- packageInfo = null;
- }
-
- if (packageInfo == null) {
- Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show();
- activity.finish();
- return;
- }
-
- mAppPermissions = new AppPermissions(
- activity, packageInfo, true, () -> getActivity().finish());
-
- addPreferencesFromResource(R.xml.watch_permissions);
- initializePermissionGroupList();
- }
-
- @Override
- public void onResume() {
- super.onResume();
- mAppPermissions.refresh();
-
- // Also refresh the UI
- for (final AppPermissionGroup group : mAppPermissions.getPermissionGroups()) {
- if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())) {
- for (PermissionInfo perm : getPermissionInfosFromGroup(group)) {
- setPreferenceCheckedIfPresent(perm.name,
- group.areRuntimePermissionsGranted(new String[]{ perm.name }));
- }
- } else {
- setPreferenceCheckedIfPresent(group.getName(), group.areRuntimePermissionsGranted());
- }
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
- logAndClearToggledGroups();
- }
-
- private void initializePermissionGroupList() {
- List<AppPermissionGroup> groups = mAppPermissions.getPermissionGroups();
- List<SwitchPreference> nonSystemPreferences = new ArrayList<>();
-
- if (!groups.isEmpty()) {
- getPreferenceScreen().removePreference(findPreference(KEY_NO_PERMISSIONS));
- }
-
- for (final AppPermissionGroup group : groups) {
- if (!Utils.shouldShowPermission(getContext(), group)) {
- continue;
- }
-
- boolean isPlatform = group.getDeclaringPackage().equals(Utils.OS_PKG);
-
- if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())) {
- // If permission is controlled individually, we show all requested permission
- // inside this group.
- for (PermissionInfo perm : getPermissionInfosFromGroup(group)) {
- final SwitchPreference pref = createSwitchPreferenceForPermission(group, perm);
- showOrAddToNonSystemPreferences(pref, nonSystemPreferences, isPlatform);
- }
- } else {
- final SwitchPreference pref = createSwitchPreferenceForGroup(group);
- showOrAddToNonSystemPreferences(pref, nonSystemPreferences, isPlatform);
- }
- }
-
- // Now add the non-system settings to the end of the list
- for (SwitchPreference nonSystemPreference : nonSystemPreferences) {
- getPreferenceScreen().addPreference(nonSystemPreference);
- }
- }
-
- private void showOrAddToNonSystemPreferences(SwitchPreference pref,
- List<SwitchPreference> nonSystemPreferences, // Mutate
- boolean isPlatform) {
- // The UI shows System settings first, then non-system settings
- if (isPlatform) {
- getPreferenceScreen().addPreference(pref);
- } else {
- nonSystemPreferences.add(pref);
- }
- }
-
- private SwitchPreference createSwitchPreferenceForPermission(AppPermissionGroup group,
- PermissionInfo perm) {
- final SwitchPreference pref = new PermissionSwitchPreference(getActivity());
- pref.setKey(perm.name);
- pref.setTitle(perm.loadLabel(mPackageManager));
- pref.setChecked(group.areRuntimePermissionsGranted(new String[]{ perm.name }));
- pref.setOnPreferenceChangeListener((p, newVal) -> {
- if((Boolean) newVal) {
- group.grantRuntimePermissions(true, false, new String[]{ perm.name });
-
- if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())
- && group.doesSupportRuntimePermissions()) {
- // We are granting a permission from a group but since this is an
- // individual permission control other permissions in the group may
- // be revoked, hence we need to mark them user fixed to prevent the
- // app from requesting a non-granted permission and it being granted
- // because another permission in the group is granted. This applies
- // only to apps that support runtime permissions.
- String[] revokedPermissionsToFix = null;
- final int permissionCount = group.getPermissions().size();
-
- for (int i = 0; i < permissionCount; i++) {
- Permission current = group.getPermissions().get(i);
- if (!current.isGranted() && !current.isUserFixed()) {
- revokedPermissionsToFix = ArrayUtils.appendString(
- revokedPermissionsToFix, current.getName());
- }
- }
-
- if (revokedPermissionsToFix != null) {
- // If some permissions were not granted then they should be fixed.
- group.revokeRuntimePermissions(true, revokedPermissionsToFix);
- }
- }
- } else {
- final Permission appPerm = getPermissionFromGroup(group, perm.name);
- if (appPerm == null) {
- return false;
- }
-
- final boolean grantedByDefault = appPerm.isGrantedByDefault();
- if (grantedByDefault
- || (!group.doesSupportRuntimePermissions() && !mHasConfirmedRevoke)) {
- showRevocationWarningDialog(
- (dialog, which) -> {
- revokePermissionInGroup(group, perm.name);
- pref.setChecked(false);
- if (!appPerm.isGrantedByDefault()) {
- mHasConfirmedRevoke = true;
- }
- },
- grantedByDefault
- ? R.string.system_warning
- : R.string.old_sdk_deny_warning);
- return false;
- } else {
- revokePermissionInGroup(group, perm.name);
- }
- }
-
- return true;
- });
- return pref;
- }
-
- private void showRevocationWarningDialog(
- DialogInterface.OnClickListener confirmListener,
- int warningMessageId) {
- new WearableDialogHelper.DialogBuilder(getContext())
- .setNegativeIcon(R.drawable.confirm_button)
- .setPositiveIcon(R.drawable.cancel_button)
- .setNegativeButton(R.string.grant_dialog_button_deny_anyway, confirmListener)
- .setPositiveButton(R.string.cancel, null)
- .setMessage(warningMessageId)
- .show();
- }
-
- private static Permission getPermissionFromGroup(AppPermissionGroup group, String permName) {
- final int permissionCount = group.getPermissions().size();
-
- for (int i = 0; i < permissionCount; i++) {
- Permission currentPerm = group.getPermissions().get(i);
- if(currentPerm.getName().equals(permName)) {
- return currentPerm;
- };
- }
-
- if ("user".equals(Build.TYPE)) {
- Log.e(LOG_TAG, String.format("The impossible happens, permission %s is not in group %s.",
- permName, group.getName()));
- return null;
- } else {
- // This is impossible, throw a fatal error in non-user build.
- throw new IllegalArgumentException(
- String.format("Permission %s is not in group %s", permName, group.getName()));
- }
- }
-
- private void revokePermissionInGroup(AppPermissionGroup group, String permName) {
- group.revokeRuntimePermissions(true, new String[]{ permName });
-
- if (Utils.areGroupPermissionsIndividuallyControlled(getContext(), group.getName())
- && group.doesSupportRuntimePermissions()
- && !group.areRuntimePermissionsGranted()) {
- // If we just revoked the last permission we need to clear
- // the user fixed state as now the app should be able to
- // request them at runtime if supported.
- group.revokeRuntimePermissions(false);
- }
- }
-
- private SwitchPreference createSwitchPreferenceForGroup(AppPermissionGroup group) {
- final SwitchPreference pref = new PermissionSwitchPreference(getActivity());
-
- pref.setKey(group.getName());
- pref.setTitle(group.getLabel());
- pref.setChecked(group.areRuntimePermissionsGranted());
-
- if (group.isSystemFixed() || group.isPolicyFixed()) {
- pref.setEnabled(false);
- } else {
- pref.setOnPreferenceChangeListener((p, newVal) -> {
- if (LocationUtils.isLocationGroupAndProvider(getContext(),
- group.getName(), group.getApp().packageName)) {
- LocationUtils.showLocationDialog(
- getContext(), mAppPermissions.getAppLabel());
- return false;
- }
-
- if ((Boolean) newVal) {
- setPermission(group, pref, true);
- } else {
- final boolean grantedByDefault = group.hasGrantedByDefaultPermission();
- if (grantedByDefault
- || (!group.doesSupportRuntimePermissions() && !mHasConfirmedRevoke)) {
- showRevocationWarningDialog(
- (dialog, which) -> {
- setPermission(group, pref, false);
- if (!group.hasGrantedByDefaultPermission()) {
- mHasConfirmedRevoke = true;
- }
- },
- grantedByDefault
- ? R.string.system_warning
- : R.string.old_sdk_deny_warning);
- return false;
- } else {
- setPermission(group, pref, false);
- }
- }
-
- return true;
- });
- }
- return pref;
- }
-
- private void setPermission(AppPermissionGroup group, SwitchPreference pref, boolean grant) {
- if (grant) {
- group.grantRuntimePermissions(true, false);
- } else {
- group.revokeRuntimePermissions(false);
- }
- addToggledGroup(group);
- pref.setChecked(grant);
- }
-
- private void addToggledGroup(AppPermissionGroup group) {
- if (mToggledGroups == null) {
- mToggledGroups = new ArraySet<>();
- }
-
- mToggledGroups.add(group);
- }
-
- private void logAndClearToggledGroups() {
- if (mToggledGroups != null) {
- LegacySafetyNetLogger.logPermissionsToggled(mToggledGroups);
- mToggledGroups = null;
- }
- }
-
- private List<PermissionInfo> getPermissionInfosFromGroup(AppPermissionGroup group) {
- ArrayList<PermissionInfo> permInfos = new ArrayList<>(group.getPermissions().size());
- for(Permission perm : group.getPermissions()) {
- try {
- permInfos.add(mPackageManager.getPermissionInfo(perm.getName(), 0));
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(LOG_TAG, "No permission:" + perm.getName());
- }
- }
- return permInfos;
- }
-
- private void setPreferenceCheckedIfPresent(String preferenceKey, boolean checked) {
- Preference pref = findPreference(preferenceKey);
- if (pref instanceof SwitchPreference) {
- ((SwitchPreference) pref).setChecked(checked);
- }
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsFragment.kt
new file mode 100644
index 000000000..cf48805dc
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsFragment.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.wear
+
+import android.app.Activity
+import android.content.Intent
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.os.UserHandle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.compose.ui.platform.ComposeView
+import androidx.core.os.BundleCompat
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.model.AppPermissions
+import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel
+import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModelFactory
+import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionGroupsRevokeDialogViewModel
+import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionGroupsRevokeDialogViewModelFactory
+
+
+class WearAppPermissionGroupsFragment : Fragment() {
+ private lateinit var helper: WearAppPermissionGroupsHelper
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val packageName = arguments?.getString(Intent.EXTRA_PACKAGE_NAME) ?: ""
+ val user = arguments?.let {
+ BundleCompat.getParcelable(it, Intent.EXTRA_USER, UserHandle::class.java)!!
+ } ?: UserHandle.SYSTEM
+
+ val activity: Activity = requireActivity()
+ val packageManager = activity.packageManager
+
+ val packageInfo: PackageInfo? = try {
+ packageManager.getPackageInfo(
+ packageName,
+ PackageManager.GET_PERMISSIONS
+ )
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.i(LOG_TAG, "No package:" + activity.getCallingPackage(), e)
+ null
+ }
+
+ if (packageInfo == null) {
+ Toast.makeText(activity, R.string.app_not_found_dlg_title, Toast.LENGTH_LONG).show()
+ activity.finish()
+ return null
+ }
+ val sessionId = arguments?.getLong(EXTRA_SESSION_ID, 0) ?: 0
+ val appPermissions = AppPermissions(activity, packageInfo, true, { activity.finish() })
+ val factory = AppPermissionGroupsViewModelFactory(
+ packageName,
+ user,
+ sessionId
+ )
+ val viewModel =
+ ViewModelProvider(this, factory).get(AppPermissionGroupsViewModel::class.java)
+ val revokeDialogViewModel =
+ ViewModelProvider(this, AppPermissionGroupsRevokeDialogViewModelFactory()).get(
+ AppPermissionGroupsRevokeDialogViewModel::class.java
+ )
+ helper = WearAppPermissionGroupsHelper(
+ context = requireContext(),
+ fragment = this,
+ user = user,
+ sessionId = sessionId,
+ appPermissions = appPermissions,
+ viewModel = viewModel,
+ revokeDialogViewModel = revokeDialogViewModel
+ )
+
+ return ComposeView(activity).apply {
+ setContent {
+ WearAppPermissionGroupsScreen(
+ helper
+ )
+ }
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ helper.logAndClearToggledGroups()
+ }
+
+ companion object {
+ const val LOG_TAG = "WearAppPermissionGroups"
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsHelper.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsHelper.kt
new file mode 100644
index 000000000..a2b4ffa87
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsHelper.kt
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.wear
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.PermissionInfo
+import android.os.Build
+import android.os.UserHandle
+import android.util.ArraySet
+import android.util.Log
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.hibernation.isHibernationEnabled
+import com.android.permissioncontroller.permission.model.AppPermissionGroup
+import com.android.permissioncontroller.permission.model.AppPermissions
+import com.android.permissioncontroller.permission.model.Permission
+import com.android.permissioncontroller.permission.model.livedatatypes.HibernationSettingState
+import com.android.permissioncontroller.permission.ui.Category
+import com.android.permissioncontroller.permission.ui.LocationProviderInterceptDialog
+import com.android.permissioncontroller.permission.ui.handheld.AppPermissionFragment
+import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel
+import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel.GroupUiInfo
+import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel.PermSubtitle
+import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionGroupsRevokeDialogViewModel
+import com.android.permissioncontroller.permission.ui.wear.model.RevokeDialogArgs
+import com.android.permissioncontroller.permission.utils.ArrayUtils
+import com.android.permissioncontroller.permission.utils.LocationUtils
+import com.android.permissioncontroller.permission.utils.Utils
+import com.android.permissioncontroller.permission.utils.legacy.LegacySafetyNetLogger
+import com.android.permissioncontroller.permission.utils.navigateSafe
+
+class WearAppPermissionGroupsHelper(
+ val context: Context,
+ val fragment: Fragment,
+ val user: UserHandle,
+ val sessionId: Long,
+ private val appPermissions: AppPermissions,
+ val viewModel: AppPermissionGroupsViewModel,
+ val revokeDialogViewModel: AppPermissionGroupsRevokeDialogViewModel,
+ private val toggledGroups: ArraySet<AppPermissionGroup> = ArraySet()
+) {
+ fun getPermissionGroupChipParams(): List<PermissionGroupChipParam> {
+ if (DEBUG) {
+ Log.d(TAG, "getPermissionGroupChipParams() called")
+ }
+ val groupUiInfos = viewModel.packagePermGroupsLiveData.value
+ val groups: List<AppPermissionGroup> = appPermissions.permissionGroups
+
+ val grantedTypes: MutableMap<String, Category> = HashMap()
+ val bookKeeping: MutableMap<String, GroupUiInfo> = HashMap()
+ if (groupUiInfos != null) {
+ for (category in groupUiInfos.keys) {
+ val groupInfoList: List<GroupUiInfo> = groupUiInfos[category] ?: emptyList()
+ for (groupInfo in groupInfoList) {
+ bookKeeping[groupInfo.groupName] = groupInfo
+ grantedTypes[groupInfo.groupName] = category
+ }
+ }
+ }
+
+ val list: MutableList<PermissionGroupChipParam> = ArrayList()
+
+ groups.filter { Utils.shouldShowPermission(context, it) }
+ .partition { it.declaringPackage == Utils.OS_PKG }
+ .let { it.first.plus(it.second) }.forEach { group ->
+ if (Utils.areGroupPermissionsIndividuallyControlled(context, group.name)) {
+ // If permission is controlled individually, we show all requested permission
+ // inside this group.
+ for (perm in getPermissionInfosFromGroup(group)) {
+ list.add(PermissionGroupChipParam(
+ group = group,
+ perm = perm,
+ label = perm.loadLabel(context.packageManager).toString(),
+ checked = group.areRuntimePermissionsGranted(arrayOf(perm.name)),
+ onCheckedChanged = { checked ->
+ run {
+ onPermissionGrantedStateChanged(group, perm, checked)
+ }
+ }
+ ))
+ }
+ } else {
+ val category = grantedTypes[group.name]
+ if (category != null) {
+ list.add(
+ PermissionGroupChipParam(
+ group = group,
+ label = group.label.toString(),
+ summary = bookKeeping[group.name]?.let {
+ getSummary(category, it.subtitle)
+ },
+ onClick = {
+ onPermissionGroupClicked(group, category.categoryName)
+ }
+ )
+ )
+ }
+ }
+ }
+ return list
+ }
+
+ private fun getSummary(
+ category: Category?,
+ subtitle: PermSubtitle
+ ): Int? {
+ if (category != null) {
+ when (category) {
+ Category.ALLOWED -> return R.string.allowed_header
+ Category.ASK -> return R.string.ask_header
+ Category.DENIED -> return R.string.denied_header
+ else -> { /* Fallback though */ }
+ }
+ }
+ return when (subtitle) {
+ PermSubtitle.FOREGROUND_ONLY -> R.string.permission_subtitle_only_in_foreground
+ PermSubtitle.MEDIA_ONLY -> R.string.permission_subtitle_media_only
+ PermSubtitle.ALL_FILES -> R.string.permission_subtitle_all_files
+ else -> null
+ }
+ }
+
+ private fun getPermissionInfosFromGroup(group: AppPermissionGroup): List<PermissionInfo> =
+ group.permissions.map {
+ it?.let {
+ try {
+ context.packageManager.getPermissionInfo(it.name, 0)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "No permission:" + it.name)
+ null
+ }
+ }
+ }.filterNotNull().toList()
+
+ private fun onPermissionGrantedStateChanged(
+ group: AppPermissionGroup,
+ perm: PermissionInfo,
+ checked: Boolean
+ ) {
+ if (checked) {
+ group.grantRuntimePermissions(true, false, arrayOf(perm.name))
+
+ if (Utils.areGroupPermissionsIndividuallyControlled(context, group.name) &&
+ group.doesSupportRuntimePermissions()
+ ) {
+ // We are granting a permission from a group but since this is an
+ // individual permission control other permissions in the group may
+ // be revoked, hence we need to mark them user fixed to prevent the
+ // app from requesting a non-granted permission and it being granted
+ // because another permission in the group is granted. This applies
+ // only to apps that support runtime permissions.
+ var revokedPermissionsToFix: Array<String?>? = null
+ val permissionCount = group.permissions.size
+ for (i in 0 until permissionCount) {
+ val current = group.permissions[i]
+ if (!current.isGranted && !current.isUserFixed) {
+ revokedPermissionsToFix = ArrayUtils.appendString(
+ revokedPermissionsToFix, current.name
+ )
+ }
+ }
+ if (revokedPermissionsToFix != null) {
+ // If some permissions were not granted then they should be fixed.
+ group.revokeRuntimePermissions(true, revokedPermissionsToFix)
+ }
+ }
+ } else {
+ val appPerm: Permission = getPermissionFromGroup(group, perm.name) ?: return
+
+ val grantedByDefault = appPerm.isGrantedByDefault
+ if (grantedByDefault ||
+ (!group.doesSupportRuntimePermissions() &&
+ !revokeDialogViewModel.hasConfirmedRevoke)) {
+ showRevocationWarningDialog(
+ messageId = if (grantedByDefault) {
+ R.string.system_warning
+ } else {
+ R.string.old_sdk_deny_warning
+ },
+ onOkButtonClick = {
+ revokePermissionInGroup(group, perm.name)
+ if (!appPerm.isGrantedByDefault) {
+ revokeDialogViewModel.hasConfirmedRevoke = true
+ }
+ revokeDialogViewModel.dismissDialog()
+ }
+ )
+ } else {
+ revokePermissionInGroup(group, perm.name)
+ }
+ }
+ }
+
+ private fun getPermissionFromGroup(group: AppPermissionGroup, permName: String): Permission? {
+ return group.permissions.find { it.name == permName } ?: let{
+ if ("user" == Build.TYPE) {
+ Log.e(TAG,
+ "The impossible happens, permission $permName is not in group $group.name.")
+ null
+ } else {
+ // This is impossible, throw a fatal error in non-user build.
+ throw IllegalArgumentException("Permission $permName is not in group $group.name%s")
+ }
+ }
+ }
+
+ private fun revokePermissionInGroup(group: AppPermissionGroup, permName: String) {
+ group.revokeRuntimePermissions(true, arrayOf(permName))
+
+ if (Utils.areGroupPermissionsIndividuallyControlled(context, group.name) &&
+ group.doesSupportRuntimePermissions() &&
+ !group.areRuntimePermissionsGranted()
+ ) {
+ // If we just revoked the last permission we need to clear
+ // the user fixed state as now the app should be able to
+ // request them at runtime if supported.
+ group.revokeRuntimePermissions(false)
+ }
+ }
+
+ private fun showRevocationWarningDialog(
+ messageId: Int,
+ onOkButtonClick: () -> Unit,
+ onCancelButtonClick: () -> Unit = { revokeDialogViewModel.dismissDialog() }
+ ) {
+ revokeDialogViewModel.revokeDialogArgs = RevokeDialogArgs(
+ messageId = messageId,
+ onOkButtonClick = onOkButtonClick,
+ onCancelButtonClick = onCancelButtonClick
+ )
+ revokeDialogViewModel.showDialogLiveData.value = true
+ }
+
+ private fun onPermissionGroupClicked(group: AppPermissionGroup, grantCategory: String) {
+ val permGroupName = group.name
+ val packageName = group.app?.packageName ?: ""
+ val caller = WearAppPermissionGroupsFragment::class.java.name
+
+ addToggledGroup(group)
+
+ if (LocationUtils.isLocationGroupAndProvider(context, permGroupName, packageName)) {
+ val intent = Intent(
+ context,
+ LocationProviderInterceptDialog::class.java
+ )
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+ context.startActivityAsUser(intent, user)
+ } else if (LocationUtils.isLocationGroupAndControllerExtraPackage(
+ context,
+ permGroupName,
+ packageName
+ )
+ ) {
+ // Redirect to location controller extra package settings.
+ LocationUtils.startLocationControllerExtraPackageSettings(context, user)
+ } else {
+ val args = AppPermissionFragment.createArgs(
+ packageName,
+ null,
+ permGroupName,
+ user,
+ caller,
+ sessionId,
+ grantCategory
+ )
+ fragment.findNavController().navigateSafe(R.id.perm_groups_to_app, args)
+ }
+ }
+
+ private fun addToggledGroup(group: AppPermissionGroup) {
+ toggledGroups.add(group)
+ }
+
+ fun logAndClearToggledGroups() {
+ LegacySafetyNetLogger.logPermissionsToggled(toggledGroups)
+ toggledGroups.clear()
+ }
+
+ fun getAutoRevokeChipParam(state: HibernationSettingState?): AutoRevokeChipParam? =
+ state?.let {
+ AutoRevokeChipParam(
+ labelRes = if (isHibernationEnabled()) {
+ R.string.unused_apps_label_v2
+ } else {
+ R.string.auto_revoke_label
+ },
+ visible = it.revocableGroupNames.isNotEmpty(),
+ checked = it.isEligibleForHibernation(),
+ onCheckedChanged = { checked ->
+ run {
+ viewModel.setAutoRevoke(checked)
+ Log.w(TAG, "setAutoRevoke $checked")
+ }
+ }
+ )
+ }
+
+ companion object {
+ const val DEBUG = false
+ const val TAG = WearAppPermissionGroupsFragment.LOG_TAG
+ }
+}
+
+data class PermissionGroupChipParam(
+ val group: AppPermissionGroup,
+ val perm: PermissionInfo? = null,
+ val label: String,
+ val summary: Int? = null,
+ val enabled: Boolean = true,
+ val checked: Boolean? = null,
+ val onClick: () -> Unit = {},
+ val onCheckedChanged: (Boolean) -> Unit = {}
+)
+
+data class AutoRevokeChipParam(
+ val labelRes: Int,
+ val visible: Boolean,
+ val checked: Boolean = false,
+ val onCheckedChanged: (Boolean) -> Unit
+)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt
new file mode 100644
index 000000000..2d06ef950
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.wear
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.res.stringResource
+import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog
+import com.android.permissioncontroller.permission.ui.wear.elements.Chip
+import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
+import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip
+import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl
+import com.android.permissioncontroller.permission.ui.wear.model.RevokeDialogArgs
+
+@Composable
+fun WearAppPermissionGroupsScreen(
+ helper: WearAppPermissionGroupsHelper
+) {
+ val packagePermGroups = helper.viewModel.packagePermGroupsLiveData.observeAsState(emptyMap())
+ val autoRevoke = helper.viewModel.autoRevokeLiveData.observeAsState(null)
+ val showRevokeDialog = helper.revokeDialogViewModel.showDialogLiveData.observeAsState(false)
+ var isLoading by remember { mutableStateOf(true) }
+
+ Box {
+ WearAppPermissionGroupsContent(
+ isLoading,
+ helper.getPermissionGroupChipParams(),
+ helper.getAutoRevokeChipParam(autoRevoke.value)
+ )
+ RevokeDialog(
+ showDialog = showRevokeDialog.value,
+ args = helper.revokeDialogViewModel.revokeDialogArgs
+ )
+ }
+
+ if (isLoading && packagePermGroups.value.isNotEmpty()) {
+ isLoading = false
+ }
+}
+
+@Composable
+internal fun WearAppPermissionGroupsContent(
+ isLoading: Boolean,
+ permissionGroupChipParams: List<PermissionGroupChipParam>,
+ autoRevokeChipParam: AutoRevokeChipParam?
+) {
+ ScrollableScreen(
+ title = stringResource(R.string.app_permissions),
+ isLoading = isLoading
+ ) {
+ if (permissionGroupChipParams.isEmpty()) {
+ item {
+ Chip(
+ label = stringResource(R.string.no_permissions),
+ onClick = {}
+ )
+ }
+ } else {
+ for (info in permissionGroupChipParams) {
+ item {
+ if (info.checked != null) {
+ ToggleChip(
+ checked = info.checked,
+ label = info.label,
+ enabled = info.enabled,
+ toggleControl = ToggleChipToggleControl.Switch,
+ onCheckedChanged = info.onCheckedChanged
+ )
+ } else {
+ Chip(
+ label = info.label,
+ secondaryLabel = info.summary?.let {
+ stringResource(info.summary)
+ },
+ enabled = info.enabled,
+ onClick = info.onClick
+ )
+ }
+ }
+ }
+ autoRevokeChipParam?.let {
+ if (it.visible) {
+ item {
+ ToggleChip(
+ checked = it.checked,
+ label = stringResource(it.labelRes),
+ labelMaxLine = 3,
+ toggleControl = ToggleChipToggleControl.Switch,
+ onCheckedChanged = it.onCheckedChanged
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+internal fun RevokeDialog(
+ showDialog: Boolean,
+ args: RevokeDialogArgs?
+) {
+ args?.let {
+ AlertDialog(
+ showDialog = showDialog,
+ message = stringResource(it.messageId),
+ onOKButtonClick = it.onOkButtonClick,
+ onCancelButtonClick = it.onCancelButtonClick,
+ scalingLazyListState = rememberScalingLazyListState()
+ )
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt
new file mode 100644
index 000000000..94d994421
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.wear
+
+import android.graphics.drawable.Drawable
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo
+import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModel
+import com.android.permissioncontroller.permission.ui.wear.elements.Chip
+import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupIcon
+import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel
+import com.android.permissioncontroller.permission.utils.StringUtils
+import java.text.Collator
+
+@Composable
+fun WearManageStandardPermissionScreen(
+ viewModel: ManageStandardPermissionsViewModel,
+ onPermGroupClick: (String) -> Unit,
+ onCustomPermissionsClick: () -> Unit,
+ onAutoRevokedClick: () -> Unit
+) {
+ val permissionGroups = viewModel.uiDataLiveData.observeAsState(emptyMap())
+ val numCustomPermGroups = viewModel.numCustomPermGroups.observeAsState(0)
+ val numAutoRevoked = viewModel.numAutoRevoked.observeAsState(0)
+ var isLoading by remember { mutableStateOf(true) }
+
+ WearManageStandardPermissionContent(
+ isLoading,
+ getPermGroupChipParams(permissionGroups.value),
+ numCustomPermGroups.value,
+ numAutoRevoked.value,
+ onPermGroupClick,
+ onCustomPermissionsClick,
+ onAutoRevokedClick
+ )
+
+ if (isLoading && permissionGroups.value.isNotEmpty()) {
+ isLoading = false
+ }
+}
+
+@Composable
+internal fun getPermGroupChipParams(
+ permissionGroups: Map<String, PermGroupPackagesUiInfo?>
+): List<PermGroupChipParam> {
+ val context = LocalContext.current
+ val collator = Collator.getInstance(context.resources.getConfiguration().getLocales().get(0))
+ val summary = if (context.resources.getBoolean(R.bool.config_useAlternativePermGroupSummary)) {
+ R.string.app_permissions_group_summary2
+ } else {
+ R.string.app_permissions_group_summary
+ }
+ return permissionGroups.mapNotNull {
+ val uiInfo = it.value ?: return@mapNotNull null
+ PermGroupChipParam(
+ permGroupName = it.key,
+ label = getPermGroupLabel(context, it.key).toString(),
+ icon = getPermGroupIcon(context, it.key),
+ secondaryLabel = stringResource(
+ summary,
+ uiInfo.nonSystemGranted,
+ uiInfo.nonSystemTotal
+ )
+ )
+ }.sortedWith { lhs, rhs ->
+ collator.compare(lhs.label, rhs.label)
+ }.toList()
+}
+
+@Composable
+internal fun WearManageStandardPermissionContent(
+ isLoading: Boolean,
+ permGroupChipParams: List<PermGroupChipParam>,
+ numCustomPermGroups: Int,
+ numAutoRevoked: Int,
+ onPermGroupClick: (String) -> Unit,
+ onCustomPermissionsClick: () -> Unit,
+ onAutoRevokedClick: () -> Unit
+) {
+ ScrollableScreen(
+ title = stringResource(R.string.app_permission_manager),
+ isLoading = isLoading
+ ) {
+ for (params in permGroupChipParams) {
+ item {
+ Chip(
+ label = params.label,
+ labelMaxLines = 3,
+ icon = params.icon,
+ secondaryLabel = params.secondaryLabel,
+ secondaryLabelMaxLines = 3,
+ onClick = {
+ onPermGroupClick(params.permGroupName)
+ }
+ )
+ }
+ }
+
+ if (numCustomPermGroups > 0) {
+ item {
+ Chip(
+ label = stringResource(R.string.additional_permissions),
+ labelMaxLines = 3,
+ icon = R.drawable.ic_more_horizontal,
+ secondaryLabel = StringUtils.getIcuPluralsString(
+ LocalContext.current,
+ R.string.additional_permissions_more,
+ numCustomPermGroups
+ ),
+ secondaryLabelMaxLines = 3,
+ onClick = onCustomPermissionsClick
+ )
+ }
+ }
+
+ if (numAutoRevoked > 0) {
+ item {
+ Chip(
+ label = stringResource(R.string.auto_revoke_permission_notification_title),
+ labelMaxLines = 3,
+ icon = R.drawable.ic_info,
+ secondaryLabel = stringResource(R.string.auto_revoke_setting_subtitle),
+ secondaryLabelMaxLines = 3,
+ onClick = onAutoRevokedClick
+ )
+ }
+ }
+ }
+}
+
+internal data class PermGroupChipParam(
+ val permGroupName: String,
+ val label: String,
+ val icon: Drawable?,
+ val secondaryLabel: String,
+)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionsFragment.kt
new file mode 100644
index 000000000..f4e2689d0
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionsFragment.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.wear
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.compose.ui.platform.ComposeView
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.ViewModelProvider
+import com.android.permissioncontroller.Constants
+import com.android.permissioncontroller.permission.ui.UnusedAppsFragment
+import com.android.permissioncontroller.permission.ui.handheld.ManageCustomPermissionsFragment
+import com.android.permissioncontroller.permission.ui.handheld.PermissionAppsFragment
+import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModel
+
+class WearManageStandardPermissionsFragment : Fragment() {
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val activity = requireActivity()
+ val application = activity.getApplication()
+ val sessionId: Long =
+ arguments?.getLong(Constants.EXTRA_SESSION_ID) ?: Constants.INVALID_SESSION_ID
+ val viewModel: ManageStandardPermissionsViewModel = ViewModelProvider(
+ this,
+ ViewModelProvider.AndroidViewModelFactory.getInstance(application)
+ ).get(ManageStandardPermissionsViewModel::class.java)
+
+ val onPermGroupClick: (String) -> Unit = { permGroupName ->
+ viewModel.showPermissionApps(
+ this,
+ PermissionAppsFragment.createArgs(permGroupName, sessionId)
+ )
+ }
+ val onCustomPermGroupClick = {
+ viewModel.showCustomPermissions(
+ this,
+ ManageCustomPermissionsFragment.createArgs(sessionId)
+ )
+ }
+ val onAutoRevokeClick = {
+ viewModel.showAutoRevoke(this, UnusedAppsFragment.createArgs(sessionId))
+ }
+
+ return ComposeView(activity).apply {
+ setContent {
+ WearManageStandardPermissionScreen(
+ viewModel,
+ onPermGroupClick,
+ onCustomPermGroupClick,
+ onAutoRevokeClick
+ )
+ }
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt
index 5dc8141fc..9a404def3 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt
@@ -31,6 +31,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
@@ -42,6 +43,7 @@ import androidx.wear.compose.material.ChipDefaults
import androidx.wear.compose.material.Icon
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.Text
+import com.android.permissioncontroller.R
/**
* This component is an alternative to [Chip], providing the following:
@@ -52,9 +54,11 @@ import androidx.wear.compose.material.Text
@Composable
public fun Chip(
label: String,
+ labelMaxLines: Int? = null,
onClick: () -> Unit,
modifier: Modifier = Modifier,
secondaryLabel: String? = null,
+ secondaryLabelMaxLines: Int? = null,
icon: Any? = null,
iconContentDescription: String? = null,
largeIcon: Boolean = false,
@@ -108,9 +112,11 @@ public fun Chip(
Chip(
label = label,
+ labelMaxLines = labelMaxLines,
onClick = onClick,
modifier = modifier,
secondaryLabel = secondaryLabel,
+ secondaryLabelMaxLines = secondaryLabelMaxLines,
icon = iconParam,
largeIcon = largeIcon,
textColor = textColor,
@@ -128,9 +134,11 @@ public fun Chip(
@Composable
public fun Chip(
@StringRes labelId: Int,
+ labelMaxLines: Int? = null,
onClick: () -> Unit,
modifier: Modifier = Modifier,
@StringRes secondaryLabel: Int? = null,
+ secondaryLabelMaxLines: Int? = null,
icon: Any? = null,
largeIcon: Boolean = false,
textColor: Color = MaterialTheme.colors.onSurface,
@@ -140,9 +148,11 @@ public fun Chip(
) {
Chip(
label = stringResource(id = labelId),
+ labelMaxLines = labelMaxLines,
onClick = onClick,
modifier = modifier,
secondaryLabel = secondaryLabel?.let { stringResource(id = it) },
+ secondaryLabelMaxLines = secondaryLabelMaxLines,
icon = icon,
largeIcon = largeIcon,
textColor = textColor,
@@ -159,12 +169,15 @@ public fun Chip(
@Composable
public fun Chip(
label: String,
+ labelMaxLines: Int? = null,
onClick: () -> Unit,
modifier: Modifier = Modifier,
secondaryLabel: String? = null,
+ secondaryLabelMaxLines: Int? = null,
icon: (@Composable BoxScope.() -> Unit)? = null,
largeIcon: Boolean = false,
textColor: Color = MaterialTheme.colors.onSurface,
+ secondaryTextColor: Color = colorResource(R.color.wear_material_gray_600),
colors: ChipColors = ChipDefaults.secondaryChipColors(),
enabled: Boolean = true
) {
@@ -179,7 +192,7 @@ public fun Chip(
modifier = Modifier.fillMaxWidth(),
textAlign = if (hasSecondaryLabel || hasIcon) TextAlign.Start else TextAlign.Center,
overflow = TextOverflow.Ellipsis,
- maxLines = if (hasSecondaryLabel) 1 else 2,
+ maxLines = labelMaxLines ?: if (hasSecondaryLabel) 1 else 2,
style = MaterialTheme.typography.button
)
}
@@ -189,8 +202,9 @@ public fun Chip(
{
Text(
text = secondaryLabel,
+ color = secondaryTextColor,
overflow = TextOverflow.Ellipsis,
- maxLines = 1,
+ maxLines = secondaryLabelMaxLines ?: 1,
style = MaterialTheme.typography.caption2
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
index 20b51ed61..2a96f3b2e 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt
@@ -43,6 +43,7 @@ import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
import androidx.wear.compose.foundation.lazy.ScalingLazyListState
import androidx.wear.compose.material.CircularProgressIndicator
+import androidx.wear.compose.material.ListHeader
import androidx.wear.compose.material.MaterialTheme
import androidx.wear.compose.material.PositionIndicator
import androidx.wear.compose.material.Scaffold
@@ -119,16 +120,9 @@ fun ScrollableScreen(
}
if (title != null) {
item {
- Text(
- text = title,
- textAlign = TextAlign.Center,
- style = MaterialTheme.typography.button,
- modifier = Modifier.padding(
- bottom = 12.dp,
- start = 24.dp,
- end = 24.dp
- )
- )
+ ListHeader {
+ Text(text = title, textAlign = TextAlign.Center)
+ }
}
}
if (subtitle != null) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt
index 29342658c..56c426dbb 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt
@@ -53,6 +53,7 @@ public fun ToggleChip(
checked: Boolean,
onCheckedChanged: (Boolean) -> Unit,
label: String,
+ labelMaxLine: Int? = null,
toggleControl: ToggleChipToggleControl,
modifier: Modifier = Modifier,
icon: ImageVector? = null,
@@ -71,7 +72,7 @@ public fun ToggleChip(
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Start,
overflow = TextOverflow.Ellipsis,
- maxLines = if (hasSecondaryLabel) 1 else 2,
+ maxLines = labelMaxLine ?: if (hasSecondaryLabel) 1 else 2,
style = MaterialTheme.typography.button
)
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/AppPermissionGroupsRevokeDialogViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/AppPermissionGroupsRevokeDialogViewModel.kt
new file mode 100644
index 000000000..699591912
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/AppPermissionGroupsRevokeDialogViewModel.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.wear.model
+
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+
+class AppPermissionGroupsRevokeDialogViewModel : ViewModel() {
+ /** A livedata which stores whether the dialog is visible. */
+ val showDialogLiveData = MutableLiveData<Boolean>()
+ var hasConfirmedRevoke: Boolean = false
+ var revokeDialogArgs: RevokeDialogArgs? = null
+
+ init {
+ showDialogLiveData.value = false
+ }
+
+ fun dismissDialog() {
+ showDialogLiveData.value = false
+ revokeDialogArgs = null
+ }
+}
+
+/**
+ * Factory for an AppPermissionGroupsRevokeDialogViewModel
+ */
+class AppPermissionGroupsRevokeDialogViewModelFactory : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ @Suppress("UNCHECKED_CAST")
+ return AppPermissionGroupsRevokeDialogViewModel() as T
+ }
+}
+
+data class RevokeDialogArgs(
+ val messageId: Int,
+ val onOkButtonClick: () -> Unit,
+ val onCancelButtonClick: () -> Unit
+)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
index c9a05c087..7ebd9b211 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -197,9 +197,6 @@ public final class Utils {
public static final String PROPERTY_PERMISSION_DECISIONS_MAX_DATA_AGE_MILLIS =
"permission_decisions_max_data_age_millis";
- /** Whether or not warning banner is displayed when device sensors are off **/
- public static final String PROPERTY_WARNING_BANNER_DISPLAY_ENABLED = "warning_banner_enabled";
-
/** All permission whitelists. */
public static final int FLAGS_PERMISSION_WHITELIST_ALL =
PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
@@ -1326,10 +1323,8 @@ public final class Utils {
* Returns if a card should be shown if the sensor is blocked
**/
public static boolean shouldDisplayCardIfBlocked(@NonNull String permissionGroupName) {
- return DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_WARNING_BANNER_DISPLAY_ENABLED, true) && (
- CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName)
- || LOCATION.equals(permissionGroupName));
+ return CAMERA.equals(permissionGroupName) || MICROPHONE.equals(permissionGroupName)
+ || LOCATION.equals(permissionGroupName);
}
/**
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt
index c7a33e857..0611ec3c4 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/HealthConnectAllAppPermissionFragmentTest.kt
@@ -21,6 +21,7 @@ import android.os.Build
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
import com.android.compatibility.common.util.SystemUtil.eventually
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject
@@ -104,14 +105,18 @@ class HealthConnectAllAppPermissionFragmentTest : BasePermissionUiTest() {
}
private fun startManageAppPermissionsActivity() {
- runWithShellPermissionIdentity {
- instrumentationContext.startActivity(Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
- .apply {
- addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
- putExtra(Intent.EXTRA_PACKAGE_NAME, PERM_USER_PACKAGE)
- })
- }
+ uiDevice.performActionAndWait({
+ runWithShellPermissionIdentity {
+ instrumentationContext.startActivity(
+ Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS)
+ .apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ putExtra(Intent.EXTRA_PACKAGE_NAME, PERM_USER_PACKAGE)
+ }
+ )
+ }
+ }, Until.newWindow(), TIMEOUT_SHORT)
waitFindObject(By.descContains(MORE_OPTIONS)).click()
waitFindObject(By.text(ALL_PERMISSIONS)).click()
diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/v31/PermissionUsageFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/v31/PermissionUsageFragmentTest.kt
index 2869e1863..45cc52649 100644
--- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/v31/PermissionUsageFragmentTest.kt
+++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/v31/PermissionUsageFragmentTest.kt
@@ -61,9 +61,12 @@ class PermissionUsageFragmentTest : PermissionHub2Test() {
runWithShellPermissionIdentity {
context.startActivity(
- Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).apply {
+ Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE)
+ .apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- })
+ }
+ )
}
eventually {
diff --git a/SafetyCenter/Resources/shared_res/values-af/strings.xml b/SafetyCenter/Resources/shared_res/values-af/strings.xml
index 9ab860ecf..3c20c6428 100644
--- a/SafetyCenter/Resources/shared_res/values-af/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-af/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Sien waarskuwing}other{Sien waarskuwings}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Kon nie bladsy oopmaak nie"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Kon nie opletberig afhandel nie"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Kon nie instellings herlaai nie"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Kon nie instelling nagaan nie}other{Kon nie instellings nagaan nie}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Werkprofiel is onderbreek"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nog geen inligting nie"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-am/strings.xml b/SafetyCenter/Resources/shared_res/values-am/strings.xml
index 093dda2f2..d1080ebf7 100644
--- a/SafetyCenter/Resources/shared_res/values-am/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-am/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ማንቂያ ይመልከቱ}one{ማንቂያ ይመልከቱ}other{ማንቂያዎች ይመልከቱ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ገጹን መከፈት አልተቻለም"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ማንቂያን መፍታት አልተቻለም"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ቅንብሮችን ማደስ አልተቻለም"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ቅንብርን መፈተሽ አልተቻለም}one{ቅንብርን መፈተሽ አልተቻለም}other{ቅንብሮችን መፈተሽ አልተቻለም}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"የስራ መገለጫ ባለበት ቆሟል"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ገና ምንም መረጃ የለም"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ar/strings.xml b/SafetyCenter/Resources/shared_res/values-ar/strings.xml
index ce0b1c10d..49e16c39c 100644
--- a/SafetyCenter/Resources/shared_res/values-ar/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ar/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{الاطّلاع على التنبيه}zero{الاطّلاع على التنبيهات}two{الاطّلاع على التنبيهَين}few{الاطّلاع على التنبيهات}many{الاطّلاع على التنبيهات}other{الاطّلاع على التنبيهات}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"تعذَّر فتح الصفحة"</string>
<string name="resolving_action_error" msgid="371968886143262375">"تعذَّر التعامل بشكل نهائي مع التنبيه"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"تعذّر تحديث الإعدادات"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{تعذّر التحقّق من الإعداد.}zero{تعذّر التحقّق من الإعدادات.}two{تعذّر التحقّق من الإعدادَين.}few{تعذّر التحقّق من الإعدادات.}many{تعذّر التحقّق من الإعدادات.}other{تعذّر التحقّق من الإعدادات.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"تم إيقاف الملف الشخصي للعمل مؤقتًا"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ما مِن معلومات بعد."</string>
diff --git a/SafetyCenter/Resources/shared_res/values-as/strings.xml b/SafetyCenter/Resources/shared_res/values-as/strings.xml
index 473003f59..4105c26d0 100644
--- a/SafetyCenter/Resources/shared_res/values-as/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-as/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{সতৰ্কবাৰ্তা চাওক}one{সতৰ্কবাৰ্তাসমূহ চাওক}other{সতৰ্কবাৰ্তাসমূহ চাওক}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"পৃষ্ঠাখন খুলিব পৰা নগ’ল"</string>
<string name="resolving_action_error" msgid="371968886143262375">"সতৰ্কবাৰ্তা সমাধান কৰিব পৰা নগ’ল"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ছেটিং ৰিফ্ৰেশ্ব কৰিব পৰা নগ’ল"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ছেটিং পৰীক্ষা কৰিব পৰা নগ’ল}one{ছেটিং পৰীক্ষা কৰিব পৰা নগ’ল}other{ছেটিং পৰীক্ষা কৰিব পৰা নগ’ল}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"কৰ্মস্থানৰ প্ৰ’ফাইলটো পজ কৰা আছে"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"এতিয়ালৈকে কোনো তথ্য নাই"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-az/strings.xml b/SafetyCenter/Resources/shared_res/values-az/strings.xml
index e3c31a178..7e2e7c140 100644
--- a/SafetyCenter/Resources/shared_res/values-az/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-az/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Xəbərdarlığa baxın}other{Xəbərdarlıqlara baxın}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Səhifəni açmaq mümkün olmadı"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Siqnalı həll etmək mümkün olmadı"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Ayarları yeniləmək mümkün olmadı"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ayarı yoxlamaq alınmadı}other{Ayarları yoxlamaq alınmadı}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"İş profili durdurulub"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Hələ ki, məlumat yoxdur"</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 5233edc91..19660cb06 100644
--- a/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-b+sr+Latn/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Prikaži obaveštenje}one{Prikaži obaveštenja}few{Prikaži obaveštenja}other{Prikaži obaveštenja}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Otvaranje stranice nije uspelo"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Rešavanje obaveštenja nije uspelo"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Osvežavanje podešavanja nije uspelo"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Provera podešavanja nije uspela}one{Provera podešavanja nije uspela}few{Provera podešavanja nije uspela}other{Provera podešavanja nije uspela}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Poslovni profil je pauziran"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Još nema informacija"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-be/strings.xml b/SafetyCenter/Resources/shared_res/values-be/strings.xml
index 2b386f3e6..2c27670e4 100644
--- a/SafetyCenter/Resources/shared_res/values-be/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-be/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Паглядзець абвестку}one{Паглядзець абвесткі}few{Паглядзець абвесткі}many{Паглядзець абвесткі}other{Паглядзець абвесткі}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не ўдалося адкрыць старонку"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не ўдалося вырашыць праблему"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Не ўдалося абнавіць налады"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не ўдалося праверыць наладу}one{Не ўдалося праверыць налады}few{Не ўдалося праверыць налады}many{Не ўдалося праверыць налады}other{Не ўдалося праверыць налады}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Працоўны профіль прыпынены"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Пакуль няма інфармацыі"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bg/strings.xml b/SafetyCenter/Resources/shared_res/values-bg/strings.xml
index 85833f145..e3495d4d5 100644
--- a/SafetyCenter/Resources/shared_res/values-bg/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bg/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Преглед на сигнала}other{Преглед на сигналите}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Страницата не се отвори"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Сигналът не се отстрани"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Настройките не бяха опреснени"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Настройката не бе проверена}other{Настройките не бяха проверени}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Служебният потребителски профил е поставен на пауза"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Още няма информация"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bn/strings.xml b/SafetyCenter/Resources/shared_res/values-bn/strings.xml
index 30904c97e..3f24f05a4 100644
--- a/SafetyCenter/Resources/shared_res/values-bn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bn/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{বিজ্ঞপ্তি দেখুন}one{বিজ্ঞপ্তি দেখুন}other{বিজ্ঞপ্তি দেখুন}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"পৃষ্ঠা খোলা যায়নি"</string>
<string name="resolving_action_error" msgid="371968886143262375">"সতর্কতার সমাধান করা যায়নি"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"সেটিংস রিফ্রেশ করা যায়নি"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{সেটিং চেক করা যায়নি}one{সেটিংস চেক করা যায়নি}other{সেটিংস চেক করা যায়নি}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"অফিস প্রোফাইল পজ করা আছে"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"এখনও কোনও তথ্য নেই"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-bs/strings.xml b/SafetyCenter/Resources/shared_res/values-bs/strings.xml
index 93cfcf8fa..a877f8b36 100644
--- a/SafetyCenter/Resources/shared_res/values-bs/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-bs/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Pogledajte upozorenje}one{Pogledajte upozorenja}few{Pogledajte upozorenja}other{Pogledajte upozorenja}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Otvaranje stranice nije uspjelo"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Rješavanje upozorenja nije uspjelo"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Osvježavanje postavki nije uspjelo"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Provjera postavke nije uspjela}one{Provjera postavki nije uspjela}few{Provjera postavki nije uspjela}other{Provjera postavki nije uspjela}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Radni profil je pauziran"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Još uvijek nema informacija"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ca/strings.xml b/SafetyCenter/Resources/shared_res/values-ca/strings.xml
index dbec15679..a2d8b0f35 100644
--- a/SafetyCenter/Resources/shared_res/values-ca/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ca/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Mostra l\'alerta}many{Mostra les alertes}other{Mostra les alertes}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"No s\'ha pogut obrir la pàgina"</string>
<string name="resolving_action_error" msgid="371968886143262375">"No s\'ha pogut resoldre l\'alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"No s\'ha pogut actualitzar la configuració"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{No s\'ha pogut comprovar la configuració}many{No s\'ha pogut comprovar la configuració}other{No s\'ha pogut comprovar la configuració}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"El perfil de treball s\'ha posat en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Encara no hi ha informació"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-cs/strings.xml b/SafetyCenter/Resources/shared_res/values-cs/strings.xml
index 9d90b05aa..28aabd4e8 100644
--- a/SafetyCenter/Resources/shared_res/values-cs/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-cs/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Zobrazit upozornění}few{Zobrazit upozornění}many{Zobrazit upozornění}other{Zobrazit upozornění}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Stránku nelze otevřít"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Upozornění se nepodařilo vyřešit"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nastavení se nepodařilo obnovit"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nastavení nelze zkontrolovat}few{Nastavení nelze zkontrolovat}many{Nastavení nelze zkontrolovat}other{Nastavení nelze zkontrolovat}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Pracovní profil je pozastaven"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Zatím žádné údaje"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-da/strings.xml b/SafetyCenter/Resources/shared_res/values-da/strings.xml
index bce5898fa..e4d28aaf1 100644
--- a/SafetyCenter/Resources/shared_res/values-da/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-da/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Se underretning}one{Se underretning}other{Se underretninger}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Siden kunne ikke åbnes"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Underretningen kunne ikke behandles"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Indstillingerne kunne ikke opdateres"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Indstillingen kunne ikke tjekkes}one{Indstillingen kunne ikke tjekkes}other{Indstillingerne kunne ikke tjekkes}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Arbejdsprofilen er sat på pause"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Der er ingen oplysninger endnu"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-de/strings.xml b/SafetyCenter/Resources/shared_res/values-de/strings.xml
index cbaf373d1..a5b1758ad 100644
--- a/SafetyCenter/Resources/shared_res/values-de/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-de/strings.xml
@@ -36,11 +36,10 @@
<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="1913235490583842004">"Konto gefährdet"</string>
+ <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"Konto ist gefährdet"</string>
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Benachrichtigung ansehen}other{Benachrichtigungen ansehen}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Seite konnte nicht geöffnet werden"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ursache konnte nicht behoben werden"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Einstellungen konnten nicht aktualisiert werden"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Einstellung konnte nicht überprüft werden}other{Einstellungen konnten nicht überprüft werden}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Arbeitsprofil pausiert"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Noch keine Angaben vorhanden"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-el/strings.xml b/SafetyCenter/Resources/shared_res/values-el/strings.xml
index ac93dedd8..c6f005d5f 100644
--- a/SafetyCenter/Resources/shared_res/values-el/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-el/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Εμφάνιση ειδοποίησης}other{Εμφάνιση ειδοποιήσεων}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Δεν ήταν δυνατό το άνοιγμα της σελίδας"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Δεν ήταν δυνατή η επίλυση της ειδοποίησης"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Δεν ήταν δυνατή η ανανέωση των ρυθμίσεων"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Δεν ήταν δυνατός ο έλεγχος της ρύθμισης}other{Δεν ήταν δυνατός ο έλεγχος των ρυθμίσεων}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Το προφίλ εργασίας έχει τεθεί σε παύση"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Δεν υπάρχουν ακόμα πληροφορίες"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
index 7e0312660..f389be966 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rAU/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldn\'t open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldn\'t resolve alert"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Couldn\'t refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldn\'t check setting}other{Couldn\'t check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
index f63228247..00234beca 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rCA/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldnt open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldnt resolve alert"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Couldnt refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldnt check setting}other{Couldnt check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
index 7e0312660..f389be966 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rGB/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldn\'t open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldn\'t resolve alert"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Couldn\'t refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldn\'t check setting}other{Couldn\'t check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
index 7e0312660..f389be966 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rIN/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{See alert}other{See alerts}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Couldn\'t open page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Couldn\'t resolve alert"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Couldn\'t refresh settings"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Couldn\'t check setting}other{Couldn\'t check settings}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Work profile is paused"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"No info yet"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml b/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
index d1cd45d43..811e6e7fd 100644
--- a/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-en-rXC/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎See alert‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎See alerts‎‏‎‎‏‎}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎Couldnt open page‎‏‎‎‏‎"</string>
<string name="resolving_action_error" msgid="371968886143262375">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎Couldnt resolve alert‎‏‎‎‏‎"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‏‎‏‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‎‎Couldnt refresh settings‎‏‎‎‏‎"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎Couldnt check setting‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‎‏‎‎‎‏‏‎‎‎‎‏‎Couldnt check settings‎‏‎‎‏‎}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‎‏‎‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‏‏‎Work profile is paused‎‏‎‎‏‎"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‏‎‎‎‎‏‎‎‏‎‎‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎No info yet‎‏‎‎‏‎"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml b/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
index 787838a20..29613fc69 100644
--- a/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-es-rUS/strings.xml
@@ -36,11 +36,10 @@
<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="1913235490583842004">"Cuenta en peligro"</string>
+ <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"La cuenta está en riesgo"</string>
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ver alerta}many{Ver alertas}other{Ver alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"No se pudo abrir la página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"No se pudo resolver la alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"No se pudo actualizar la configuración"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{No se pudo revisar el parámetro de configuración}many{No se pudieron revisar los parámetros de configuración}other{No se pudieron revisar los parámetros de configuración}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"El perfil de trabajo está en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aún no hay información"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-es/strings.xml b/SafetyCenter/Resources/shared_res/values-es/strings.xml
index 245d3394c..a13a68d8f 100644
--- a/SafetyCenter/Resources/shared_res/values-es/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-es/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ver alerta}many{Ver alertas}other{Ver alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"No se ha podido abrir la página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"No se ha podido resolver la alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"No se han podido actualizar los ajustes"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{No se ha podido comprobar el ajuste}many{No se han podido comprobar los ajustes}other{No se han podido comprobar los ajustes}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"El perfil de trabajo está en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aún no hay información"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-et/strings.xml b/SafetyCenter/Resources/shared_res/values-et/strings.xml
index 0aab37815..cfe0541d2 100644
--- a/SafetyCenter/Resources/shared_res/values-et/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-et/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Vaadake hoiatust}other{Vaadake hoiatusi}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Lehte ei saanud avada"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Hoiatusega seotud probleemi ei saanud lahendada"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Seadeid ei saanud värskendada"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Seadet ei õnnestunud kontrollida}other{Seadeid ei õnnestunud kontrollida}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Tööprofiil on peatatud"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Teavet ei ole veel"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-eu/strings.xml b/SafetyCenter/Resources/shared_res/values-eu/strings.xml
index 88942a47b..c00cb5827 100644
--- a/SafetyCenter/Resources/shared_res/values-eu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-eu/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ikusi alerta}other{Ikusi alertak}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Ezin da ireki orria"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ezin izan da ebatzi alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Ezin izan dira freskatu ezarpenak"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ezin izan da egiaztatu ezarpena}other{Ezin izan dira egiaztatu ezarpenak}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Laneko profila pausatuta dago"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ez dago informaziorik oraindik"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fa/strings.xml b/SafetyCenter/Resources/shared_res/values-fa/strings.xml
index df11d470f..744ac950c 100644
--- a/SafetyCenter/Resources/shared_res/values-fa/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fa/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{دیدن هشدار}one{دیدن هشدار}other{دیدن هشدارها}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"صفحه باز نشد"</string>
<string name="resolving_action_error" msgid="371968886143262375">"هشدار رفع نشد"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"تنظیمات بازآوری نشد"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{تنظیم بررسی نشد}one{تنظیم بررسی نشد}other{تنظیمات بررسی نشدند}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"نمایه کاری موقتاً متوقف شده است"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"هنوز اطلاعاتی دردسترس نیست"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fi/strings.xml b/SafetyCenter/Resources/shared_res/values-fi/strings.xml
index bad542e6d..801b40081 100644
--- a/SafetyCenter/Resources/shared_res/values-fi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fi/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Näytä ilmoitus}other{Näytä ilmoitukset}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Sivun avaaminen epäonnistui"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Hälytyksen ratkaiseminen epäonnistui"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Asetuksia ei voitu päivittää"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Asetuksen tarkistaminen ei onnistunut}other{Asetusten tarkistaminen ei onnistunut}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Työprofiilin käyttö on keskeytetty"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ei vielä tietoa"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml b/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
index c1530b2cb..3956c61f5 100644
--- a/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fr-rCA/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Afficher l\'alerte}one{Afficher l\'alerte}many{Afficher les alertes}other{Afficher les alertes}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Impossible d\'ouvrir la page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Impossible de résoudre l\'alerte"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Impossible d\'actualiser les paramètres"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Impossible de vérifier le paramètre}one{Impossible de vérifier le paramètre}many{Impossible de vérifier les paramètres}other{Impossible de vérifier les paramètres}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Le profil professionnel est interrompu"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aucune donnée pour le moment"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-fr/strings.xml b/SafetyCenter/Resources/shared_res/values-fr/strings.xml
index d7bf215a0..b05f99a15 100644
--- a/SafetyCenter/Resources/shared_res/values-fr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-fr/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Voir l\'alerte}one{Voir l\'alerte}many{Voir les alertes}other{Voir les alertes}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Impossible d\'accéder à la page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Impossible de résoudre l\'alerte"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Impossible d\'actualiser les paramètres"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Impossible de vérifier le paramètre}one{Impossible de vérifier le paramètre}many{Impossible de vérifier les paramètres}other{Impossible de vérifier les paramètres}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profil professionnel en pause"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aucune info pour l\'instant"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-gl/strings.xml b/SafetyCenter/Resources/shared_res/values-gl/strings.xml
index ea934a37b..3a0251dd2 100644
--- a/SafetyCenter/Resources/shared_res/values-gl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-gl/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Consulta a alerta}other{Consulta as alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Non se puido abrir a páxina"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Non se puido resolver a alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Non se puido actualizar a configuración"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Non se puido comprobar a opción de configuración}other{Non se puideron comprobar as opcións de configuración}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"O perfil de traballo está en pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Aínda non hai información"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-gu/strings.xml b/SafetyCenter/Resources/shared_res/values-gu/strings.xml
index 0894b304b..ded82b3e5 100644
--- a/SafetyCenter/Resources/shared_res/values-gu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-gu/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{અલર્ટ જુઓ}one{અલર્ટ જુઓ}other{અલર્ટ જુઓ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"પેજ ખોલી શક્યા નથી"</string>
<string name="resolving_action_error" msgid="371968886143262375">"અલર્ટનું નિરાકરણ લાવી શક્યા નથી"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"સેટિંગ રિફ્રેશ કરી શકાયા નથી"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{સેટિંગ ચેક કરી શકાયું નથી}one{સેટિંગ ચેક કરી શકાયું નથી}other{સેટિંગ ચેક કરી શકાયા નથી}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ઑફિસની પ્રોફાઇલ થોભાવી છે"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"હજી સુધી કોઈ માહિતી નથી"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hi/strings.xml b/SafetyCenter/Resources/shared_res/values-hi/strings.xml
index c9a80e7f0..6fe62b414 100644
--- a/SafetyCenter/Resources/shared_res/values-hi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hi/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{चेतावनी देखें}one{चेतावनी देखें}other{चेतावनियां देखें}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"पेज को खोला नहीं जा सका"</string>
<string name="resolving_action_error" msgid="371968886143262375">"चेतावनी में बताई गई समस्या को ठीक नहीं किया जा सका"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"सेटिंग को रीफ़्रेश नहीं किया जा सका"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{सेटिंग की जांच नहीं की जा सकी}one{सेटिंग की जांच नहीं की जा सकी}other{सेटिंग की जांच नहीं की जा सकी}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"वर्क प्रोफ़ाइल रोक दी गई है"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"कोई जानकारी मौजूद नहीं है"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hr/strings.xml b/SafetyCenter/Resources/shared_res/values-hr/strings.xml
index ed02bc991..a644d6126 100644
--- a/SafetyCenter/Resources/shared_res/values-hr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hr/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Pogledajte upozorenje}one{Pogledajte upozorenja}few{Pogledajte upozorenja}other{Pogledajte upozorenja}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Otvaranje stranice nije uspjelo"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Razrješavanje upozorenja nije uspjelo"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nije bilo moglo osvježiti postavke"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nije moguće provjeriti postavku}one{Nije moguće provjeriti postavke}few{Nije moguće provjeriti postavke}other{Nije moguće provjeriti postavke}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Poslovni profil je pauziran"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Još nema podataka"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hu/strings.xml b/SafetyCenter/Resources/shared_res/values-hu/strings.xml
index 5f97845bc..b8a8540ef 100644
--- a/SafetyCenter/Resources/shared_res/values-hu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hu/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Értesítés megtekintése}other{Értesítések megtekintése}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nem sikerült megnyitni az oldalt"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nem sikerült feloldani a figyelmeztetést"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nem sikerült a beállítások frissítése"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nem sikerült a beállítás ellenőrzése}other{Nem sikerült a beállítások ellenőrzése}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"A munkaprofil használata szünetel"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Még nincsenek adatok"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-hy/strings.xml b/SafetyCenter/Resources/shared_res/values-hy/strings.xml
index befaeb8e5..e6b05056b 100644
--- a/SafetyCenter/Resources/shared_res/values-hy/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-hy/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Տեսնել ծանուցումը}one{Տեսնել ծանուցումները}other{Տեսնել ծանուցումները}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Չհաջողվեց բացել էջը"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Չհաջողվեց լուծել ծանուցումը"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Չհաջողվեց թարմացնել կարգավորումները"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Չհաջողվեց ստուգել կարգավորումը}one{Չհաջողվեց ստուգել կարգավորումը}other{Չհաջողվեց ստուգել կարգավորումները}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Աշխատանքային պրոֆիլը դադարեցված է"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Տեղեկություններ դեռ չկան"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-in/strings.xml b/SafetyCenter/Resources/shared_res/values-in/strings.xml
index cd584dbab..8d25d7340 100644
--- a/SafetyCenter/Resources/shared_res/values-in/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-in/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Lihat peringatan}other{Lihat peringatan}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Tidak dapat membuka halaman"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Tidak dapat menyelesaikan peringatan"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Tidak dapat merefresh setelan"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Tidak dapat memeriksa setelan}other{Tidak dapat memeriksa setelan}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profil kerja dijeda"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Belum ada info"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-is/strings.xml b/SafetyCenter/Resources/shared_res/values-is/strings.xml
index 3e36f81f1..faa4c2e1f 100644
--- a/SafetyCenter/Resources/shared_res/values-is/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-is/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Sjá viðvörun}one{Sjá viðvaranir}other{Sjá viðvaranir}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Ekki tókst að opna síðuna"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ekki tókst að leysa úr viðvöruninni"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Ekki tókst að endurnýja stillingar"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ekki tókst að athuga stillingu}one{Ekki tókst að athuga stillingar}other{Ekki tókst að athuga stillingar}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Hlé gert á vinnusniði"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Engar upplýsingar ennþá"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-it/strings.xml b/SafetyCenter/Resources/shared_res/values-it/strings.xml
index 68aa4eab3..b2661e84a 100644
--- a/SafetyCenter/Resources/shared_res/values-it/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-it/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Visualizza l\'avviso}many{Visualizza gli avvisi}other{Visualizza gli avvisi}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Impossibile aprire la pagina"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Impossibile risolvere l\'avviso"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Impossibile aggiornare le impostazioni"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Impossibile controllare l\'impostazione}many{Impossibile controllare le impostazioni}other{Impossibile controllare le impostazioni}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profilo di lavoro in pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ancora nessuna informazione"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-iw/strings.xml b/SafetyCenter/Resources/shared_res/values-iw/strings.xml
index 05cb0cd18..8a0794632 100644
--- a/SafetyCenter/Resources/shared_res/values-iw/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-iw/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{הצגת ההתראה}one{הצגת ההתראות}two{הצגת ההתראות}other{הצגת ההתראות}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"לא ניתן היה לפתוח את הדף"</string>
<string name="resolving_action_error" msgid="371968886143262375">"לא ניתן היה לפתור את הבעיה בהתראה"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"לא ניתן היה לרענן את ההגדרות"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{לא ניתן היה לבדוק את ההגדרה}one{לא ניתן היה לבדוק את ההגדרות}two{לא ניתן היה לבדוק את ההגדרות}other{לא ניתן היה לבדוק את ההגדרות}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"פרופיל העבודה מושהה"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"אין עדיין פרטים"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ja/strings.xml b/SafetyCenter/Resources/shared_res/values-ja/strings.xml
index aa760e5d7..393ca271c 100644
--- a/SafetyCenter/Resources/shared_res/values-ja/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ja/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{アラートを確認する}other{アラートを確認する}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ページを開けませんでした"</string>
<string name="resolving_action_error" msgid="371968886143262375">"アラートを解決できませんでした"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"設定を更新できませんでした"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{設定を確認できませんでした}other{設定を確認できませんでした}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"仕事用プロファイルが一時停止しています"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"まだ情報がありません"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ka/strings.xml b/SafetyCenter/Resources/shared_res/values-ka/strings.xml
index 4dda15927..de8890dec 100644
--- a/SafetyCenter/Resources/shared_res/values-ka/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ka/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{გაფრთხილების ნახვა}other{გაფრთხილებების ნახვა}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"გვერდის გახსნა ვერ მოხერხდა"</string>
<string name="resolving_action_error" msgid="371968886143262375">"გაფრთხილება ვერ გადაიჭრა"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"პარამეტრები ვერ განახლდა"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{პარამეტრის შემოწმება ვერ მოხერხდა}other{პარამეტრების Შემოწმება ვერ მოხერხდა}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"სამსახურის პროფილი დაპაუზებულია"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ინფო ჯერ არ არის"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-kk/strings.xml b/SafetyCenter/Resources/shared_res/values-kk/strings.xml
index 5ea3fa3e1..9226d4fcb 100644
--- a/SafetyCenter/Resources/shared_res/values-kk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-kk/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Хабарландыруды көру}other{Хабарландыруларды көру}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Бет ашылмады."</string>
<string name="resolving_action_error" msgid="371968886143262375">"Хабарландыруда көрсетілген мәселе шешілмеді."</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Параметрлер жаңартылмады."</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Параметрді тексеру мүмкін болмады.}other{Параметрлерді тексеру мүмкін болмады.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Жұмыс профилі кідіртілді."</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Әзірге мәлімет жоқ."</string>
diff --git a/SafetyCenter/Resources/shared_res/values-km/strings.xml b/SafetyCenter/Resources/shared_res/values-km/strings.xml
index d2bf8fc48..e1af9019d 100644
--- a/SafetyCenter/Resources/shared_res/values-km/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-km/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{មើលការជូន​ដំណឹង}other{មើលការជូនដំណឹង}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"មិនអាច​បើកទំព័រ​បានទេ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"មិនអាច​ដោះស្រាយការជូនដំណឹងនេះ​បានទេ"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"មិនអាចផ្ទុកការកំណត់ឡើងវិញបានទេ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{មិន​អាច​ពិនិត្យ​មើល​ការកំណត់​បាន​ទេ}other{មិន​អាច​ពិនិត្យ​មើល​ការកំណត់​បាន​ទេ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"កម្រងព័ត៌មានការងារត្រូវបាន​ផ្អាក"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"មិន​ទាន់​មាន​ព័ត៌មាន​នៅ​ឡើយ​ទេ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-kn/strings.xml b/SafetyCenter/Resources/shared_res/values-kn/strings.xml
index 9c3cb9dca..46eedaac5 100644
--- a/SafetyCenter/Resources/shared_res/values-kn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-kn/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ಎಚ್ಚರಿಕೆಯನ್ನು ನೋಡಿ}one{ಎಚ್ಚರಿಕೆಗಳನ್ನು ನೋಡಿ}other{ಎಚ್ಚರಿಕೆಗಳನ್ನು ನೋಡಿ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ಪುಟವನ್ನು ತೆರೆಯಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ಅಲರ್ಟ್ ಅನ್ನು ಬಗೆಹರಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ರಿಫ್ರೆಶ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ಸೆಟ್ಟಿಂಗ್ ಅನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ}one{ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ}other{ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ಉದ್ಯೋಗ ಪ್ರೊಫೈಲ್‌ ಅನ್ನು ವಿರಾಮಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ಇನ್ನೂ ಯಾವುದೇ ಮಾಹಿತಿ ಲಭ್ಯವಿಲ್ಲ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ko/strings.xml b/SafetyCenter/Resources/shared_res/values-ko/strings.xml
index 295fe4992..1a5938c9a 100644
--- a/SafetyCenter/Resources/shared_res/values-ko/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ko/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{알림 보기}other{알림 보기}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"페이지를 열 수 없습니다."</string>
<string name="resolving_action_error" msgid="371968886143262375">"알림을 해결할 수 없습니다."</string>
- <string name="refresh_timeout" msgid="251734999692581852">"설정을 새로고침할 수 없습니다."</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{설정을 확인할 수 없습니다.}other{설정을 확인할 수 없습니다.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"직장 프로필이 일시중지됨"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"아직 정보 없음"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ky/strings.xml b/SafetyCenter/Resources/shared_res/values-ky/strings.xml
index 2da0e82e4..474377b87 100644
--- a/SafetyCenter/Resources/shared_res/values-ky/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ky/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Эскетүүнү көрүү}other{Эскертүүлөрдү көрүү}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Барак ачылган жок"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Эскертүү чечилген жок"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Параметрлер жаңырган жок"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Параметр текшерилген жок}other{Параметрлер текшерилген жок}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Жумуш профили тындырылды"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Азырынча маалымат жок"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lo/strings.xml b/SafetyCenter/Resources/shared_res/values-lo/strings.xml
index 724411879..0abf76b30 100644
--- a/SafetyCenter/Resources/shared_res/values-lo/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lo/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ເບິ່ງແຈ້ງເຕືອນ}other{ເບິ່ງແຈ້ງເຕືອນ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ບໍ່ສາມາດເປີດໜ້າໄດ້"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ບໍ່ສາມາດແກ້ໄຂແຈ້ງເຕືອນໄດ້"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ບໍ່ສາມາດໂຫຼດການຕັ້ງຄ່າຄືນໃໝ່"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ກວດສອບການຕັ້ງຄ່າບໍ່ໄດ້}other{ກວດສອບການຕັ້ງຄ່າບໍ່ໄດ້}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ຢຸດໂປຣໄຟລ໌ວຽກໄວ້ຊົ່ວຄາວແລ້ວ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ບໍ່ມີຂໍ້ມູນເທື່ອ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lt/strings.xml b/SafetyCenter/Resources/shared_res/values-lt/strings.xml
index fc1704c13..832ef32f1 100644
--- a/SafetyCenter/Resources/shared_res/values-lt/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lt/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Žr. įspėjimą}one{Žr. įspėjimus}few{Žr. įspėjimus}many{Žr. įspėjimus}other{Žr. įspėjimus}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nepavyko atidaryti puslapio"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nepavyko pašalinti įspėjimo"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nepavyko atnaujinti nustatymų"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nepavyko patikrinti nustatymo}one{Nepavyko patikrinti nustatymų}few{Nepavyko patikrinti nustatymų}many{Nepavyko patikrinti nustatymų}other{Nepavyko patikrinti nustatymų}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Darbo profilis pristabdytas"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Kol kas informacijos nėra"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-lv/strings.xml b/SafetyCenter/Resources/shared_res/values-lv/strings.xml
index c27624603..eb544b15f 100644
--- a/SafetyCenter/Resources/shared_res/values-lv/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-lv/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Skatiet brīdinājumu}zero{Skatiet brīdinājumus}one{Skatiet brīdinājumus}other{Skatiet brīdinājumus}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nevarēja atvērt lapu"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nevarēja atrisināt ieteikumu vai brīdinājumu"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nevarēja atsvaidzināt iestatījumus"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nevarēja pārbaudīt iestatījumu.}zero{Nevarēja pārbaudīt iestatījumus.}one{Nevarēja pārbaudīt iestatījumus.}other{Nevarēja pārbaudīt iestatījumus.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Darba profila darbība ir apturēta"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Vēl nav informācijas"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mk/strings.xml b/SafetyCenter/Resources/shared_res/values-mk/strings.xml
index 2ba7a1976..30b405c67 100644
--- a/SafetyCenter/Resources/shared_res/values-mk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mk/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Видете го предупредувањето}one{Видете ги предупредувањата}other{Видете ги предупредувањата}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не можеше да се отвори страницата"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не можеше да се реши предупредувањето"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Не можеше да се освежат поставките"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не може да се провери поставката}one{Не може да се проверат поставките}other{Не може да се проверат поставките}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Работниот профил е паузиран"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Сѐ уште нема податоци"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ml/strings.xml b/SafetyCenter/Resources/shared_res/values-ml/strings.xml
index a05a7eed1..4a77469da 100644
--- a/SafetyCenter/Resources/shared_res/values-ml/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ml/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{മുന്നറിയിപ്പ് കാണുക}other{മുന്നറിയിപ്പുകൾ കാണുക}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"പേജ് തുറക്കാനായില്ല"</string>
<string name="resolving_action_error" msgid="371968886143262375">"മുന്നറിയിപ്പ് പരിഹരിക്കാനായില്ല"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ക്രമീകരണം റീഫ്രഷ് ചെയ്യാനായില്ല"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ക്രമീകരണം പരിശോധിക്കാനായില്ല}other{ക്രമീകരണം പരിശോധിക്കാനായില്ല}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ഔദ്യോഗിക പ്രൊഫൈൽ തൽക്കാലം നിർത്തിയിരിക്കുന്നു"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ഇതുവരെ വിവരങ്ങളൊന്നുമില്ല"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mn/strings.xml b/SafetyCenter/Resources/shared_res/values-mn/strings.xml
index 8b2f17f37..88f75f648 100644
--- a/SafetyCenter/Resources/shared_res/values-mn/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mn/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Сэрэмжлүүлгийг харах}other{Сэрэмжлүүлгүүдийг харах}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Хуудсыг нээж чадсангүй"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Сэрэмжлүүлгийг шийдвэрлэж чадсангүй"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Тохиргоог сэргээж чадсангүй"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Тохиргоог шалгаж чадсангүй}other{Тохиргоог шалгаж чадсангүй}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Ажлын профайлыг түр зогсоосон"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Мэдээлэл хараахан алга"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-mr/strings.xml b/SafetyCenter/Resources/shared_res/values-mr/strings.xml
index f406a2931..0b47053cd 100644
--- a/SafetyCenter/Resources/shared_res/values-mr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-mr/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{सूचना पहा}other{सूचना पहा}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"पेज उघडता आले नाही"</string>
<string name="resolving_action_error" msgid="371968886143262375">"इशाऱ्याचे निराकरण करता आले नाही"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"सेटिंग्ज रिफ्रेश करता आली नाही"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{सेटिंग तपासता आले नाही}other{सेटिंग्ज तपासता आली नाहीत}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"कार्य प्रोफाइल थांबवली आहे"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"अद्याप कोणतीही माहिती नाही"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ms/strings.xml b/SafetyCenter/Resources/shared_res/values-ms/strings.xml
index bb98c69d0..f8c3e2c32 100644
--- a/SafetyCenter/Resources/shared_res/values-ms/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ms/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Lihat makluman}other{Lihat makluman}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Tidak dapat membuka halaman"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Tidak dapat menyelesaikan amaran"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Tidak dapat menyegar semula tetapan"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Tidak dapat menyemak tetapan}other{Tidak dapat menyemak tetapan}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profil kerja dijeda"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Belum ada maklumat lagi"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-my/strings.xml b/SafetyCenter/Resources/shared_res/values-my/strings.xml
index af7f71a34..76e53c8dc 100644
--- a/SafetyCenter/Resources/shared_res/values-my/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-my/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{သတိပေးချက် ကြည့်ရန်}other{သတိပေးချက်များ ကြည့်ရန်}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"စာမျက်နှာကို ဖွင့်၍မရပါ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"သတိပေးချက်ကို ဖြေရှင်း၍မရပါ"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ဆက်တင်များကို ပြန်လည် စတင်၍မရပါ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ဆက်တင်ကြည့်၍မရပါ}other{ဆက်တင်များ ကြည့်၍မရပါ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"အလုပ်ပရိုဖိုင် ခဏရပ်ထားသည်"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"အချက်အလက် မရှိသေးပါ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-nb/strings.xml b/SafetyCenter/Resources/shared_res/values-nb/strings.xml
index 8365aaea8..61338a390 100644
--- a/SafetyCenter/Resources/shared_res/values-nb/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-nb/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Se varselet}other{Se varslene}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Kunne ikke åpne siden"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Kunne ikke løse varselet"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Kunne ikke laste inn innstillingene på nytt"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Kunne ikke sjekke innstillingen}other{Kunne ikke sjekke innstillingene}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Jobbprofilen er satt på pause"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ingen informasjon ennå"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ne/strings.xml b/SafetyCenter/Resources/shared_res/values-ne/strings.xml
index 4e55d23f1..e7c392a3b 100644
--- a/SafetyCenter/Resources/shared_res/values-ne/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ne/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{अलर्ट हेर्नुहोस्}other{अलर्टहरू हेर्नुहोस्}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"पेज खोल्न सकिएन"</string>
<string name="resolving_action_error" msgid="371968886143262375">"अलर्ट समाधान गर्न सकिएन"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"सेटिङ रिफ्रेस गर्न सकिएन"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{सेटिङ जाँच गर्न सकिएन}other{सेटिङहरू जाँच गर्न सकिएन}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"कार्य प्रोफाइल पज गरिएको छ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"कुनै जानकारी उपलब्ध छैन"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-nl/strings.xml b/SafetyCenter/Resources/shared_res/values-nl/strings.xml
index b916f37bc..ab8edda5d 100644
--- a/SafetyCenter/Resources/shared_res/values-nl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-nl/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Melding bekijken}other{Meldingen bekijken}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Kan de pagina niet openen"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Kan melding niet oplossen"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Kan instellingen niet vernieuwen"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Kan instelling niet checken}other{Kan instellingen niet checken}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Werkprofiel is onderbroken"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nog geen informatie"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-or/strings.xml b/SafetyCenter/Resources/shared_res/values-or/strings.xml
index cf550456f..341149120 100644
--- a/SafetyCenter/Resources/shared_res/values-or/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-or/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ଆଲର୍ଟ ଦେଖନ୍ତୁ}other{ଆଲର୍ଟଗୁଡ଼ିକ ଦେଖନ୍ତୁ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ପୃଷ୍ଠାକୁ ଖୋଲା ଯାଇପାରିଲା ନାହିଁ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ଆଲର୍ଟର ସମାଧାନ କରାଯାଇପାରିଲା ନାହିଁ"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ସେଟିଂସ ରିଫ୍ରେସ କରାଯାଇପାରିଲା ନାହିଁ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ସେଟିଂ ଯାଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ}other{ସେଟିଂସ ଯାଞ୍ଚ କରାଯାଇପାରିଲା ନାହିଁ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ୱାର୍କ ପ୍ରୋଫାଇଲକୁ ବିରତ କରାଯାଇଛି"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ସୂଚନା ନାହିଁ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pa/strings.xml b/SafetyCenter/Resources/shared_res/values-pa/strings.xml
index e13d6327f..54aad5cbb 100644
--- a/SafetyCenter/Resources/shared_res/values-pa/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pa/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ਅਲਰਟ ਦੇਖੋ}one{ਅਲਰਟ ਦੇਖੋ}other{ਅਲਰਟ ਦੇਖੋ}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"ਪੰਨਾ ਖੋਲ੍ਹਿਆ ਨਹੀਂ ਜਾ ਸਕਿਆ"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ਸੁਚੇਤਨਾ ਦਾ ਹੱਲ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ਸੈਟਿੰਗਾਂ ਨੂੰ ਰਿਫ੍ਰੈਸ਼ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ਸੈਟਿੰਗ ਦੀ ਜਾਂਚ ਨਹੀਂ ਕਰ ਸਕੇ}one{ਸੈਟਿੰਗ ਦੀ ਜਾਂਚ ਨਹੀਂ ਕਰ ਸਕੇ}other{ਸੈਟਿੰਗਾਂ ਦੀ ਜਾਂਚ ਨਹੀਂ ਕਰ ਸਕੇ}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ ਨੂੰ ਰੋਕਿਆ ਗਿਆ ਹੈ"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ਅਜੇ ਕੋਈ ਜਾਣਕਾਰੀ ਨਹੀਂ ਹੈ"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pl/strings.xml b/SafetyCenter/Resources/shared_res/values-pl/strings.xml
index 23a331b7f..89a242e7f 100644
--- a/SafetyCenter/Resources/shared_res/values-pl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pl/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Zobacz alert}few{Zobacz alerty}many{Zobacz alerty}other{Zobacz alerty}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Nie udało się otworzyć strony"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nie udało się rozwiązać problemu z alertu"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nie udało się odświeżyć ustawień"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nie udało się sprawdzić ustawienia}few{Nie udało się sprawdzić ustawień}many{Nie udało się sprawdzić ustawień}other{Nie udało się sprawdzić ustawień}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Wstrzymano profil służbowy"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nie ma jeszcze informacji"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml b/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
index b9d887f78..0599f1c24 100644
--- a/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt-rBR/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Mostrar alerta}one{Mostrar alerta}many{Mostrar alertas}other{Mostrar alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Não foi possível abrir a página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Não foi possível resolver o alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Não foi possível atualizar as configurações"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Não foi possível verificar a configuração}one{Não foi possível verificar a configuração}many{Não foi possível verificar as configurações}other{Não foi possível verificar as configurações}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"O perfil de trabalho está pausado"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ainda não há informações"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml b/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
index 592e1b60a..835b8a14f 100644
--- a/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt-rPT/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Veja o alerta}many{Veja os alertas}other{Veja os alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Não foi possível abrir a página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Não foi possível resolver o alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Não foi possível atualizar as definições"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Não foi possível verificar a definição}many{Não foi possível verificar as definições}other{Não foi possível verificar as definições}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Perfil de trabalho em pausa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ainda sem informações"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-pt/strings.xml b/SafetyCenter/Resources/shared_res/values-pt/strings.xml
index b9d887f78..0599f1c24 100644
--- a/SafetyCenter/Resources/shared_res/values-pt/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-pt/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Mostrar alerta}one{Mostrar alerta}many{Mostrar alertas}other{Mostrar alertas}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Não foi possível abrir a página"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Não foi possível resolver o alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Não foi possível atualizar as configurações"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Não foi possível verificar a configuração}one{Não foi possível verificar a configuração}many{Não foi possível verificar as configurações}other{Não foi possível verificar as configurações}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"O perfil de trabalho está pausado"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ainda não há informações"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ro/strings.xml b/SafetyCenter/Resources/shared_res/values-ro/strings.xml
index e64b8b497..8fa57dcb4 100644
--- a/SafetyCenter/Resources/shared_res/values-ro/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ro/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Vezi alerta}few{Vezi alertele}other{Vezi alertele}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Pagina nu s-a putut deschide"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Nu s-a putut rezolva alerta"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nu s-au putut actualiza setările"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nu s-a putut verifica setarea}few{Nu s-au putut verifica setările}other{Nu s-au putut verifica setările}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profilul de serviciu este întrerupt"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nu există informații încă"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ru/strings.xml b/SafetyCenter/Resources/shared_res/values-ru/strings.xml
index 832dd396c..b72d9b0f0 100644
--- a/SafetyCenter/Resources/shared_res/values-ru/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ru/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Посмотрите оповещение}one{Посмотрите оповещения}few{Посмотрите оповещения}many{Посмотрите оповещения}other{Посмотрите оповещения}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не удалось открыть страницу."</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не удалось устранить проблему."</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Не удалось обновить настройки"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не удалось проверить параметр}one{Не удалось проверить параметры}few{Не удалось проверить параметры}many{Не удалось проверить параметры}other{Не удалось проверить параметры}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Действие рабочего профиля приостановлено."</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Данных пока нет"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-si/strings.xml b/SafetyCenter/Resources/shared_res/values-si/strings.xml
index 08e6787cc..1ee63199f 100644
--- a/SafetyCenter/Resources/shared_res/values-si/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-si/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ඇඟවීම බලන්න}one{ඇඟවීම් බලන්න}other{ඇඟවීම් බලන්න}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"පිටුව විවෘත කළ නොහැකි විය"</string>
<string name="resolving_action_error" msgid="371968886143262375">"ඇඟවීම විසඳිය නොහැකි විය"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"සැකසීම් නැවුම් කිරීමට නොහැකි විය"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{සැකසීම පරීක්ෂා කිරීමට නොහැකි විය}one{සැකසීම් පරීක්ෂා කිරීමට නොහැකි විය}other{සැකසීම් පරීක්ෂා කිරීමට නොහැකි විය}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"කාර්යාල පැතිකඩ විරාම කර ඇත"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"තවම තතු නැත"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sk/strings.xml b/SafetyCenter/Resources/shared_res/values-sk/strings.xml
index 0c6da9c5b..905c3ffe5 100644
--- a/SafetyCenter/Resources/shared_res/values-sk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sk/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Zobraziť upozornenie}few{Zobraziť upozornenia}many{See alerts}other{Zobraziť upozornenia}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Stránku sa nepodarilo otvoriť"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Upozornenie sa nepodarilo vyriešiť"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nastavenia sa nepodarilo obnoviť"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nastavenie sa nepodarilo skontrolovať}few{Nastavenia sa nepodarilo skontrolovať}many{Nastavenia sa nepodarilo skontrolovať}other{Nastavenia sa nepodarilo skontrolovať}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Pracovný profil je pozastavený"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Zatiaľ žiadne informácie"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sl/strings.xml b/SafetyCenter/Resources/shared_res/values-sl/strings.xml
index c8b0d7d00..4502b63fd 100644
--- a/SafetyCenter/Resources/shared_res/values-sl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sl/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ogled opozorila}one{Ogled opozoril}two{Ogled opozoril}few{Ogled opozoril}other{Ogled opozoril}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Strani ni bilo mogoče odpreti."</string>
<string name="resolving_action_error" msgid="371968886143262375">"Opozorila ni bilo mogoče odpraviti."</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Nastavitev ni bilo mogoče osvežiti."</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Nastavitve ni bilo mogoče preveriti.}one{Nastavitve ni bilo mogoče preveriti.}two{Nastavitev ni bilo mogoče preveriti.}few{Nastavitev ni bilo mogoče preveriti.}other{Nastavitev ni bilo mogoče preveriti.}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Delovni profil je začasno zaustavljen."</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ni še nobenega podatka."</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sq/strings.xml b/SafetyCenter/Resources/shared_res/values-sq/strings.xml
index 68a9d3e1b..0e4981d2f 100644
--- a/SafetyCenter/Resources/shared_res/values-sq/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sq/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Shiko sinjalizimin}other{Shiko sinjalizimet}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Faqja nuk mund të hapej"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Sinjalizimi nuk mund të zgjidhej"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Cilësimet nuk mund të rifreskoheshin"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Cilësimi nuk mund të kontrollohej}other{Cilësimet nuk mund të kontrolloheshin}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Profili i punës është në pauzë"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Nuk ka ende informacione"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sr/strings.xml b/SafetyCenter/Resources/shared_res/values-sr/strings.xml
index e258d138b..e7a5db675 100644
--- a/SafetyCenter/Resources/shared_res/values-sr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sr/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Прикажи обавештење}one{Прикажи обавештења}few{Прикажи обавештења}other{Прикажи обавештења}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Отварање странице није успело"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Решавање обавештења није успело"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Освежавање подешавања није успело"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Провера подешавања није успела}one{Провера подешавања није успела}few{Провера подешавања није успела}other{Провера подешавања није успела}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Пословни профил је паузиран"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Још нема информација"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sv/strings.xml b/SafetyCenter/Resources/shared_res/values-sv/strings.xml
index a14fd7c8b..e7773a565 100644
--- a/SafetyCenter/Resources/shared_res/values-sv/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sv/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Se varning}other{Se varningar}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Det gick inte att öppna sidan"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Det gick inte att åtgärda varningen"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Det gick inte att uppdatera inställningarna"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Det gick inte att kontrollera inställningen}other{Det gick inte att kontrollera inställningarna}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Jobbprofilen är pausad"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Ingen information än"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-sw/strings.xml b/SafetyCenter/Resources/shared_res/values-sw/strings.xml
index aaea33e79..b78abb739 100644
--- a/SafetyCenter/Resources/shared_res/values-sw/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-sw/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Angalia tahadhari}other{Angalia tahadhari}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Imeshindwa kufungua ukurasa"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Imeshindwa kutia alama kuwa arifa imeshughulikiwa"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Tumeshindwa kuonyesha upya mipangilio"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Imeshindwa kukagua mipangilio}other{Imeshindwa kukagua mipangilio}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Wasifu wa kazini umesimamishwa"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Bado hakuna maelezo"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ta/strings.xml b/SafetyCenter/Resources/shared_res/values-ta/strings.xml
index 05eb793e9..3acc13e92 100644
--- a/SafetyCenter/Resources/shared_res/values-ta/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ta/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{விழிப்பூட்டலைப் பாருங்கள்}other{விழிப்பூட்டல்களைப் பாருங்கள்}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"பக்கத்தைத் திறக்க முடியவில்லை"</string>
<string name="resolving_action_error" msgid="371968886143262375">"எச்சரிக்கையைத் தீர்க்க முடியவில்லை"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"அமைப்புகளைப் புதுப்பிக்க முடியவில்லை"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{அமைப்பைச் சரிபார்க்க முடியவில்லை}other{அமைப்புகளைச் சரிபார்க்க முடியவில்லை}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"பணிக் கணக்கு இடைநிறுத்தப்பட்டது"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"தகவல்கள் எதுவுமில்லை"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-te/strings.xml b/SafetyCenter/Resources/shared_res/values-te/strings.xml
index 1fc82228d..c9ecc0f23 100644
--- a/SafetyCenter/Resources/shared_res/values-te/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-te/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{అలర్ట్‌ను చూడండి}other{అలర్ట్‌లను చూడండి}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"పేజీని తెరవడం సాధ్యపడలేదు"</string>
<string name="resolving_action_error" msgid="371968886143262375">"అలర్ట్‌ను పరిష్కరించడం సాధ్యపడలేదు"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"సెట్టింగ్‌లను రిఫ్రెష్ చేయడం సాధ్యపడలేదు"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{సెట్టింగ్‌ను చెక్ చేయడం సాధ్యపడలేదు}other{సెట్టింగ్‌లను చెక్ చేయడం సాధ్యపడలేదు}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"వర్క్ ప్రొఫైల్ పాజ్ చేయబడింది"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ఇంకా ఏ సమాచారం లేదు"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-th/strings.xml b/SafetyCenter/Resources/shared_res/values-th/strings.xml
index 37f345214..f879c3ae4 100644
--- a/SafetyCenter/Resources/shared_res/values-th/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-th/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{ดูการแจ้งเตือน}other{ดูการแจ้งเตือน}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"เปิดหน้าไม่ได้"</string>
<string name="resolving_action_error" msgid="371968886143262375">"แก้ไขการแจ้งเตือนไม่ได้"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"รีเฟรชการตั้งค่าไม่ได้"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ไม่สามารถตรวจสอบการตั้งค่า}other{ไม่สามารถตรวจสอบการตั้งค่า}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"โปรไฟล์งานหยุดชั่วคราว"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ยังไม่มีข้อมูล"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-tl/strings.xml b/SafetyCenter/Resources/shared_res/values-tl/strings.xml
index ee7210dc2..d2449116f 100644
--- a/SafetyCenter/Resources/shared_res/values-tl/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-tl/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Tingnan ang alerto}one{Tingnan ang mga alerto}other{Tingnan ang mga alerto}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Hindi mabuksan ang page"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Hindi ma-resolve ang alerto"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Hindi ma-refresh ang mga setting"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Hindi masuri ang setting}one{Hindi masuri ang mga setting}other{Hindi masuri ang mga setting}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Naka-pause ang profile sa trabaho"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Wala pang impormasyon"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-tr/strings.xml b/SafetyCenter/Resources/shared_res/values-tr/strings.xml
index 5a32bcfac..69bf874dd 100644
--- a/SafetyCenter/Resources/shared_res/values-tr/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-tr/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Uyarıya göz atın}other{Uyarılara göz atın}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Sayfa açılamadı"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Uyarı sonlandırılamadı"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Ayarlar yenilenemedi"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ayar kontrol edilemedi}other{Ayarlar kontrol edilemedi}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"İş profili duraklatıldı"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Henüz bilgi yok"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-uk/strings.xml b/SafetyCenter/Resources/shared_res/values-uk/strings.xml
index f103d5a4f..44b304f80 100644
--- a/SafetyCenter/Resources/shared_res/values-uk/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-uk/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Перегляньте сповіщення}one{Перегляньте сповіщення}few{Перегляньте сповіщення}many{Перегляньте сповіщення}other{Перегляньте сповіщення}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Не вдалося відкрити сторінку"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Не вдалося закрити сповіщення"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Не вдалось оновити налаштування"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Не вдалося перевірити налаштування}one{Не вдалося перевірити налаштування}few{Не вдалося перевірити налаштування}many{Не вдалося перевірити налаштування}other{Не вдалося перевірити налаштування}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Робочий профіль призупинено"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Поки немає інформації"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-ur/strings.xml b/SafetyCenter/Resources/shared_res/values-ur/strings.xml
index df25d9ac2..f0d791f1e 100644
--- a/SafetyCenter/Resources/shared_res/values-ur/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-ur/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{الرٹ دیکھیں}other{الرٹس دیکھیں}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"صفحہ نہیں کھل سکا"</string>
<string name="resolving_action_error" msgid="371968886143262375">"الرٹ حل نہیں ہو سکا"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"ترتیبات ریفریش نہیں کی جا سکی"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{ترتیب کی جانچ نہیں کی جا سکی}other{ترتیبات کی جانچ نہیں کی جا سکی}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"دفتری پروفائل روک دی گئی ہے"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"ابھی تک کوئی معلومات نہیں ہے"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-uz/strings.xml b/SafetyCenter/Resources/shared_res/values-uz/strings.xml
index 5965fb760..a4b933eb9 100644
--- a/SafetyCenter/Resources/shared_res/values-uz/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-uz/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Ogohlantirish}other{Ogohlantirishlar}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Sahifa ochilmadi"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ogohlantirish hal qilinmadi"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Sozlamalar yangilanmadi"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Sozlama tekshirilmadi}other{Sozlamalar tekshirilmadi}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Ish profili pauzada"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Hali axborot olinmadi"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-vi/strings.xml b/SafetyCenter/Resources/shared_res/values-vi/strings.xml
index b697186e0..8bbffeb1b 100644
--- a/SafetyCenter/Resources/shared_res/values-vi/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-vi/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Xem cảnh báo}other{Xem cảnh báo}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Không thể mở trang"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Không thể giải quyết vấn đề cảnh báo"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Không thể làm mới cài đặt"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Không kiểm tra được chế độ cài đặt}other{Không kiểm tra được các chế độ cài đặt}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Hồ sơ công việc của bạn đã bị tạm dừng"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Chưa có thông tin"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
index 1ed1355cf..bc59150b4 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rCN/strings.xml
@@ -35,12 +35,11 @@
<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="1913235490583842004">"帐号存在风险"</string>
+ <string name="overall_severity_level_account_recommendation_title" msgid="2267542168734275090">"账号可能存在风险"</string>
+ <string name="overall_severity_level_critical_account_warning_title" msgid="1913235490583842004">"账号存在风险"</string>
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{查看提醒}other{查看提醒}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"无法打开页面"</string>
<string name="resolving_action_error" msgid="371968886143262375">"无法解决提醒事项"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"无法刷新设置"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{无法检查设置}other{无法检查设置}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"工作资料已被暂停"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"尚无任何信息"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
index 0222a0acc..a2f8f75a8 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rHK/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{查看警示}other{查看警示}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"無法開啟頁面"</string>
<string name="resolving_action_error" msgid="371968886143262375">"無法解除警示"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"無法重新整理設定"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{無法檢查設定}other{無法檢查設定}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"工作設定檔已暫停"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"暫時沒有資料"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml b/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
index 1be6fa621..beb5af4a2 100644
--- a/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zh-rTW/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{查看警示}other{查看警示}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"無法開啟網頁"</string>
<string name="resolving_action_error" msgid="371968886143262375">"無法解決警示"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"無法重新整理設定"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{無法檢查設定}other{無法檢查設定}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"工作資料夾已暫停"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"目前還沒有任何資訊"</string>
diff --git a/SafetyCenter/Resources/shared_res/values-zu/strings.xml b/SafetyCenter/Resources/shared_res/values-zu/strings.xml
index 61096c234..21c358f15 100644
--- a/SafetyCenter/Resources/shared_res/values-zu/strings.xml
+++ b/SafetyCenter/Resources/shared_res/values-zu/strings.xml
@@ -40,7 +40,6 @@
<string name="overall_severity_n_alerts_summary" msgid="3262010942295408403">"{count,plural, =1{Bona isixwayiso}one{Bona izixwayiso}other{Bona izixwayiso}}"</string>
<string name="redirecting_error" msgid="8146983632878233202">"Ayikwazanga ukuvula ikhasi"</string>
<string name="resolving_action_error" msgid="371968886143262375">"Ayikwazanga ukuxazulula isexwayiso"</string>
- <string name="refresh_timeout" msgid="251734999692581852">"Ayikwazanga ukuvuselela amasethingi"</string>
<string name="refresh_error" msgid="656062128422446177">"{count,plural, =1{Ayikwazanga ukuhlola isethingi}one{Ayikwazanga ukuhlola amasethingi}other{Ayikwazanga ukuhlola amasethingi}}"</string>
<string name="work_profile_paused" msgid="7037400224040869079">"Iphrofayela yomsebenzi iphunyuziwe"</string>
<string name="group_unknown_summary" msgid="6951386960814105641">"Alukho ulwazi okwamanje"</string>
diff --git a/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java b/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java
index 17688063a..d98127300 100644
--- a/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java
+++ b/service/java/com/android/safetycenter/SafetyCenterRefreshTracker.java
@@ -155,8 +155,7 @@ public final class SafetyCenterRefreshTracker {
*/
public boolean reportSourceRefreshCompleted(
String refreshBroadcastId,
- String sourceId,
- @UserIdInt int userId,
+ SafetySourceKey safetySourceKey,
boolean successful,
boolean dataChanged) {
RefreshInProgress refreshInProgress =
@@ -165,9 +164,9 @@ public final class SafetyCenterRefreshTracker {
return false;
}
- SafetySourceKey sourceKey = SafetySourceKey.of(sourceId, userId);
Duration duration =
- refreshInProgress.markSourceRefreshComplete(sourceKey, successful, dataChanged);
+ refreshInProgress.markSourceRefreshComplete(
+ safetySourceKey, successful, dataChanged);
int refreshReason = refreshInProgress.getReason();
int requestType = RefreshReasons.toRefreshRequestType(refreshReason);
@@ -175,8 +174,8 @@ public final class SafetyCenterRefreshTracker {
int sourceResult = toSystemEventResult(successful);
SafetyCenterStatsdLogger.writeSourceRefreshSystemEvent(
requestType,
- sourceId,
- UserUtils.isManagedProfile(userId, mContext),
+ safetySourceKey.getSourceId(),
+ UserUtils.isManagedProfile(safetySourceKey.getUserId(), mContext),
duration,
sourceResult,
refreshReason,
diff --git a/service/java/com/android/safetycenter/SafetySourceIssues.java b/service/java/com/android/safetycenter/SafetySourceIssues.java
new file mode 100644
index 000000000..dc3c2a83e
--- /dev/null
+++ b/service/java/com/android/safetycenter/SafetySourceIssues.java
@@ -0,0 +1,87 @@
+/*
+ * 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.safetycenter;
+
+import android.safetycenter.SafetySourceIssue;
+
+import androidx.annotation.Nullable;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import java.util.List;
+
+/**
+ * A helper class to facilitate working with {@link SafetySourceIssue} objects.
+ *
+ * @hide
+ */
+public final class SafetySourceIssues {
+
+ /**
+ * Returns the {@link SafetySourceIssue.Action} with the given action ID belonging to the given
+ * {@link SafetySourceIssue} or {@code null} if no such action is present.
+ *
+ * <p>The action will either belong to the issue directly from {@link
+ * SafetySourceIssue#getActions()} or via {@link SafetySourceIssue#getCustomNotification()} if
+ * the issue has a custom notification.
+ */
+ @Nullable
+ public static SafetySourceIssue.Action findAction(SafetySourceIssue issue, String actionId) {
+ SafetySourceIssue.Action action = null;
+ if (SdkLevel.isAtLeastU() && issue.getCustomNotification() != null) {
+ action = findAction(issue.getCustomNotification().getActions(), actionId);
+ }
+ if (action == null) {
+ action = findAction(issue.getActions(), actionId);
+ }
+ return action;
+ }
+
+ @Nullable
+ private static SafetySourceIssue.Action findAction(
+ List<SafetySourceIssue.Action> actions, String actionId) {
+ for (int i = 0; i < actions.size(); i++) {
+ SafetySourceIssue.Action action = actions.get(i);
+ if (action.getId().equals(actionId)) {
+ return action;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns {@code true} if {@code actionId} corresponds to a "primary" action of the given
+ * {@code issue}, or {@code false} if the action is not the primary or if no action with the
+ * given ID is found.
+ *
+ * <p>A primary action is the first action of either the issue, or its custom notification.
+ */
+ public static boolean isPrimaryAction(SafetySourceIssue issue, String actionId) {
+ boolean isPrimaryNotificationAction =
+ SdkLevel.isAtLeastU()
+ && issue.getCustomNotification() != null
+ && matchesFirst(issue.getCustomNotification().getActions(), actionId);
+ boolean isPrimaryIssueAction = matchesFirst(issue.getActions(), actionId);
+ return isPrimaryNotificationAction || isPrimaryIssueAction;
+ }
+
+ private static boolean matchesFirst(List<SafetySourceIssue.Action> actions, String actionId) {
+ return !actions.isEmpty() && actions.get(0).getId().equals(actionId);
+ }
+
+ private SafetySourceIssues() {}
+}
diff --git a/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java b/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java
index 3925b64aa..018fedf41 100644
--- a/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java
+++ b/service/java/com/android/safetycenter/data/SafetyCenterDataManager.java
@@ -133,7 +133,7 @@ public final class SafetyCenterDataManager {
safetySourceData, safetySourceId, packageName, userId)) {
return false;
}
- SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
+ SafetySourceKey safetySourceKey = SafetySourceKey.of(safetySourceId, userId);
// Must fetch refresh reason before calling processSafetyEvent because the latter may
// complete and clear the current refresh.
@@ -143,16 +143,15 @@ public final class SafetyCenterDataManager {
refreshReason = mSafetyCenterRefreshTracker.getRefreshReason();
}
- boolean sourceDataDiffers =
- mSafetySourceDataRepository.setSafetySourceData(
- safetySourceData, safetySourceId, userId);
+ // It is important to process the event first as it relies on the data available prior to
+ // changing it.
+ boolean sourceDataWillChange =
+ !mSafetySourceDataRepository.sourceHasData(safetySourceKey, safetySourceData);
boolean eventCausedChange =
processSafetyEvent(
- safetySourceId,
- safetyEvent,
- userId,
- /* isError= */ false,
- sourceDataDiffers);
+ safetySourceKey, safetyEvent, /* isError= */ false, sourceDataWillChange);
+ boolean sourceDataDiffers =
+ mSafetySourceDataRepository.setSafetySourceData(safetySourceKey, safetySourceData);
boolean safetyCenterDataChanged = sourceDataDiffers || eventCausedChange;
if (safetyCenterDataChanged) {
@@ -160,7 +159,12 @@ public final class SafetyCenterDataManager {
}
mSafetySourceStateCollectedLogger.writeSourceUpdatedAtom(
- key, safetySourceData, refreshReason, sourceDataDiffers, userId, safetyEvent);
+ safetySourceKey,
+ safetySourceData,
+ refreshReason,
+ sourceDataDiffers,
+ userId,
+ safetyEvent);
return safetyCenterDataChanged;
}
@@ -204,7 +208,7 @@ public final class SafetyCenterDataManager {
return false;
}
SafetyEvent safetyEvent = safetySourceErrorDetails.getSafetyEvent();
- SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
+ SafetySourceKey safetySourceKey = SafetySourceKey.of(safetySourceId, userId);
// Must fetch refresh reason before calling processSafetyEvent because the latter may
// complete and clear the current refresh.
@@ -214,16 +218,15 @@ public final class SafetyCenterDataManager {
refreshReason = mSafetyCenterRefreshTracker.getRefreshReason();
}
- boolean sourceDataDiffers =
- mSafetySourceDataRepository.reportSafetySourceError(
- safetySourceErrorDetails, safetySourceId, userId);
+ // It is important to process the event first as it relies on the data available prior to
+ // changing it.
+ boolean sourceDataWillChange = !mSafetySourceDataRepository.sourceHasError(safetySourceKey);
boolean eventCausedChange =
processSafetyEvent(
- safetySourceId,
- safetyEvent,
- userId,
- /* isError= */ true,
- sourceDataDiffers);
+ safetySourceKey, safetyEvent, /* isError= */ true, sourceDataWillChange);
+ boolean sourceDataDiffers =
+ mSafetySourceDataRepository.reportSafetySourceError(
+ safetySourceKey, safetySourceErrorDetails);
boolean safetyCenterDataChanged = sourceDataDiffers || eventCausedChange;
if (safetyCenterDataChanged) {
@@ -231,7 +234,7 @@ public final class SafetyCenterDataManager {
}
mSafetySourceStateCollectedLogger.writeSourceUpdatedAtom(
- key,
+ safetySourceKey,
/* safetySourceData= */ null,
refreshReason,
sourceDataDiffers,
@@ -488,11 +491,10 @@ public final class SafetyCenterDataManager {
}
private boolean processSafetyEvent(
- String safetySourceId,
+ SafetySourceKey safetySourceKey,
SafetyEvent safetyEvent,
- @UserIdInt int userId,
boolean isError,
- boolean sourceDataChanged) {
+ boolean sourceDataWillChange) {
int type = safetyEvent.getType();
switch (type) {
case SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED:
@@ -502,7 +504,7 @@ public final class SafetyCenterDataManager {
return false;
}
return mSafetyCenterRefreshTracker.reportSourceRefreshCompleted(
- refreshBroadcastId, safetySourceId, userId, !isError, sourceDataChanged);
+ refreshBroadcastId, safetySourceKey, !isError, sourceDataWillChange);
case SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED:
case SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED:
String safetySourceIssueId = safetyEvent.getSafetySourceIssueId();
@@ -517,9 +519,9 @@ public final class SafetyCenterDataManager {
}
SafetyCenterIssueKey safetyCenterIssueKey =
SafetyCenterIssueKey.newBuilder()
- .setSafetySourceId(safetySourceId)
+ .setSafetySourceId(safetySourceKey.getSourceId())
.setSafetySourceIssueId(safetySourceIssueId)
- .setUserId(userId)
+ .setUserId(safetySourceKey.getUserId())
.build();
SafetyCenterIssueActionId safetyCenterIssueActionId =
SafetyCenterIssueActionId.newBuilder()
diff --git a/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java b/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java
index bcf83dd9e..82eb3a6c7 100644
--- a/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java
+++ b/service/java/com/android/safetycenter/data/SafetyCenterInFlightIssueActionRepository.java
@@ -29,13 +29,13 @@ import android.util.Log;
import androidx.annotation.Nullable;
import com.android.permission.util.UserUtils;
+import com.android.safetycenter.SafetySourceIssues;
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
import com.android.safetycenter.logging.SafetyCenterStatsdLogger;
import java.io.PrintWriter;
import java.time.Duration;
-import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;
@@ -135,18 +135,8 @@ final class SafetyCenterInFlightIssueActionRepository {
return null;
}
- List<SafetySourceIssue.Action> safetySourceIssueActions = safetySourceIssue.getActions();
- for (int i = 0; i < safetySourceIssueActions.size(); i++) {
- SafetySourceIssue.Action safetySourceIssueAction = safetySourceIssueActions.get(i);
-
- if (safetyCenterIssueActionId
- .getSafetySourceIssueActionId()
- .equals(safetySourceIssueAction.getId())) {
- return safetySourceIssueAction;
- }
- }
-
- return null;
+ return SafetySourceIssues.findAction(
+ safetySourceIssue, safetyCenterIssueActionId.getSafetySourceIssueActionId());
}
/** Dumps in-flight action data for debugging purposes. */
diff --git a/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java b/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java
index 15e420dce..cdb8709d6 100644
--- a/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java
+++ b/service/java/com/android/safetycenter/data/SafetySourceDataRepository.java
@@ -75,9 +75,8 @@ final class SafetySourceDataRepository {
}
/**
- * Sets the latest {@link SafetySourceData} for the given {@code safetySourceId}, {@link
- * SafetyEvent}, {@code packageName} and {@code userId}, and returns {@code true} if this caused
- * any changes which would alter {@link SafetyCenterData}.
+ * Sets the latest {@link SafetySourceData} for the given {@link SafetySourceKey}, and returns
+ * {@code true} if this caused any changes which would alter {@link SafetyCenterData}.
*
* <p>This method does not perform any validation, {@link
* SafetyCenterDataManager#setSafetySourceData(SafetySourceData, String, SafetyEvent, String,
@@ -90,18 +89,16 @@ final class SafetySourceDataRepository {
* <p>This method may modify the {@link SafetyCenterIssueDismissalRepository}.
*/
boolean setSafetySourceData(
- @Nullable SafetySourceData safetySourceData,
- String safetySourceId,
- @UserIdInt int userId) {
- SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
- boolean sourceDataDiffers = !Objects.equals(safetySourceData, mSafetySourceData.get(key));
- boolean removedSourceError = mSafetySourceErrors.remove(key);
+ SafetySourceKey safetySourceKey, @Nullable SafetySourceData safetySourceData) {
+ boolean sourceDataDiffers =
+ !Objects.equals(safetySourceData, mSafetySourceData.get(safetySourceKey));
+ boolean removedSourceError = mSafetySourceErrors.remove(safetySourceKey);
if (sourceDataDiffers) {
- setSafetySourceDataInternal(key, safetySourceData);
+ setSafetySourceDataInternal(safetySourceKey, safetySourceData);
}
- setLastUpdatedNow(key);
+ setLastUpdatedNow(safetySourceKey);
return sourceDataDiffers || removedSourceError;
}
@@ -143,19 +140,33 @@ final class SafetySourceDataRepository {
}
/**
- * Reports the given {@link SafetySourceErrorDetails} for the given {@code safetySourceId} and
- * {@code userId}, and returns {@code true} if this changed the repository's data.
+ * Returns whether the repository has the given {@link SafetySourceData} for the given {@link
+ * SafetySourceKey}.
+ */
+ boolean sourceHasData(
+ SafetySourceKey safetySourceKey, @Nullable SafetySourceData safetySourceData) {
+ if (mSafetySourceErrors.contains(safetySourceKey)) {
+ // Any error will cause the SafetySourceData to be discarded in favor of an error
+ // message, so it can't possibly match the SafetySourceData passed in parameter.
+ return false;
+ }
+ return Objects.equals(safetySourceData, mSafetySourceData.get(safetySourceKey));
+ }
+
+ /**
+ * Reports the given {@link SafetySourceErrorDetails} for the given {@link SafetySourceKey}, and
+ * returns {@code true} if this changed the repository's data.
*
* <p>This method does not perform any validation, {@link
* SafetyCenterDataManager#reportSafetySourceError(SafetySourceErrorDetails, String, String,
* int)} should be called wherever validation is required.
*/
boolean reportSafetySourceError(
- SafetySourceErrorDetails safetySourceErrorDetails,
- String safetySourceId,
- @UserIdInt int userId) {
+ SafetySourceKey safetySourceKey, SafetySourceErrorDetails safetySourceErrorDetails) {
SafetyEvent safetyEvent = safetySourceErrorDetails.getSafetyEvent();
- Log.w(TAG, "Error reported from source: " + safetySourceId + ", for event: " + safetyEvent);
+ Log.w(
+ TAG,
+ "Error reported from source: " + safetySourceKey + ", for event: " + safetyEvent);
int safetyEventType = safetyEvent.getType();
if (safetyEventType == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED
@@ -163,9 +174,9 @@ final class SafetySourceDataRepository {
return false;
}
- SafetySourceKey sourceKey = SafetySourceKey.of(safetySourceId, userId);
- mSourceStates.put(sourceKey, SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_ERROR);
- return setSafetySourceError(sourceKey);
+ mSourceStates.put(
+ safetySourceKey, SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_ERROR);
+ return setSafetySourceError(safetySourceKey);
}
/**
diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java
index 29af9a99b..ed0e95177 100644
--- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java
+++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationReceiver.java
@@ -33,6 +33,7 @@ import com.android.safetycenter.PendingIntentFactory;
import com.android.safetycenter.SafetyCenterDataChangeNotifier;
import com.android.safetycenter.SafetyCenterFlags;
import com.android.safetycenter.SafetyCenterService;
+import com.android.safetycenter.SafetySourceIssues;
import com.android.safetycenter.UserProfileGroup;
import com.android.safetycenter.data.SafetyCenterDataManager;
import com.android.safetycenter.internaldata.SafetyCenterIds;
@@ -242,16 +243,8 @@ public final class SafetyCenterNotificationReceiver extends BroadcastReceiver {
UserUtils.isManagedProfile(issueKey.getUserId(), context),
issue.getIssueTypeId(),
issue.getSeverityLevel(),
- isPrimaryAction(issue, issueActionId));
+ SafetySourceIssues.isPrimaryAction(
+ issue, issueActionId.getSafetySourceIssueActionId()));
}
}
-
- /** Returns {@code true} if {@code actionId} is the first action of {@code issue}. */
- private boolean isPrimaryAction(SafetySourceIssue issue, SafetyCenterIssueActionId actionId) {
- return !issue.getActions().isEmpty()
- && issue.getActions()
- .get(0)
- .getId()
- .equals(actionId.getSafetySourceIssueActionId());
- }
}
diff --git a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java
index c74753050..d17090c34 100644
--- a/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java
+++ b/service/java/com/android/safetycenter/notifications/SafetyCenterNotificationSender.java
@@ -40,6 +40,7 @@ import com.android.modules.utils.build.SdkLevel;
import com.android.permission.util.UserUtils;
import com.android.safetycenter.SafetyCenterFlags;
import com.android.safetycenter.SafetySourceIssueInfo;
+import com.android.safetycenter.SafetySourceIssues;
import com.android.safetycenter.UserProfileGroup;
import com.android.safetycenter.data.SafetyCenterDataManager;
import com.android.safetycenter.internaldata.SafetyCenterIds;
@@ -177,12 +178,8 @@ public final class SafetyCenterNotificationSender {
return;
}
- SafetySourceIssue.Action successfulAction = null;
- for (int i = 0; i < notifiedIssue.getActions().size(); i++) {
- if (notifiedIssue.getActions().get(i).getId().equals(sourceIssueActionId)) {
- successfulAction = notifiedIssue.getActions().get(i);
- }
- }
+ SafetySourceIssue.Action successfulAction =
+ SafetySourceIssues.findAction(notifiedIssue, sourceIssueActionId);
if (successfulAction == null) {
Log.w(TAG, "Successful action not found");
return;
diff --git a/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt b/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
index 1806f8e13..d90ffade9 100644
--- a/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
+++ b/tests/apex/java/com/android/role/persistence/RolesPersistenceTest.kt
@@ -91,7 +91,7 @@ class RolesPersistenceTest {
.writeText("<roles version=\"-1\"><role name=\"com.foo.bar\"><holder")
val persistedState = persistence.readForUser(user)
- checkPersistedState(persistedState!!)
+ checkPersistedState(persistedState)
}
@Test
@@ -103,11 +103,11 @@ class RolesPersistenceTest {
assertThat(persistedState).isNull()
}
- private fun checkPersistedState(persistedState: RolesState) {
+ private fun checkPersistedState(persistedState: RolesState?) {
assertThat(persistedState).isEqualTo(state)
- assertThat(persistedState.version).isEqualTo(state.version)
- assertThat(persistedState.packagesHash).isEqualTo(state.packagesHash)
- assertThat(persistedState.roles).isEqualTo(state.roles)
+ assertThat(persistedState?.version).isEqualTo(state.version)
+ assertThat(persistedState?.packagesHash).isEqualTo(state.packagesHash)
+ assertThat(persistedState?.roles).isEqualTo(state.roles)
}
companion object {
diff --git a/tests/cts/permission/Android.bp b/tests/cts/permission/Android.bp
index 47d3b1200..4749124eb 100644
--- a/tests/cts/permission/Android.bp
+++ b/tests/cts/permission/Android.bp
@@ -51,6 +51,7 @@ android_test {
"safety-center-internal-data",
"sts-device-util",
"platform-test-rules",
+ "CtsVirtualDeviceCommonLib",
],
jni_libs: [
"libctspermission_jni",
@@ -120,6 +121,7 @@ android_test {
":CtsAppThatRequestsSystemAlertWindow22",
":CtsAppThatRequestsSystemAlertWindow23",
":CtsAppThatRequestCustomCameraPermission",
+ ":CtsAppThatRequestsDevicePermissions",
],
per_testcase_directory: true,
}
diff --git a/tests/cts/permission/AndroidTest.xml b/tests/cts/permission/AndroidTest.xml
index 3773a889d..2a272359a 100644
--- a/tests/cts/permission/AndroidTest.xml
+++ b/tests/cts/permission/AndroidTest.xml
@@ -107,6 +107,7 @@
<option name="push" value="CtsAppThatRequestsSystemAlertWindow22.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsSystemAlertWindow22.apk" />
<option name="push" value="CtsAppThatRequestsSystemAlertWindow23.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsSystemAlertWindow23.apk" />
<option name="push" value="CtsAppThatRequestCustomCameraPermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestCustomCameraPermission.apk" />
+ <option name="push" value="CtsAppThatRequestsDevicePermissions.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsDevicePermissions.apk" />
</target_preparer>
<!-- Remove additional apps if installed -->
diff --git a/tests/cts/permission/AppThatRequestDevicePermissions/Android.bp b/tests/cts/permission/AppThatRequestDevicePermissions/Android.bp
new file mode 100644
index 000000000..bb02dbe4b
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestDevicePermissions/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsAppThatRequestsDevicePermissions",
+ defaults: ["cts_defaults"],
+ sdk_version: "current",
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+}
diff --git a/tests/cts/permission/AppThatRequestDevicePermissions/AndroidManifest.xml b/tests/cts/permission/AppThatRequestDevicePermissions/AndroidManifest.xml
new file mode 100644
index 000000000..5fcd575c7
--- /dev/null
+++ b/tests/cts/permission/AppThatRequestDevicePermissions/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.permission.cts.appthatrequestpermission">
+
+ <uses-permission android:name="android.permission.CAMERA" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+ <application />
+</manifest>
diff --git a/tests/cts/permission/nativeTests/AndroidTest.xml b/tests/cts/permission/nativeTests/AndroidTest.xml
index 7d1bdb04f..f477231ef 100644
--- a/tests/cts/permission/nativeTests/AndroidTest.xml
+++ b/tests/cts/permission/nativeTests/AndroidTest.xml
@@ -15,7 +15,7 @@
-->
<configuration description="Config for CTS PermissionManager native test cases">
<option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="component" value="permissions" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
diff --git a/tests/cts/permission/sdk28/AndroidTest.xml b/tests/cts/permission/sdk28/AndroidTest.xml
index d1b5ac615..391142964 100644
--- a/tests/cts/permission/sdk28/AndroidTest.xml
+++ b/tests/cts/permission/sdk28/AndroidTest.xml
@@ -15,7 +15,7 @@
-->
<configuration description="Config for CTS Permission test cases for TargetSdk 28">
<option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="component" value="permissions" />
<option name="not-shardable" value="true" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
diff --git a/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt b/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt
new file mode 100644
index 000000000..fb83d7978
--- /dev/null
+++ b/tests/cts/permission/src/android/permission/cts/DevicePermissionsTest.kt
@@ -0,0 +1,113 @@
+/*
+ * 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 android.permission.cts
+
+import android.Manifest
+import android.app.Instrumentation
+import android.companion.virtual.VirtualDeviceManager
+import android.companion.virtual.VirtualDeviceManager.VirtualDevice
+import android.companion.virtual.VirtualDeviceParams
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.UserHandle
+import android.virtualdevice.cts.common.FakeAssociationRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.AdoptShellPermissionsRule
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assume.assumeNotNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, codeName = "VanillaIceCream")
+class DevicePermissionsTest {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context = instrumentation.targetContext
+
+ private lateinit var virtualDeviceManager: VirtualDeviceManager
+ private lateinit var virtualDevice: VirtualDevice
+ private lateinit var deviceContext: Context
+
+ @get:Rule var mFakeAssociationRule = FakeAssociationRule()
+
+ @get:Rule
+ val mAdoptShellPermissionsRule = AdoptShellPermissionsRule(
+ instrumentation.uiAutomation, Manifest.permission.CREATE_VIRTUAL_DEVICE
+ )
+
+ @Before
+ fun setup() {
+ virtualDeviceManager = context.getSystemService(VirtualDeviceManager::class.java)!!
+ virtualDevice = virtualDeviceManager.createVirtualDevice(
+ mFakeAssociationRule.getAssociationInfo().getId(),
+ VirtualDeviceParams.Builder().build()
+ )
+ assumeNotNull(virtualDevice)
+ deviceContext = context.createDeviceContext(virtualDevice.deviceId)
+ runShellCommand("pm install -r $TEST_APK")
+ }
+
+ @After
+ fun cleanup() {
+ runShellCommand("pm uninstall $TEST_PACKAGE_NAME")
+ }
+
+ @Test
+ fun testPermissionGrant() {
+ val packageManager = deviceContext.packageManager
+ runWithShellPermissionIdentity {
+ packageManager.grantRuntimePermission(
+ TEST_PACKAGE_NAME, Manifest.permission.CAMERA, UserHandle.of(context.userId))
+ }
+ assertThat(packageManager.checkPermission(Manifest.permission.CAMERA, TEST_PACKAGE_NAME))
+ .isEqualTo(PackageManager.PERMISSION_GRANTED)
+ }
+
+ @Test
+ fun testPermissionRevoke() {
+ val packageManager = deviceContext.packageManager
+ runWithShellPermissionIdentity {
+ packageManager.grantRuntimePermission(
+ TEST_PACKAGE_NAME, Manifest.permission.RECORD_AUDIO, UserHandle.of(context.userId))
+ }
+ assertThat(
+ packageManager.checkPermission(Manifest.permission.RECORD_AUDIO, TEST_PACKAGE_NAME))
+ .isEqualTo(PackageManager.PERMISSION_GRANTED)
+
+ runWithShellPermissionIdentity {
+ packageManager.revokeRuntimePermission(
+ TEST_PACKAGE_NAME, Manifest.permission.RECORD_AUDIO, UserHandle.of(context.userId))
+ }
+ assertThat(
+ packageManager.checkPermission(Manifest.permission.RECORD_AUDIO, TEST_PACKAGE_NAME))
+ .isEqualTo(PackageManager.PERMISSION_DENIED)
+ }
+
+ companion object {
+ private const val TEST_PACKAGE_NAME = "android.permission.cts.appthatrequestpermission"
+ private const val TEST_APK =
+ "/data/local/tmp/cts/permissions/CtsAppThatRequestsDevicePermissions.apk"
+ }
+}
diff --git a/tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java
index 5041adcf2..af60289b4 100644
--- a/tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java
+++ b/tests/cts/permission/src/android/permission/cts/NearbyDevicesPermissionTest.java
@@ -26,6 +26,7 @@ import static android.permission.cts.PermissionUtils.revokePermission;
import static android.permission.cts.PermissionUtils.uninstallApp;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -38,8 +39,10 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
+import android.os.Process;
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
@@ -84,22 +87,41 @@ public class NearbyDevicesPermissionTest {
UNKNOWN, EXCEPTION, EMPTY, FILTERED, FULL
}
- private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
- private BluetoothAdapter mBluetoothAdapter;
- private boolean mBluetoothAdapterWasEnabled;
+ private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ private final LocationManager mLocationManager =
+ mContext.getSystemService(LocationManager.class);
+ private final BluetoothManager mBluetoothManager =
+ mContext.getSystemService(BluetoothManager.class);
+ private final BluetoothAdapter mBluetoothAdapter = mBluetoothManager.getAdapter();
+ private boolean mBluetoothAdapterWasEnabled = true;
+ private boolean mLocationWasEnabled = true;
+
+ private boolean enableLocation() throws Exception {
+ boolean locationWasEnabled = mLocationManager.isLocationEnabled();
+ if (!locationWasEnabled) {
+ runWithShellPermissionIdentity(() -> {
+ mLocationManager.setLocationEnabledForUser(true, Process.myUserHandle());
+ });
+ }
+ return locationWasEnabled;
+ }
- @Before
- public void enableBluetooth() {
- assumeTrue(supportsBluetooth());
- mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter();
- mBluetoothAdapterWasEnabled = mBluetoothAdapter.isEnabled();
- assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext));
+ private void disableLocation() throws Exception {
+ runWithShellPermissionIdentity(() -> {
+ mLocationManager.setLocationEnabledForUser(false, Process.myUserHandle());
+ });
+ }
+
+ private boolean enableBluetooth() {
+ boolean bluetoothAdapterWasEnabled = mBluetoothAdapter.isEnabled();
+ if (!bluetoothAdapterWasEnabled) {
+ assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext));
+ }
enableTestMode();
+ return bluetoothAdapterWasEnabled;
}
- @After
- public void disableBluetooth() {
- assumeTrue(supportsBluetooth());
+ private void disableBluetooth() {
disableTestMode();
if (!mBluetoothAdapterWasEnabled) {
assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
@@ -107,10 +129,25 @@ public class NearbyDevicesPermissionTest {
}
@Before
+ public void setUp() throws Exception {
+ assumeTrue(supportsBluetooth());
+ uninstallApp(DISAVOWAL_APP_PKG);
+ uninstallApp(TEST_APP_PKG);
+ mLocationWasEnabled = enableLocation();
+ mBluetoothAdapterWasEnabled = enableBluetooth();
+ }
+
@After
- public void uninstallTestApp() {
+ public void tearDown() throws Exception {
+ assumeTrue(supportsBluetooth());
uninstallApp(TEST_APP_PKG);
uninstallApp(DISAVOWAL_APP_PKG);
+ if (!mBluetoothAdapterWasEnabled) {
+ disableBluetooth();
+ }
+ if (!mLocationWasEnabled) {
+ disableLocation();
+ }
}
@Test
diff --git a/tests/cts/permissionmultiuser/AndroidTest.xml b/tests/cts/permissionmultiuser/AndroidTest.xml
index 10847c7c6..d70908294 100644
--- a/tests/cts/permissionmultiuser/AndroidTest.xml
+++ b/tests/cts/permissionmultiuser/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="component" value="permissions" />
<option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
diff --git a/tests/cts/permissionpolicy/AndroidTest.xml b/tests/cts/permissionpolicy/AndroidTest.xml
index ac27a0366..6b75949aa 100644
--- a/tests/cts/permissionpolicy/AndroidTest.xml
+++ b/tests/cts/permissionpolicy/AndroidTest.xml
@@ -18,7 +18,7 @@
<option name="test-suite-tag" value="cts" />
<option name="not-shardable" value="true" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="component" value="permissions" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
index eb663acbd..2caf75bbb 100644
--- a/tests/cts/permissionpolicy/res/raw/android_manifest.xml
+++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml
@@ -2193,6 +2193,14 @@
<permission android:name="android.permission.MANAGE_ETHERNET_NETWORKS"
android:protectionLevel="signature" />
+ <!-- Allows system apps to call methods to register itself as a mDNS offload engine.
+ <p>Not for use by third-party or privileged applications.
+ @SystemApi
+ @hide This should only be used by system apps.
+ -->
+ <permission android:name="android.permission.REGISTER_NSD_OFFLOAD_ENGINE"
+ android:protectionLevel="signature" />
+
<!-- ======================================= -->
<!-- Permissions for short range, peripheral networks -->
<!-- ======================================= -->
diff --git a/tests/cts/permissionui/AndroidTest.xml b/tests/cts/permissionui/AndroidTest.xml
index 5c74df2b6..6dbf998f0 100644
--- a/tests/cts/permissionui/AndroidTest.xml
+++ b/tests/cts/permissionui/AndroidTest.xml
@@ -20,7 +20,7 @@
<option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="component" value="permissions" />
<option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
<option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
diff --git a/tests/cts/permissionui/PermissionPolicyApp25/src/android/permissionui/cts/permissionpolicy/TestProtectionFlagsActivity.kt b/tests/cts/permissionui/PermissionPolicyApp25/src/android/permissionui/cts/permissionpolicy/TestProtectionFlagsActivity.kt
index b5fea9b8f..35e61d0c2 100644
--- a/tests/cts/permissionui/PermissionPolicyApp25/src/android/permissionui/cts/permissionpolicy/TestProtectionFlagsActivity.kt
+++ b/tests/cts/permissionui/PermissionPolicyApp25/src/android/permissionui/cts/permissionpolicy/TestProtectionFlagsActivity.kt
@@ -40,7 +40,7 @@ class TestProtectionFlagsActivity : Activity() {
private fun getProtectionFlagsErrorMessage(): String {
val packageInfo = packageManager.getPackageInfo("android", PackageManager.GET_PERMISSIONS)
val errorMessageBuilder = StringBuilder()
- for (declaredPermissionInfo in packageInfo.permissions) {
+ for (declaredPermissionInfo in packageInfo.permissions ?: emptyArray()) {
val permissionInfo = packageManager.getPermissionInfo(declaredPermissionInfo.name, 0)
val protection = permissionInfo.protection and (
PermissionInfo.PROTECTION_NORMAL
diff --git a/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt b/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
index 759700a12..e7977b459 100644
--- a/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
+++ b/tests/cts/permissionui/UsePermissionAppLocationProvider/src/android/permissionui/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
@@ -28,7 +28,7 @@ class AddLocationProviderActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val attrContext = createAttributionContext("test.tag")
- val locationManager = attrContext.getSystemService(LocationManager::class.java)
+ val locationManager = attrContext.getSystemService(LocationManager::class.java)!!
locationManager.addTestProvider(
packageName, false, false, false, false, false, false, false, Criteria.POWER_LOW,
Criteria.ACCURACY_COARSE
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt
index b14603fd2..b6a8b3fbf 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/LocationProviderInterceptDialogTest.kt
@@ -22,6 +22,7 @@ import android.content.ComponentName
import android.content.Intent
import android.location.LocationManager
import android.os.Build
+import android.permission.cts.MtsIgnore
import android.permission.cts.PermissionUtils
import android.platform.test.annotations.FlakyTest
import androidx.test.filters.SdkSuppress
@@ -34,6 +35,7 @@ import java.util.concurrent.TimeUnit
import org.junit.Assert
import org.junit.Assume.assumeFalse
import org.junit.Before
+import org.junit.Ignore
import org.junit.Test
private const val EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME"
@@ -62,6 +64,8 @@ class LocationProviderInterceptDialogTest : BaseUsePermissionTest() {
}
@Test
+ @Ignore("b/288471744")
+ @MtsIgnore(bugId = 288471744)
fun clickLocationPermission_showDialog_clickOk() {
openPermissionScreenForApp()
clickAndWaitForWindowTransition(By.text("Location"))
@@ -72,6 +76,8 @@ class LocationProviderInterceptDialogTest : BaseUsePermissionTest() {
}
@Test
+ @Ignore("b/288471744")
+ @MtsIgnore(bugId = 288471744)
fun clickLocationPermission_showDialog_clickLocationAccess() {
openPermissionScreenForApp()
clickAndWaitForWindowTransition(By.text("Location"))
@@ -83,6 +89,8 @@ class LocationProviderInterceptDialogTest : BaseUsePermissionTest() {
}
@Test
+ @Ignore("b/288471744")
+ @MtsIgnore(bugId = 288471744)
fun checkRestrictedPermissions() {
context.sendBroadcast(Intent(PermissionTapjackingTest.ACTION_SHOW_OVERLAY)
.putExtra("package", MIC_LOCATION_PROVIDER_APP_PACKAGE_NAME)
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt
index 92dc47a05..63ef1fb8f 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/SensorBlockedBannerTest.kt
@@ -26,13 +26,11 @@ import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
import android.location.LocationManager
import android.os.Build
import android.platform.test.annotations.FlakyTest
-import android.provider.DeviceConfig
import androidx.test.filters.SdkSuppress
import androidx.test.uiautomator.By
import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
import java.util.regex.Pattern
-import org.junit.After
import org.junit.Assume
import org.junit.Before
import org.junit.Test
@@ -45,16 +43,11 @@ import org.junit.Test
class SensorBlockedBannerTest : BaseUsePermissionTest() {
companion object {
const val LOCATION = -1
- const val WARNING_BANNER_ENABLED = "warning_banner_enabled"
const val DELAY_MILLIS = 3000L
}
val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!!
val locationManager = context.getSystemService(LocationManager::class.java)!!
- private val originalEnabledValue = callWithShellPermissionIdentity {
- DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY,
- WARNING_BANNER_ENABLED, false.toString())
- }
private val sensorToPermissionGroup = mapOf(CAMERA to CAMERA_PERMISSION_GROUP,
MICROPHONE to MICROPHONE_PERMISSION_GROUP,
@@ -72,18 +65,6 @@ class SensorBlockedBannerTest : BaseUsePermissionTest() {
// be support in T or below
Assume.assumeFalse(isAutomotive)
installPackage(APP_APK_PATH_31)
- runWithShellPermissionIdentity {
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
- WARNING_BANNER_ENABLED, true.toString(), false)
- }
- }
-
- @After
- fun restoreWarningBannerState() {
- runWithShellPermissionIdentity {
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
- WARNING_BANNER_ENABLED, originalEnabledValue, false)
- }
}
private fun navigateAndTest(sensor: Int) {
diff --git a/tests/cts/role/Android.bp b/tests/cts/role/Android.bp
new file mode 100644
index 000000000..db05067e4
--- /dev/null
+++ b/tests/cts/role/Android.bp
@@ -0,0 +1,49 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+ name: "CtsRoleTestCases",
+ defaults: ["mts-target-sdk-version-current"],
+ sdk_version: "test_current",
+ min_sdk_version: "30",
+
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "androidx.test.rules",
+ "compatibility-device-util-axt",
+ "ctstestrunner-axt",
+ "truth-prebuilt",
+ "platform-test-annotations",
+ ],
+
+ test_suites: [
+ "cts",
+ "general-tests",
+ "mts-permission",
+ ],
+
+ data: [
+ ":CtsRoleTestApp",
+ ":CtsRoleTestApp28",
+ ":CtsRoleTestApp33WithoutInCallService",
+ ],
+}
diff --git a/tests/cts/role/AndroidManifest.xml b/tests/cts/role/AndroidManifest.xml
new file mode 100644
index 000000000..a69caf18c
--- /dev/null
+++ b/tests/cts/role/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.role.cts">
+
+ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+
+ <application>
+
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name=".WaitForResultActivity" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.app.role.cts"
+ android:label="CTS tests of android.app.role">
+ </instrumentation>
+</manifest>
diff --git a/tests/cts/role/AndroidTest.xml b/tests/cts/role/AndroidTest.xml
new file mode 100644
index 000000000..74b6c1afc
--- /dev/null
+++ b/tests/cts/role/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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.
+ -->
+
+<configuration description="Config for CTS role test cases">
+
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="permissions" />
+ <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="run_on_sdk_sandbox" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.permission.apex" />
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsRoleTestCases.apk" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="mkdir -p /data/local/tmp/cts/role" />
+ <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="CtsRoleTestApp.apk->/data/local/tmp/cts/role/CtsRoleTestApp.apk" />
+ <option name="push" value="CtsRoleTestApp28.apk->/data/local/tmp/cts/role/CtsRoleTestApp28.apk" />
+ <option name="push" value="CtsRoleTestApp33WithoutInCallService.apk->/data/local/tmp/cts/role/CtsRoleTestApp33WithoutInCallService.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.app.role.cts" />
+ <option name="runtime-hint" value="5m" />
+ </test>
+</configuration>
diff --git a/tests/cts/role/CtsRoleTestApp/Android.bp b/tests/cts/role/CtsRoleTestApp/Android.bp
new file mode 100644
index 000000000..1270e490b
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/Android.bp
@@ -0,0 +1,26 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsRoleTestApp",
+ defaults: ["mts-target-sdk-version-current"],
+ min_sdk_version: "30",
+ srcs: [
+ "src/**/*.java"
+ ],
+}
diff --git a/tests/cts/role/CtsRoleTestApp/AndroidManifest.xml b/tests/cts/role/CtsRoleTestApp/AndroidManifest.xml
new file mode 100644
index 000000000..b2dfca961
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/AndroidManifest.xml
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.role.cts.app">
+
+ <uses-permission android:name="android.permission.SEND_SMS" />
+
+ <application android:label="CtsRoleTestApp">
+
+ <activity
+ android:name=".RequestRoleActivity"
+ android:exported="true" />
+
+ <activity
+ android:name=".IsRoleHeldActivity"
+ android:exported="true" />
+
+ <activity
+ android:name=".ChangeDefaultDialerActivity"
+ android:exported="true" />
+
+ <activity
+ android:name=".ChangeDefaultSmsActivity"
+ android:exported="true" />
+
+ <!-- Dialer -->
+ <activity
+ android:name=".DialerDialActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="tel" />
+ </intent-filter>
+ </activity>
+ <service
+ android:name=".DialerInCallService"
+ android:permission="android.permission.BIND_INCALL_SERVICE"
+ android:exported="true">
+ <meta-data
+ android:name="android.telecom.IN_CALL_SERVICE_UI"
+ android:value="true"/>
+ <meta-data
+ android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
+ android:value="false"/>
+ <intent-filter>
+ <action android:name="android.telecom.InCallService" />
+ </intent-filter>
+ </service>
+ <!-- Sms -->
+ <activity
+ android:name=".SmsSendToActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.SENDTO" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="smsto" />
+ </intent-filter>
+ </activity>
+ <service
+ android:name=".SmsRespondViaMessageService"
+ android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="smsto" />
+ </intent-filter>
+ </service>
+ <receiver
+ android:name=".SmsDelieverReceiver"
+ android:permission="android.permission.BROADCAST_SMS"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_DELIVER" />
+ </intent-filter>
+ </receiver>
+ <receiver
+ android:name=".SmsWapPushDelieverReceiver"
+ android:permission="android.permission.BROADCAST_WAP_PUSH"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+ <data android:mimeType="application/vnd.wap.mms-message" />
+ </intent-filter>
+ </receiver>
+
+ <!-- Browser -->
+ <activity
+ android:name=".BrowserActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.VIEW" />
+ <category android:name="android.intent.category.BROWSABLE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="http" />
+ </intent-filter>
+ </activity>
+
+ <!-- Assistant -->
+ <activity
+ android:name=".AssistantActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.ASSIST" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultDialerActivity.java b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultDialerActivity.java
new file mode 100644
index 000000000..89cafa001
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultDialerActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role.cts.app;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.TelecomManager;
+
+/**
+ * An activity that tries to change the default dialer app.
+ */
+public class ChangeDefaultDialerActivity extends Activity {
+
+ private static final int REQUEST_CODE_CHANGE_DEFAULT_DIALER = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
+ .putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName);
+ startActivityForResult(intent, REQUEST_CODE_CHANGE_DEFAULT_DIALER);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_CHANGE_DEFAULT_DIALER) {
+ setResult(resultCode, data);
+ finish();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultSmsActivity.java b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultSmsActivity.java
new file mode 100644
index 000000000..00559bf44
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/ChangeDefaultSmsActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role.cts.app;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Telephony;
+
+/**
+ * An activity that tries to change the default SMS app.
+ */
+public class ChangeDefaultSmsActivity extends Activity {
+
+ private static final int REQUEST_CODE_CHANGE_DEFAULT_SMS = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
+ .putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName);
+ startActivityForResult(intent, REQUEST_CODE_CHANGE_DEFAULT_SMS);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_CHANGE_DEFAULT_SMS) {
+ setResult(resultCode, data);
+ finish();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java
new file mode 100644
index 000000000..8e97f9f24
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role.cts.app;
+
+import android.app.Activity;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+/**
+ * An activity that checks whether a role is held.
+ */
+public class IsRoleHeldActivity extends Activity {
+
+ private static final String EXTRA_IS_ROLE_HELD = "android.app.role.cts.app.extra.IS_ROLE_HELD";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String roleName = getIntent().getStringExtra(Intent.EXTRA_ROLE_NAME);
+ if (TextUtils.isEmpty(roleName)) {
+ throw new IllegalArgumentException("Role name in extras cannot be null or empty");
+ }
+
+ RoleManager roleManager = getSystemService(RoleManager.class);
+ setResult(RESULT_OK, new Intent()
+ .putExtra(EXTRA_IS_ROLE_HELD, roleManager.isRoleHeld(roleName)));
+ finish();
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java
new file mode 100644
index 000000000..b2d69e044
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java
@@ -0,0 +1,53 @@
+/*
+ * 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 android.app.role.cts.app;
+
+import android.app.Activity;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+/**
+ * An activity that requests a role.
+ */
+public class RequestRoleActivity extends Activity {
+
+ private static final int REQUEST_CODE_REQUEST_ROLE = 1;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ String roleName = getIntent().getStringExtra(Intent.EXTRA_ROLE_NAME);
+ RoleManager roleManager = getSystemService(RoleManager.class);
+ Intent intent = roleManager.createRequestRoleIntent(roleName);
+ startActivityForResult(intent, REQUEST_CODE_REQUEST_ROLE);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_REQUEST_ROLE) {
+ setResult(resultCode, data);
+ finish();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp28/Android.bp b/tests/cts/role/CtsRoleTestApp28/Android.bp
new file mode 100644
index 000000000..dc8239edb
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp28/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsRoleTestApp28",
+ min_sdk_version: "28",
+ target_sdk_version: "28",
+
+ srcs: [
+ "src/**/*.java"
+ ],
+}
diff --git a/tests/cts/role/CtsRoleTestApp28/AndroidManifest.xml b/tests/cts/role/CtsRoleTestApp28/AndroidManifest.xml
new file mode 100644
index 000000000..8113b2676
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp28/AndroidManifest.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.role.cts.app28">
+
+ <uses-permission android:name="android.permission.SEND_SMS"/>
+
+ <application android:label="CtsRoleTestApp28">
+
+ <activity android:name=".ChangeDefaultDialerActivity"
+ android:exported="true"/>
+
+ <activity android:name=".ChangeDefaultSmsActivity"
+ android:exported="true"/>
+
+ <!-- Dialer -->
+ <activity android:name=".DialerDialActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="tel"/>
+ </intent-filter>
+ </activity>
+
+ <!-- Sms -->
+ <activity android:name=".SmsSendToActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.SENDTO"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="smsto"/>
+ </intent-filter>
+ </activity>
+ <service android:name=".SmsRespondViaMessageService"
+ android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <data android:scheme="smsto"/>
+ </intent-filter>
+ </service>
+ <receiver android:name=".SmsDelieverReceiver"
+ android:permission="android.permission.BROADCAST_SMS"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_DELIVER"/>
+ </intent-filter>
+ </receiver>
+ <receiver android:name=".SmsWapPushDelieverReceiver"
+ android:permission="android.permission.BROADCAST_WAP_PUSH"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
+ <data android:mimeType="application/vnd.wap.mms-message"/>
+ </intent-filter>
+ </receiver>
+ </application>
+</manifest>
diff --git a/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultDialerActivity.java b/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultDialerActivity.java
new file mode 100644
index 000000000..5d1c47cfc
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultDialerActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role.cts.app28;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.telecom.TelecomManager;
+
+/**
+ * An activity that tries to change the default dialer app.
+ */
+public class ChangeDefaultDialerActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ Intent intent = new Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
+ .putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName);
+ startActivity(intent);
+ }
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultSmsActivity.java b/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultSmsActivity.java
new file mode 100644
index 000000000..37819bbec
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp28/src/android/app/role/cts/app28/ChangeDefaultSmsActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role.cts.app28;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Telephony;
+import android.telecom.TelecomManager;
+
+/**
+ * An activity that tries to change the default SMS app.
+ */
+public class ChangeDefaultSmsActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+ Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT)
+ .putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, packageName);
+ startActivity(intent);
+ }
+ }
+}
diff --git a/tests/cts/role/CtsRoleTestApp33WithoutInCallService/Android.bp b/tests/cts/role/CtsRoleTestApp33WithoutInCallService/Android.bp
new file mode 100644
index 000000000..7cce565af
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp33WithoutInCallService/Android.bp
@@ -0,0 +1,23 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+ name: "CtsRoleTestApp33WithoutInCallService",
+ min_sdk_version: "30",
+ target_sdk_version: "33",
+}
diff --git a/tests/cts/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml b/tests/cts/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml
new file mode 100644
index 000000000..a6504adae
--- /dev/null
+++ b/tests/cts/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.app.role.cts.app33WithoutInCallService">
+ <application android:label="CtsRoleTestApp33WithoutInCallService">
+ <!-- Dialer -->
+ <activity
+ android:name=".DialerDialActivity"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <intent-filter>
+ <action android:name="android.intent.action.DIAL" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <data android:scheme="tel" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/cts/role/OWNERS b/tests/cts/role/OWNERS
new file mode 100644
index 000000000..fb6099cf7
--- /dev/null
+++ b/tests/cts/role/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 137825
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/tests/cts/role/TEST_MAPPING b/tests/cts/role/TEST_MAPPING
new file mode 100644
index 000000000..a95df2fd5
--- /dev/null
+++ b/tests/cts/role/TEST_MAPPING
@@ -0,0 +1,48 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsRoleTestCases",
+ "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": "android.platform.test.annotations.FlakyTest"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsRoleTestCases"
+ }
+ ],
+ "mainline-postsubmit": [
+ {
+ "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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/cts/role/src/android/app/role/cts/RoleControllerManagerTest.kt b/tests/cts/role/src/android/app/role/cts/RoleControllerManagerTest.kt
new file mode 100644
index 000000000..cf7da81ec
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/RoleControllerManagerTest.kt
@@ -0,0 +1,154 @@
+/*
+ * 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 android.app.role.cts
+
+import android.app.Instrumentation
+
+import android.app.role.RoleManager
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Process
+import android.provider.Settings
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SdkSuppress
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.compatibility.common.util.ThrowingSupplier
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+
+/**
+ * Tests RoleControllerManager APIs exposed on [RoleManager].
+ */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+class RoleControllerManagerTest {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context: Context = instrumentation.context
+ private val packageManager: PackageManager = context.packageManager
+ private val roleManager: RoleManager = context.getSystemService(RoleManager::class.java)!!
+
+ @Before
+ fun installApp() {
+ installPackage(APP_APK_PATH)
+ }
+
+ @After
+ fun uninstallApp() {
+ uninstallPackage(APP_PACKAGE_NAME)
+ }
+
+ @Test
+ fun appIsVisibleForRole() {
+ assumeRoleIsVisible()
+ assertAppIsVisibleForRole(APP_PACKAGE_NAME, ROLE_NAME, true)
+ }
+
+ @Test
+ fun settingsIsNotVisibleForHomeRole() {
+ // Settings should never show as a possible home app even if qualified.
+ val settingsPackageName = packageManager.resolveActivity(
+ Intent(Settings.ACTION_SETTINGS), PackageManager.MATCH_DEFAULT_ONLY
+ or PackageManager.MATCH_DIRECT_BOOT_AWARE or PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ )!!.activityInfo.packageName
+ assertAppIsVisibleForRole(settingsPackageName, RoleManager.ROLE_HOME, false)
+ }
+
+ @Test
+ fun appIsNotVisibleForInvalidRole() {
+ assertAppIsVisibleForRole(APP_PACKAGE_NAME, "invalid", false)
+ }
+
+ @Test
+ fun invalidAppIsNotVisibleForRole() {
+ assertAppIsVisibleForRole("invalid", ROLE_NAME, false)
+ }
+
+ private fun assertAppIsVisibleForRole(
+ packageName: String,
+ roleName: String,
+ expectedIsVisible: Boolean
+ ) {
+ runWithShellPermissionIdentity {
+ val future = CompletableFuture<Boolean>()
+ roleManager.isApplicationVisibleForRole(
+ roleName, packageName, context.mainExecutor, Consumer { future.complete(it) }
+ )
+ val isVisible = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ assertThat(isVisible).isEqualTo(expectedIsVisible)
+ }
+ }
+
+ private fun assumeRoleIsVisible() {
+ assumeTrue(isRoleVisible(ROLE_NAME))
+ }
+
+ @Test
+ fun systemGalleryRoleIsNotVisible() {
+ // The system gallery role should always be hidden.
+ assertThat(isRoleVisible(SYSTEM_GALLERY_ROLE_NAME)).isEqualTo(false)
+ }
+
+ @Test
+ fun invalidRoleIsNotVisible() {
+ assertThat(isRoleVisible("invalid")).isEqualTo(false)
+ }
+
+ private fun isRoleVisible(roleName: String): Boolean =
+ runWithShellPermissionIdentity(ThrowingSupplier {
+ val future = CompletableFuture<Boolean>()
+ roleManager.isRoleVisible(
+ roleName, context.mainExecutor, Consumer { future.complete(it) }
+ )
+ future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+ })
+
+ private fun installPackage(apkPath: String) {
+ assertEquals(
+ "Success",
+ runShellCommand("pm install -r --user ${Process.myUserHandle().identifier} $apkPath")
+ .trim()
+ )
+ }
+
+ private fun uninstallPackage(packageName: String) {
+ assertEquals(
+ "Success",
+ runShellCommand("pm uninstall --user ${Process.myUserHandle().identifier} $packageName")
+ .trim()
+ )
+ }
+
+ companion object {
+ private const val ROLE_NAME = RoleManager.ROLE_BROWSER
+ private const val APP_APK_PATH = "/data/local/tmp/cts/role/CtsRoleTestApp.apk"
+ private const val APP_PACKAGE_NAME = "android.app.role.cts.app"
+ private const val SYSTEM_GALLERY_ROLE_NAME = "android.app.role.SYSTEM_GALLERY"
+ private const val TIMEOUT_MILLIS = 15 * 1000L
+ }
+}
diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
new file mode 100644
index 000000000..0b563ec0d
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java
@@ -0,0 +1,1181 @@
+/*
+ * 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 android.app.role.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObjectOrNull;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.role.OnRoleHoldersChangedListener;
+import android.app.role.RoleManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.platform.test.annotations.FlakyTest;
+import android.provider.Settings;
+import android.provider.Telephony;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.DisableAnimationRule;
+import com.android.compatibility.common.util.FreezeRotationRule;
+import com.android.compatibility.common.util.TestUtils;
+import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.compatibility.common.util.UiAutomatorUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+
+/**
+ * Tests {@link RoleManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RoleManagerTest {
+
+ private static final long TIMEOUT_MILLIS = 15 * 1000;
+
+ private static final long UNEXPECTED_TIMEOUT_MILLIS = 1000;
+
+ private static final String ROLE_NAME = RoleManager.ROLE_BROWSER;
+ private static final String ROLE_SHORT_LABEL = "Browser app";
+
+ private static final String APP_APK_PATH = "/data/local/tmp/cts/role/CtsRoleTestApp.apk";
+ private static final String APP_PACKAGE_NAME = "android.app.role.cts.app";
+ private static final String APP_LABEL = "CtsRoleTestApp";
+ private static final String APP_IS_ROLE_HELD_ACTIVITY_NAME = APP_PACKAGE_NAME
+ + ".IsRoleHeldActivity";
+ private static final String APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD = APP_PACKAGE_NAME
+ + ".extra.IS_ROLE_HELD";
+ private static final String APP_REQUEST_ROLE_ACTIVITY_NAME = APP_PACKAGE_NAME
+ + ".RequestRoleActivity";
+ private static final String APP_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME = APP_PACKAGE_NAME
+ + ".ChangeDefaultDialerActivity";
+ private static final String APP_CHANGE_DEFAULT_SMS_ACTIVITY_NAME = APP_PACKAGE_NAME
+ + ".ChangeDefaultSmsActivity";
+
+ private static final String APP_28_APK_PATH = "/data/local/tmp/cts/role/CtsRoleTestApp28.apk";
+ private static final String APP_28_PACKAGE_NAME = "android.app.role.cts.app28";
+ private static final String APP_28_LABEL = "CtsRoleTestApp28";
+ private static final String APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME = APP_28_PACKAGE_NAME
+ + ".ChangeDefaultDialerActivity";
+ private static final String APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME = APP_28_PACKAGE_NAME
+ + ".ChangeDefaultSmsActivity";
+
+ private static final String APP_33_WITHOUT_INCALLSERVICE_APK_PATH =
+ "/data/local/tmp/cts/role/CtsRoleTestApp33WithoutInCallService.apk";
+ private static final String APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME =
+ "android.app.role.cts.app33WithoutInCallService";
+
+ private static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER =
+ "com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER";
+
+ private static final Instrumentation sInstrumentation =
+ InstrumentationRegistry.getInstrumentation();
+ private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private static final PackageManager sPackageManager = sContext.getPackageManager();
+ private static final RoleManager sRoleManager = sContext.getSystemService(RoleManager.class);
+
+ @Rule
+ public DisableAnimationRule mDisableAnimationRule = new DisableAnimationRule();
+
+ @Rule
+ public FreezeRotationRule mFreezeRotationRule = new FreezeRotationRule();
+
+ @Rule
+ public ActivityTestRule<WaitForResultActivity> mActivityRule =
+ new ActivityTestRule<>(WaitForResultActivity.class);
+
+ private String mRoleHolder;
+
+ @Before
+ public void saveRoleHolder() throws Exception {
+ List<String> roleHolders = getRoleHolders(ROLE_NAME);
+ mRoleHolder = !roleHolders.isEmpty() ? roleHolders.get(0) : null;
+
+ if (Objects.equals(mRoleHolder, APP_PACKAGE_NAME)) {
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ mRoleHolder = null;
+ }
+ }
+
+ @After
+ public void restoreRoleHolder() throws Exception {
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ if (mRoleHolder != null) {
+ addRoleHolder(ROLE_NAME, mRoleHolder);
+ }
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Before
+ public void installApp() throws Exception {
+ installPackage(APP_APK_PATH);
+ installPackage(APP_28_APK_PATH);
+ installPackage(APP_33_WITHOUT_INCALLSERVICE_APK_PATH);
+ }
+
+ @After
+ public void uninstallApp() throws Exception {
+ uninstallPackage(APP_PACKAGE_NAME);
+ uninstallPackage(APP_28_PACKAGE_NAME);
+ uninstallPackage(APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME);
+ }
+
+ @Before
+ public void wakeUpScreen() throws IOException {
+ runShellCommand(sInstrumentation, "input keyevent KEYCODE_WAKEUP");
+ }
+
+ @Before
+ public void closeNotificationShade() {
+ sContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ }
+
+ @Test
+ public void requestRoleIntentHasPermissionControllerPackage() throws Exception {
+ Intent intent = sRoleManager.createRequestRoleIntent(ROLE_NAME);
+
+ assertThat(intent.getPackage()).isEqualTo(
+ sPackageManager.getPermissionControllerPackageName());
+ }
+
+ @Test
+ public void requestRoleIntentHasExtraRoleName() throws Exception {
+ Intent intent = sRoleManager.createRequestRoleIntent(ROLE_NAME);
+
+ assertThat(intent.getStringExtra(Intent.EXTRA_ROLE_NAME)).isEqualTo(ROLE_NAME);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestRoleAndDenyThenIsNotRoleHolder() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestRoleAndAllowThenIsRoleHolder() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(true);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestRoleFirstTimeNoDontAskAgain() throws Exception {
+ requestRole(ROLE_NAME);
+ UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
+
+ assertThat(dontAskAgainCheck).isNull();
+
+ respondToRoleRequest(false);
+ }
+
+ @Test
+ @FlakyTest
+ public void requestRoleAndDenyThenHasDontAskAgain() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ requestRole(ROLE_NAME);
+ UiObject2 dontAskAgainCheck = findDontAskAgainCheck();
+
+ assertThat(dontAskAgainCheck).isNotNull();
+
+ respondToRoleRequest(false);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestRoleAndDenyWithDontAskAgainReturnsCanceled() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ requestRole(ROLE_NAME);
+ findDontAskAgainCheck().click();
+ Pair<Integer, Intent> result = clickButtonAndWaitForResult(true);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest
+ public void requestRoleAndDenyWithDontAskAgainThenDeniedAutomatically() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ requestRole(ROLE_NAME);
+ findDontAskAgainCheck().click();
+ clickButtonAndWaitForResult(true);
+
+ requestRole(ROLE_NAME);
+ Pair<Integer, Intent> result = waitForResult();
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest
+ public void requestRoleAndDenyWithDontAskAgainAndClearDataThenShowsUiWithoutDontAskAgain()
+ throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ requestRole(ROLE_NAME);
+ findDontAskAgainCheck().click();
+ clickButtonAndWaitForResult(true);
+ // Wait for the RequestRoleActivity inside the test app to be removed from our task so that
+ // when the test app is force stopped, our task isn't force finished and our
+ // WaitForResultActivity can survive.
+ Thread.sleep(5000);
+
+ clearPackageData(APP_PACKAGE_NAME);
+ // Wait for the don't ask again to be forgotten.
+ Thread.sleep(10000);
+
+ TestUtils.waitUntil("Find and respond to request role UI", () -> {
+ requestRole(ROLE_NAME);
+ UiObject2 cancelButton = waitFindObjectOrNull(By.res("android:id/button2"));
+ if (cancelButton == null) {
+ // Dialog not found, try again later.
+ return false;
+ }
+ UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
+
+ assertThat(dontAskAgainCheck).isNull();
+
+ respondToRoleRequest(false);
+ return true;
+ });
+ }
+
+ @Test
+ @FlakyTest
+ public void requestRoleAndDenyWithDontAskAgainAndReinstallThenShowsUiWithoutDontAskAgain()
+ throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(false);
+
+ requestRole(ROLE_NAME);
+ findDontAskAgainCheck().click();
+ clickButtonAndWaitForResult(true);
+ // Wait for the RequestRoleActivity inside the test app to be removed from our task so that
+ // when the test app is uninstalled, our task isn't force finished and our
+ // WaitForResultActivity can survive.
+ Thread.sleep(5000);
+
+ uninstallPackage(APP_PACKAGE_NAME);
+ // Wait for the don't ask again to be forgotten.
+ Thread.sleep(10000);
+ installPackage(APP_APK_PATH);
+
+ TestUtils.waitUntil("Find and respond to request role UI", () -> {
+ requestRole(ROLE_NAME);
+ UiObject2 cancelButton = waitFindObjectOrNull(By.res("android:id/button2"));
+ if (cancelButton == null) {
+ // Dialog not found, try again later.
+ return false;
+ }
+ UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
+
+ assertThat(dontAskAgainCheck).isNull();
+
+ respondToRoleRequest(false);
+ return true;
+ });
+ }
+
+ @Test
+ public void requestInvalidRoleThenDeniedAutomatically() throws Exception {
+ requestRole("invalid");
+ Pair<Integer, Intent> result = waitForResult();
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void requestUnqualifiedRoleThenDeniedAutomatically() throws Exception {
+ requestRole(RoleManager.ROLE_HOME);
+ Pair<Integer, Intent> result = waitForResult();
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void requestAssistantRoleThenDeniedAutomatically() throws Exception {
+ requestRole(RoleManager.ROLE_ASSISTANT);
+ Pair<Integer, Intent> result = waitForResult();
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void requestHoldingRoleThenAllowedAutomatically() throws Exception {
+ requestRole(ROLE_NAME);
+ respondToRoleRequest(true);
+
+ requestRole(ROLE_NAME);
+ Pair<Integer, Intent> result = waitForResult();
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_OK);
+ }
+
+ private void requestRole(@NonNull String roleName) {
+ Intent intent = new Intent()
+ .setComponent(new ComponentName(APP_PACKAGE_NAME, APP_REQUEST_ROLE_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_ROLE_NAME, roleName);
+ mActivityRule.getActivity().startActivityToWaitForResult(intent);
+ }
+
+ private void respondToRoleRequest(boolean allow)
+ throws InterruptedException, UiObjectNotFoundException {
+ if (allow) {
+ waitFindObject(By.text(APP_LABEL)).click();
+ }
+ Pair<Integer, Intent> result = clickButtonAndWaitForResult(allow);
+ int expectedResult = allow ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
+
+ assertThat(result.first).isEqualTo(expectedResult);
+ }
+
+ @Nullable
+ private UiObject2 findDontAskAgainCheck(boolean expected) throws UiObjectNotFoundException {
+ BySelector selector = By.res("com.android.permissioncontroller:id/dont_ask_again");
+ return expected
+ ? waitFindObject(selector)
+ : waitFindObjectOrNull(selector, UNEXPECTED_TIMEOUT_MILLIS);
+ }
+
+ @Nullable
+ private UiObject2 findDontAskAgainCheck() throws UiObjectNotFoundException {
+ return findDontAskAgainCheck(true);
+ }
+
+ @NonNull
+ private Pair<Integer, Intent> clickButtonAndWaitForResult(boolean positive)
+ throws InterruptedException, UiObjectNotFoundException {
+ waitFindObject(By.res(positive ? "android:id/button1" : "android:id/button2")).click();
+ return waitForResult();
+ }
+
+ @NonNull
+ private Pair<Integer, Intent> waitForResult() throws InterruptedException {
+ return mActivityRule.getActivity().waitForActivityResult(TIMEOUT_MILLIS);
+ }
+
+ private void clearPackageData(@NonNull String packageName) {
+ runShellCommand("pm clear --user " + Process.myUserHandle().getIdentifier() + " "
+ + packageName);
+ }
+
+ private void installPackage(@NonNull String apkPath) {
+ runShellCommand("pm install -r --user " + Process.myUserHandle().getIdentifier() + " "
+ + apkPath);
+ }
+
+ private void uninstallPackage(@NonNull String packageName) {
+ runShellCommand("pm uninstall --user " + Process.myUserHandle().getIdentifier() + " "
+ + packageName);
+ }
+
+ @Test
+ public void targetCurrentSdkAndChangeDefaultDialerThenDeniedAutomatically() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(new Intent()
+ .setComponent(new ComponentName(APP_PACKAGE_NAME,
+ APP_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ public void targetCurrentSdkAndChangeDefaultSmsThenDeniedAutomatically() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(new Intent()
+ .setComponent(new ComponentName(APP_PACKAGE_NAME,
+ APP_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void targetSdk28AndChangeDefaultDialerAndAllowThenIsDefaultDialer() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+
+ sContext.startActivity(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_28_PACKAGE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ waitFindObject(By.text(APP_28_LABEL)).click();
+ waitFindObject(By.res("android:id/button1")).click();
+
+ // TODO(b/149037075): Use TelecomManager.getDefaultDialerPackage() once the bug is fixed.
+ //TelecomManager telecomManager = sContext.getSystemService(TelecomManager.class);
+ //TestUtils.waitUntil("App is not set as default dialer app", () -> Objects.equals(
+ // telecomManager.getDefaultDialerPackage(), APP_28_PACKAGE_NAME));
+ TestUtils.waitUntil("App is not set as default dialer app", () ->
+ getRoleHolders(RoleManager.ROLE_DIALER).contains(APP_28_PACKAGE_NAME));
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void targetSdk28AndChangeDefaultSmsAndAllowThenIsDefaultSms() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ sContext.startActivity(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_28_PACKAGE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ waitFindObject(By.text(APP_28_LABEL)).click();
+ waitFindObject(By.res("android:id/button1")).click();
+
+ TestUtils.waitUntil("App is not set as default sms app", () -> Objects.equals(
+ Telephony.Sms.getDefaultSmsPackage(sContext), APP_28_PACKAGE_NAME));
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void targetSdk28AndChangeDefaultDialerForAnotherAppThenDeniedAutomatically()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void targetSdk28AndChangeDefaultSmsForAnotherAppThenDeniedAutomatically()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void
+ targetSdk28AndChangeDefaultDialerForAnotherAppAsHolderAndAllowThenTheOtherAppIsDefaultDialer()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+
+ addRoleHolder(RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME);
+ sContext.startActivity(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ waitFindObject(By.text(APP_LABEL)).click();
+ waitFindObject(By.res("android:id/button1")).click();
+
+ // TODO(b/149037075): Use TelecomManager.getDefaultDialerPackage() once the bug is fixed.
+ //TelecomManager telecomManager = sContext.getSystemService(TelecomManager.class);
+ //TestUtils.waitUntil("App is not set as default dialer app", () -> Objects.equals(
+ // telecomManager.getDefaultDialerPackage(), APP_PACKAGE_NAME));
+ TestUtils.waitUntil("App is not set as default dialer app", () ->
+ getRoleHolders(RoleManager.ROLE_DIALER).contains(APP_PACKAGE_NAME));
+ }
+
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+ public void testHoldDialerRoleRequirementWithInCallServiceAndSdk()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+ // target below sdk 33 without InCallService component can hold dialer role
+ addRoleHolder(
+ RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME, true);
+ assertIsRoleHolder(
+ RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME, true);
+ // target sdk 33 without InCallService component cannot hold dialer role
+ addRoleHolder(
+ RoleManager.ROLE_DIALER, APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME, false);
+ assertIsRoleHolder(
+ RoleManager.ROLE_DIALER, APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME, false);
+ // target sdk 33 with InCallService component can hold dialer role
+ addRoleHolder(
+ RoleManager.ROLE_DIALER, APP_PACKAGE_NAME, true);
+ assertIsRoleHolder(
+ RoleManager.ROLE_DIALER, APP_PACKAGE_NAME, true);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void
+ targetSdk28AndChangeDefaultSmsForAnotherAppAsHolderAndAllowThenTheOtherAppIsDefaultSms()
+ throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ addRoleHolder(RoleManager.ROLE_SMS, APP_28_PACKAGE_NAME);
+ sContext.startActivity(new Intent()
+ .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
+ APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ waitFindObject(By.text(APP_LABEL)).click();
+ waitFindObject(By.res("android:id/button1")).click();
+
+ TestUtils.waitUntil("App is not set as default sms app", () -> Objects.equals(
+ Telephony.Sms.getDefaultSmsPackage(sContext), APP_PACKAGE_NAME));
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppDetailsThenIsNotDefaultApp() throws Exception {
+ runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+ Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL)));
+
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppDetailsAndSetDefaultAppThenIsDefaultApp() throws Exception {
+ runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+ Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+ waitForIdle();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL))).click();
+
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_LABEL)));
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppDetailsAndSetDefaultAppAndSetAnotherThenIsNotDefaultApp()
+ throws Exception {
+ runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+ Intent.ACTION_MANAGE_DEFAULT_APP)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+ waitForIdle();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL))).click();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_LABEL)));
+ waitForIdle();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))).click();
+
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL)));
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppListThenHasDefaultApp() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+
+ waitFindObject(By.text(ROLE_SHORT_LABEL));
+
+ pressBack();
+ }
+
+ @Test
+ public void openDefaultAppListThenIsNotDefaultAppInList() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+
+ assertThat(waitFindObjectOrNull(By.text(APP_LABEL), UNEXPECTED_TIMEOUT_MILLIS))
+ .isNull();
+
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppListAndSetDefaultAppThenIsDefaultApp() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+ waitForIdle();
+ waitFindObject(By.text(ROLE_SHORT_LABEL)).click();
+ waitForIdle();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL))).click();
+
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_LABEL)));
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+
+ pressBack();
+ pressBack();
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void openDefaultAppListAndSetDefaultAppThenIsDefaultAppInList() throws Exception {
+ sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+ .addCategory(Intent.CATEGORY_DEFAULT)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+ waitForIdle();
+ waitFindObject(By.text(ROLE_SHORT_LABEL)).click();
+ waitForIdle();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+ .hasDescendant(By.text(APP_LABEL))).click();
+ waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+ .hasDescendant(By.text(APP_LABEL)));
+ pressBack();
+
+ waitFindObject(By.text(APP_LABEL));
+
+ pressBack();
+ }
+
+ private static void waitForIdle() {
+ UiAutomatorUtils.getUiDevice().waitForIdle();
+ }
+
+ private static void pressBack() {
+ UiAutomatorUtils.getUiDevice().pressBack();
+ waitForIdle();
+ }
+
+ @Test
+ public void roleIsAvailable() {
+ assertThat(sRoleManager.isRoleAvailable(ROLE_NAME)).isTrue();
+ }
+
+ @Test
+ public void dontAddRoleHolderThenRoleIsNotHeld() throws Exception {
+ assertRoleIsHeld(ROLE_NAME, false);
+ }
+
+ @Test
+ public void addRoleHolderThenRoleIsHeld() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertRoleIsHeld(ROLE_NAME, true);
+ }
+
+ @Test
+ public void addAndRemoveRoleHolderThenRoleIsNotHeld() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertRoleIsHeld(ROLE_NAME, false);
+ }
+
+ private void assertRoleIsHeld(@NonNull String roleName, boolean isHeld)
+ throws InterruptedException {
+ Intent intent = new Intent()
+ .setComponent(new ComponentName(APP_PACKAGE_NAME, APP_IS_ROLE_HELD_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_ROLE_NAME, roleName);
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(intent);
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_OK);
+ assertThat(result.second).isNotNull();
+ assertThat(result.second.hasExtra(APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD)).isTrue();
+ assertThat(result.second.getBooleanExtra(APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD, false))
+ .isEqualTo(isHeld);
+ }
+
+ @Test
+ public void dontAddRoleHolderThenIsNotRoleHolder() throws Exception {
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addRoleHolderThenIsRoleHolder() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+ }
+
+ @Test
+ public void addAndRemoveRoleHolderThenIsNotRoleHolder() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addAndClearRoleHoldersThenIsNotRoleHolder() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ clearRoleHolders(ROLE_NAME);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addInvalidRoleHolderThenFails() throws Exception {
+ addRoleHolder("invalid", APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addUnqualifiedRoleHolderThenFails() throws Exception {
+ addRoleHolder(RoleManager.ROLE_HOME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void removeInvalidRoleHolderThenFails() throws Exception {
+ removeRoleHolder("invalid", APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void clearInvalidRoleHoldersThenFails() throws Exception {
+ clearRoleHolders("invalid", false);
+ }
+
+ @Test
+ public void addOnRoleHoldersChangedListenerAndAddRoleHolderThenIsNotified() throws Exception {
+ assertOnRoleHoldersChangedListenerIsNotified(() -> addRoleHolder(ROLE_NAME,
+ APP_PACKAGE_NAME));
+ }
+
+ @Test
+ public void addOnRoleHoldersChangedListenerAndRemoveRoleHolderThenIsNotified()
+ throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertOnRoleHoldersChangedListenerIsNotified(() -> removeRoleHolder(ROLE_NAME,
+ APP_PACKAGE_NAME));
+ }
+
+ @Test
+ public void addOnRoleHoldersChangedListenerAndClearRoleHoldersThenIsNotified()
+ throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertOnRoleHoldersChangedListenerIsNotified(() -> clearRoleHolders(ROLE_NAME));
+ }
+
+ private void assertOnRoleHoldersChangedListenerIsNotified(@NonNull ThrowingRunnable runnable)
+ throws Exception {
+ ListenerFuture future = new ListenerFuture();
+ UserHandle user = Process.myUserHandle();
+ runWithShellPermissionIdentity(() -> sRoleManager.addOnRoleHoldersChangedListenerAsUser(
+ sContext.getMainExecutor(), future, user));
+ Pair<String, UserHandle> result;
+ try {
+ runnable.run();
+ result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } finally {
+ runWithShellPermissionIdentity(() ->
+ sRoleManager.removeOnRoleHoldersChangedListenerAsUser(future, user));
+ }
+
+ assertThat(result.first).isEqualTo(ROLE_NAME);
+ assertThat(result.second).isEqualTo(user);
+ }
+
+ @Test
+ public void addAndRemoveOnRoleHoldersChangedListenerAndAddRoleHolderThenIsNotNotified()
+ throws Exception {
+ ListenerFuture future = new ListenerFuture();
+ UserHandle user = Process.myUserHandle();
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.addOnRoleHoldersChangedListenerAsUser(sContext.getMainExecutor(), future,
+ user);
+ sRoleManager.removeOnRoleHoldersChangedListenerAsUser(future, user);
+ });
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ try {
+ future.get(UNEXPECTED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ // Expected
+ return;
+ }
+ throw new AssertionError("OnRoleHoldersChangedListener was notified after removal");
+ }
+
+ @Test
+ public void setRoleNamesFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.setRoleNamesFromController(Collections.emptyList()),
+ "setRoleNamesFromController");
+ }
+
+ @Test
+ public void addRoleHolderFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.addRoleHolderFromController(ROLE_NAME, APP_PACKAGE_NAME),
+ "addRoleHolderFromController");
+ }
+
+ @Test
+ public void removeRoleHolderFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.removeRoleHolderFromController(ROLE_NAME, APP_PACKAGE_NAME),
+ "removeRoleHolderFromController");
+ }
+
+ @Test
+ public void getHeldRolesFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.getHeldRolesFromController(APP_PACKAGE_NAME),
+ "getHeldRolesFromController");
+ }
+
+ private void assertRequiresManageRolesFromControllerPermission(@NonNull Runnable runnable,
+ @NonNull String methodName) {
+ try {
+ runnable.run();
+ } catch (SecurityException e) {
+ if (e.getMessage().contains(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)) {
+ // Expected
+ return;
+ }
+ throw e;
+ }
+ fail("RoleManager." + methodName + "() should require "
+ + PERMISSION_MANAGE_ROLES_FROM_CONTROLLER);
+ }
+
+ @Test
+ public void manageRolesFromControllerPermissionShouldBeDeclaredByPermissionController()
+ throws PackageManager.NameNotFoundException {
+ PermissionInfo permissionInfo = sPackageManager.getPermissionInfo(
+ PERMISSION_MANAGE_ROLES_FROM_CONTROLLER, 0);
+
+ assertThat(permissionInfo.packageName).isEqualTo(
+ sPackageManager.getPermissionControllerPackageName());
+ assertThat(permissionInfo.getProtection()).isEqualTo(PermissionInfo.PROTECTION_SIGNATURE);
+ assertThat(permissionInfo.getProtectionFlags()).isEqualTo(0);
+ }
+
+ @Test
+ public void smsRoleHasHolder() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ assertThat(getRoleHolders(RoleManager.ROLE_SMS)).isNotEmpty();
+ }
+
+ @Test
+ public void addSmsRoleHolderThenPermissionIsGranted() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+
+ assertThat(sPackageManager.checkPermission(android.Manifest.permission.SEND_SMS,
+ APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_GRANTED);
+ }
+
+ @Test
+ public void removeSmsRoleHolderThenPermissionIsRevoked() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0);
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+ addRoleHolder(RoleManager.ROLE_SMS, smsRoleHolder);
+
+ assertThat(sPackageManager.checkPermission(android.Manifest.permission.SEND_SMS,
+ APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_DENIED);
+ }
+
+ @Test
+ public void removeSmsRoleHolderThenDialerRolePermissionIsRetained() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER)
+ && sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ addRoleHolder(RoleManager.ROLE_DIALER, APP_PACKAGE_NAME);
+ String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0);
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+ addRoleHolder(RoleManager.ROLE_SMS, smsRoleHolder);
+
+ assertThat(sPackageManager.checkPermission(android.Manifest.permission.SEND_SMS,
+ APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_GRANTED);
+ }
+
+ @Test
+ public void packageManagerGetDefaultBrowserBackedByRole() throws Exception {
+ addRoleHolder(RoleManager.ROLE_BROWSER, APP_PACKAGE_NAME);
+
+ assertThat(sPackageManager.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId()))
+ .isEqualTo(APP_PACKAGE_NAME);
+ }
+
+ @Test
+ @FlakyTest(bugId = 288468003, detail = "CtsRoleTestCases is breaching 20min SLO")
+ public void packageManagerSetDefaultBrowserBackedByRole() throws Exception {
+ callWithShellPermissionIdentity(() -> sPackageManager.setDefaultBrowserPackageNameAsUser(
+ APP_PACKAGE_NAME, UserHandle.myUserId()));
+
+ assertIsRoleHolder(RoleManager.ROLE_BROWSER, APP_PACKAGE_NAME, true);
+ }
+
+ @Test
+ public void telephonySmsGetDefaultSmsPackageBackedByRole() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+
+ assertThat(Telephony.Sms.getDefaultSmsPackage(sContext)).isEqualTo(APP_PACKAGE_NAME);
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+ @Test
+ public void cannotBypassRoleQualificationWithoutPermission() throws Exception {
+ assertThrows(SecurityException.class, () ->
+ sRoleManager.setBypassingRoleQualification(true));
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+ @Test
+ public void bypassRoleQualificationThenCanAddUnqualifiedRoleHolder() throws Exception {
+ assertThat(sRoleManager.isRoleAvailable(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER))
+ .isTrue();
+
+ runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(true));
+ try {
+ assertThat(callWithShellPermissionIdentity(() ->
+ sRoleManager.isBypassingRoleQualification())).isTrue();
+
+ // The System Activity Recognizer role requires a system app, so this won't succeed
+ // without bypassing role qualification.
+ addRoleHolder(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER, APP_PACKAGE_NAME);
+
+ assertThat(getRoleHolders(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER))
+ .contains(APP_PACKAGE_NAME);
+ } finally {
+ runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(false));
+ }
+ assertThat(callWithShellPermissionIdentity(() ->
+ sRoleManager.isBypassingRoleQualification())).isFalse();
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void cannotGetDefaultApplicationWithoutPermission() throws Exception {
+ assertThrows(SecurityException.class, ()->
+ sRoleManager.getDefaultApplication(
+ RoleManager.ROLE_SMS));
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void getDefaultApplicationChecksRoles() throws Exception {
+ runWithShellPermissionIdentity(() ->
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.getDefaultApplication(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER)));
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void getDefaultApplicationReadsRole() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+ runWithShellPermissionIdentity(() -> {
+ assertThat(sRoleManager.getDefaultApplication(RoleManager.ROLE_SMS))
+ .isEqualTo(APP_PACKAGE_NAME);
+ });
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void cannotSetDefaultApplicationWithoutPermission() throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ assertThrows(SecurityException.class, ()->
+ sRoleManager.setDefaultApplication(
+ RoleManager.ROLE_SMS, APP_PACKAGE_NAME, 0,
+ sContext.getMainExecutor(), future));
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void setDefaultApplicationChecksRoles() throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() ->
+ assertThrows(IllegalArgumentException.class, () ->
+ sRoleManager.setDefaultApplication(
+ RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER, APP_PACKAGE_NAME, 0,
+ sContext.getMainExecutor(), future)));
+ }
+
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ public void setDefaultApplicationSetsRole() throws Exception {
+ assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
+
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.setDefaultApplication(
+ RoleManager.ROLE_SMS, APP_PACKAGE_NAME, 0,
+ sContext.getMainExecutor(), future);
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ assertThat(sRoleManager.getRoleHolders(RoleManager.ROLE_SMS))
+ .containsExactly(APP_PACKAGE_NAME);
+ });
+ }
+
+ @NonNull
+ private List<String> getRoleHolders(@NonNull String roleName) throws Exception {
+ return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName));
+ }
+
+ private void assertIsRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ boolean shouldBeRoleHolder) throws Exception {
+ List<String> packageNames = getRoleHolders(roleName);
+
+ if (shouldBeRoleHolder) {
+ assertThat(packageNames).contains(packageName);
+ } else {
+ assertThat(packageNames).doesNotContain(packageName);
+ }
+ }
+
+ private void addRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ boolean expectSuccess) throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() -> sRoleManager.addRoleHolderAsUser(roleName,
+ packageName, 0, Process.myUserHandle(), sContext.getMainExecutor(), future));
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess);
+ }
+
+ private void addRoleHolder(@NonNull String roleName, @NonNull String packageName)
+ throws Exception {
+ addRoleHolder(roleName, packageName, true);
+ }
+
+ private void removeRoleHolder(@NonNull String roleName, @NonNull String packageName,
+ boolean expectSuccess) throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() -> sRoleManager.removeRoleHolderAsUser(roleName,
+ packageName, 0, Process.myUserHandle(), sContext.getMainExecutor(), future));
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess);
+ }
+
+ private void removeRoleHolder(@NonNull String roleName, @NonNull String packageName)
+ throws Exception {
+ removeRoleHolder(roleName, packageName, true);
+ }
+
+ private void clearRoleHolders(@NonNull String roleName, boolean expectSuccess)
+ throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() -> sRoleManager.clearRoleHoldersAsUser(roleName, 0,
+ Process.myUserHandle(), sContext.getMainExecutor(), future));
+ assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess);
+ }
+
+ private void clearRoleHolders(@NonNull String roleName) throws Exception {
+ clearRoleHolders(roleName, true);
+ }
+
+ private static class ListenerFuture extends CompletableFuture<Pair<String, UserHandle>>
+ implements OnRoleHoldersChangedListener {
+
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ complete(new Pair<>(roleName, user));
+ }
+ }
+
+ private static class CallbackFuture extends CompletableFuture<Boolean>
+ implements Consumer<Boolean> {
+
+ @Override
+ public void accept(Boolean successful) {
+ complete(successful);
+ }
+ }
+}
diff --git a/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt b/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt
new file mode 100644
index 000000000..2393e51e9
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/RoleShellCommandTest.kt
@@ -0,0 +1,202 @@
+/*
+ * 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 android.app.role.cts
+
+import android.app.role.RoleManager
+import android.os.Build
+import android.os.UserHandle
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SdkSuppress
+import androidx.test.runner.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert.assertThrows
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests role shell commands.
+ */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+class RoleShellCommandTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context = instrumentation.context
+ private val roleManager = context.getSystemService(RoleManager::class.java)!!
+ private val userId = UserHandle.myUserId()
+
+ private var roleHolder: String? = null
+ private var wasBypassingRoleQualification: Boolean = false
+
+ @Before
+ fun saveRoleHolder() {
+ roleHolder = getRoleHolders().firstOrNull()
+ if (roleHolder == APP_PACKAGE_NAME) {
+ removeRoleHolder()
+ roleHolder = null
+ }
+ }
+
+ @Before
+ fun saveBypassingRoleQualification() {
+ wasBypassingRoleQualification = isBypassingRoleQualification()
+ }
+
+ @After
+ fun restoreRoleHolder() {
+ removeRoleHolder()
+ roleHolder?.let { addRoleHolder(it) }
+ assertIsRoleHolder(false)
+ }
+
+ @After
+ fun restoreBypassingRoleQualification() {
+ setBypassingRoleQualification(wasBypassingRoleQualification)
+ }
+
+ @Before
+ fun installApp() {
+ installPackage(APP_APK_PATH)
+ }
+
+ @After
+ fun uninstallApp() {
+ uninstallPackage(APP_PACKAGE_NAME)
+ }
+
+ @Test
+ fun helpPrintsNonEmpty() {
+ assertThat(runShellCommandOrThrow("cmd role help")).isNotEmpty()
+ }
+
+ @Test
+ fun dontAddRoleHolderThenIsNotRoleHolder() {
+ assertIsRoleHolder(false)
+ }
+
+ @Test
+ fun addRoleHolderThenIsRoleHolder() {
+ addRoleHolder()
+
+ assertIsRoleHolder(true)
+ }
+
+ @Test
+ fun addAndRemoveRoleHolderThenIsNotRoleHolder() {
+ addRoleHolder()
+ removeRoleHolder()
+
+ assertIsRoleHolder(false)
+ }
+
+ @Test
+ fun addAndClearRoleHolderThenIsNotRoleHolder() {
+ addRoleHolder()
+ clearRoleHolders()
+
+ assertIsRoleHolder(false)
+ }
+
+ @Test
+ fun addInvalidRoleHolderThenFails() {
+ assertThrows(AssertionError::class.java) {
+ runShellCommandOrThrow("cmd role add-role-holder --user $userId $ROLE_NAME invalid")
+ }
+ }
+
+ @Test
+ fun addRoleHolderThenAppearsInDumpsys() {
+ addRoleHolder()
+
+ assertThat(runShellCommandOrThrow("dumpsys role")).contains(APP_PACKAGE_NAME)
+ }
+
+ @Test
+ fun setBypassingRoleQualificationToTrueThenSetsToTrue() {
+ setBypassingRoleQualification(false)
+
+ runShellCommandOrThrow("cmd role set-bypassing-role-qualification true")
+
+ assertThat(isBypassingRoleQualification()).isTrue()
+ }
+
+ @Test
+ fun setBypassingRoleQualificationToFalseThenSetsToFalse() {
+ setBypassingRoleQualification(true)
+
+ runShellCommandOrThrow("cmd role set-bypassing-role-qualification false")
+
+ assertThat(isBypassingRoleQualification()).isFalse()
+ }
+
+ private fun addRoleHolder(packageName: String = APP_PACKAGE_NAME) {
+ runShellCommandOrThrow("cmd role add-role-holder --user $userId $ROLE_NAME $packageName")
+ }
+
+ private fun removeRoleHolder(packageName: String = APP_PACKAGE_NAME) {
+ runShellCommandOrThrow("cmd role remove-role-holder --user $userId $ROLE_NAME $packageName")
+ }
+
+ private fun clearRoleHolders() {
+ runShellCommandOrThrow("cmd role clear-role-holders --user $userId $ROLE_NAME")
+ }
+
+ private fun getRoleHolders(): List<String> =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ runShellCommandOrThrow("cmd role get-role-holders --user $userId $ROLE_NAME")
+ .trim().let { if (it.isNotEmpty()) it.split(";") else emptyList() }
+ } else {
+ callWithShellPermissionIdentity { roleManager.getRoleHolders(ROLE_NAME) }
+ }
+
+ private fun assertIsRoleHolder(shouldBeRoleHolder: Boolean) {
+ val packageNames = getRoleHolders()
+ if (shouldBeRoleHolder) {
+ assertThat(packageNames).contains(APP_PACKAGE_NAME)
+ } else {
+ assertThat(packageNames).doesNotContain(APP_PACKAGE_NAME)
+ }
+ }
+
+ private fun installPackage(apkPath: String) {
+ assertThat(runShellCommandOrThrow("pm install -r --user $userId $apkPath").trim())
+ .isEqualTo("Success")
+ }
+
+ private fun uninstallPackage(packageName: String) {
+ assertThat(runShellCommandOrThrow("pm uninstall --user $userId $packageName").trim())
+ .isEqualTo("Success")
+ }
+
+ private fun isBypassingRoleQualification(): Boolean =
+ callWithShellPermissionIdentity { roleManager.isBypassingRoleQualification() }
+
+ private fun setBypassingRoleQualification(value: Boolean) {
+ callWithShellPermissionIdentity {
+ roleManager.setBypassingRoleQualification(value)
+ }
+ }
+
+ companion object {
+ private const val ROLE_NAME = RoleManager.ROLE_BROWSER
+ private const val APP_APK_PATH = "/data/local/tmp/cts/role/CtsRoleTestApp.apk"
+ private const val APP_PACKAGE_NAME = "android.app.role.cts.app"
+ }
+}
diff --git a/tests/cts/role/src/android/app/role/cts/WaitForResultActivity.java b/tests/cts/role/src/android/app/role/cts/WaitForResultActivity.java
new file mode 100644
index 000000000..03944a50f
--- /dev/null
+++ b/tests/cts/role/src/android/app/role/cts/WaitForResultActivity.java
@@ -0,0 +1,62 @@
+/*
+ * 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 android.app.role.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An Activity that can start another Activity and wait for its result.
+ */
+public class WaitForResultActivity extends Activity {
+
+ private static final int REQUEST_CODE_WAIT_FOR_RESULT = 1;
+
+ private CountDownLatch mLatch;
+ private int mResultCode;
+ private Intent mData;
+
+ public void startActivityToWaitForResult(@NonNull Intent intent) {
+ mLatch = new CountDownLatch(1);
+ startActivityForResult(intent, REQUEST_CODE_WAIT_FOR_RESULT);
+ }
+
+ @NonNull
+ public Pair<Integer, Intent> waitForActivityResult(long timeoutMillis)
+ throws InterruptedException {
+ mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
+ return new Pair<>(mResultCode, mData);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ if (requestCode == REQUEST_CODE_WAIT_FOR_RESULT) {
+ mResultCode = resultCode;
+ mData = data;
+ mLatch.countDown();
+ } else {
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+}
diff --git a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
index 00832b2da..8bb27929d 100644
--- a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
+++ b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
@@ -1105,18 +1105,14 @@ class SafetyCenterActivityTest {
context.launchSafetyCenterActivity {
clickMoreIssuesCard()
- val uiDevice = getUiDevice()
- uiDevice.waitForIdle()
-
- // Verify cards initially expanded
- waitExpandedIssuesDisplayed(
- safetySourceTestData.criticalResolvingGeneralIssue,
- safetySourceTestData.recommendationGeneralIssue,
- safetySourceTestData.informationIssue
- )
+ // Not checking that all the cards are correctly expanded here, as it is already covered
+ // by other tests and makes this tests too slow otherwise. See b/288381777.
+ // We still check that the middle card title is displayed though, as this helps ensure
+ // the expansion did go through.
+ waitAllTextDisplayed(safetySourceTestData.recommendationGeneralIssue.title)
// Device rotation to trigger usage of savedinstancestate via config update
- uiDevice.rotate()
+ getUiDevice().rotate()
// Verify cards remain expanded
waitExpandedIssuesDisplayed(
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt
index 86bc5f88e..a97b24bb8 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterNotificationTest.kt
@@ -989,6 +989,73 @@ class SafetyCenterNotificationTest {
}
@Test
+ @SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
+ fun sendActionPendingIntent_actionIdDiffersFromIssueActionId_successNotification() {
+ val notification =
+ SafetySourceIssue.Notification.Builder("Custom title", "Custom text")
+ .addAction(
+ SafetySourceIssue.Action.Builder(
+ "notification_action_id",
+ "Solve now!",
+ safetySourceTestData.resolvingActionPendingIntent(
+ sourceIssueActionId = "notification_action_id"
+ )
+ )
+ .setWillResolve(true)
+ .setSuccessMessage("Solved via notification action :)")
+ .build()
+ )
+ .build()
+ val data =
+ safetySourceTestData
+ .defaultCriticalDataBuilder()
+ .clearIssues()
+ .addIssue(
+ safetySourceTestData
+ .defaultCriticalResolvingIssueBuilder()
+ .clearActions()
+ .addAction(
+ SafetySourceIssue.Action.Builder(
+ "issue_action_id",
+ "Default action",
+ safetySourceTestData.resolvingActionPendingIntent(
+ sourceIssueActionId = "issue_action_id"
+ )
+ )
+ .setWillResolve(true)
+ .setSuccessMessage("Solved via issue action :(")
+ .build()
+ )
+ .setCustomNotification(notification)
+ .setNotificationBehavior(
+ SafetySourceIssue.NOTIFICATION_BEHAVIOR_IMMEDIATELY
+ )
+ .build()
+ )
+ .build()
+
+ safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, data)
+ val notificationWithChannel = TestNotificationListener.waitForSingleNotification()
+ val action =
+ notificationWithChannel.statusBarNotification.notification.actions.firstOrNull()
+ checkNotNull(action) { "Notification action unexpectedly null" }
+ SafetySourceReceiver.setResponse(
+ Request.ResolveAction(SINGLE_SOURCE_ID),
+ Response.SetData(safetySourceTestData.information)
+ )
+
+ sendActionPendingIntentAndWaitWithPermission(action)
+
+ TestNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ "Solved via notification action :)",
+ "",
+ actions = emptyList(),
+ )
+ )
+ }
+
+ @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.
diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt
index d020aac0a..4d945aad7 100644
--- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt
+++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt
@@ -27,16 +27,21 @@ import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.reportSafetySourceErrorWithPermission
import com.android.safetycenter.testing.SafetyCenterFlags
import com.android.safetycenter.testing.SafetyCenterTestConfigs
+import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SINGLE_SOURCE_ID
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_1
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_2
import com.android.safetycenter.testing.SafetyCenterTestConfigs.Companion.SOURCE_ID_3
+import com.android.safetycenter.testing.SafetyCenterTestData
import com.android.safetycenter.testing.SafetyCenterTestHelper
import com.android.safetycenter.testing.SafetyCenterTestRule
import com.android.safetycenter.testing.SafetySourceIntentHandler.Request
import com.android.safetycenter.testing.SafetySourceIntentHandler.Response
import com.android.safetycenter.testing.SafetySourceReceiver
+import com.android.safetycenter.testing.SafetySourceReceiver.Companion.executeSafetyCenterIssueActionWithPermissionAndWait
import com.android.safetycenter.testing.SafetySourceReceiver.Companion.refreshSafetySourcesWithReceiverPermissionAndWait
import com.android.safetycenter.testing.SafetySourceTestData
+import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ACTION_ID
+import com.android.safetycenter.testing.SafetySourceTestData.Companion.CRITICAL_ISSUE_ID
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -139,6 +144,16 @@ class SafetySourceStateCollectedLoggingHelperTests {
)
}
+ @Test
+ fun resolvingAction_success() {
+ simulateResolvingActionWith(Response.SetData(safetySourceTestData.information))
+ }
+
+ @Test
+ fun resolvingAction_error() {
+ simulateResolvingActionWith(Response.Error)
+ }
+
private fun simulateRefresh(
source1Response: Response?,
source2Response: Response?,
@@ -167,4 +182,22 @@ class SafetySourceStateCollectedLoggingHelperTests {
safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(refreshReason)
listener.waitForSafetyCenterRefresh()
}
+
+ private fun simulateResolvingActionWith(response: Response) {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
+ safetyCenterTestHelper.setData(
+ SINGLE_SOURCE_ID,
+ safetySourceTestData.criticalWithResolvingGeneralIssue
+ )
+ SafetySourceReceiver.setResponse(Request.ResolveAction(SINGLE_SOURCE_ID), response)
+
+ safetyCenterManager.executeSafetyCenterIssueActionWithPermissionAndWait(
+ SafetyCenterTestData.issueId(SINGLE_SOURCE_ID, CRITICAL_ISSUE_ID),
+ SafetyCenterTestData.issueActionId(
+ SINGLE_SOURCE_ID,
+ CRITICAL_ISSUE_ID,
+ CRITICAL_ISSUE_ACTION_ID
+ )
+ )
+ }
}
diff --git a/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterSystemEventReportedLoggingHostTest.kt b/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterSystemEventReportedLoggingHostTest.kt
index 4563d2e63..2589c7a47 100644
--- a/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterSystemEventReportedLoggingHostTest.kt
+++ b/tests/hostside/safetycenter/src/android/safetycenter/hostside/SafetyCenterSystemEventReportedLoggingHostTest.kt
@@ -139,6 +139,7 @@ class SafetyCenterSystemEventReportedLoggingHostTest : BaseHostJUnit4Test() {
.that(systemEventAtoms.count { it.refreshReason == REFRESH_REASON_BUTTON_CLICK })
}
+ @Test
fun refreshAllSources_firstTime_allSourcesSuccessful_dataChangedTrueForAll() {
helperAppRule.runTest(
".SafetySourceStateCollectedLoggingHelperTests",
@@ -193,6 +194,38 @@ class SafetyCenterSystemEventReportedLoggingHostTest : BaseHostJUnit4Test() {
.isEqualTo(1) // Only source 1
}
+ @Test
+ fun resolveAction_success_resolvingActionSuccessEvent() {
+ helperAppRule.runTest(
+ ".SafetySourceStateCollectedLoggingHelperTests",
+ "resolvingAction_success"
+ )
+
+ val resolvingActionEvent =
+ ReportUtils.getEventMetricDataList(device)
+ .mapNotNull { it.atom.safetyCenterSystemEventReported }
+ .single { it.eventType == EventType.INLINE_ACTION }
+
+ assertThat(resolvingActionEvent.result).isEqualTo(Result.SUCCESS)
+ assertThat(resolvingActionEvent.encodedIssueTypeId).isNotEqualTo(0)
+ }
+
+ @Test
+ fun resolveAction_error_resolvingActionErrorEvent() {
+ helperAppRule.runTest(
+ ".SafetySourceStateCollectedLoggingHelperTests",
+ "resolvingAction_error"
+ )
+
+ val resolvingActionEvent =
+ ReportUtils.getEventMetricDataList(device)
+ .mapNotNull { it.atom.safetyCenterSystemEventReported }
+ .single { it.eventType == EventType.INLINE_ACTION }
+
+ assertThat(resolvingActionEvent.result).isEqualTo(Result.ERROR)
+ assertThat(resolvingActionEvent.encodedIssueTypeId).isNotEqualTo(0)
+ }
+
companion object {
private const val REFRESH_REASON_PAGE_OPEN = 100L
private const val REFRESH_REASON_BUTTON_CLICK = 200L
diff --git a/tests/hostside/safetycenter/src/android/safetycenter/hostside/rules/RequireSafetyCenterRule.kt b/tests/hostside/safetycenter/src/android/safetycenter/hostside/rules/RequireSafetyCenterRule.kt
index 809fe5f0f..edf76e888 100644
--- a/tests/hostside/safetycenter/src/android/safetycenter/hostside/rules/RequireSafetyCenterRule.kt
+++ b/tests/hostside/safetycenter/src/android/safetycenter/hostside/rules/RequireSafetyCenterRule.kt
@@ -28,10 +28,10 @@ import org.junit.runners.model.Statement
class RequireSafetyCenterRule(private val hostTestClass: BaseHostJUnit4Test) : TestRule {
private val safetyCenterSupported: Boolean by lazy {
- executeShellCommandOrThrow("cmd safety_center supported").toBoolean()
+ shellCommandStdoutOrThrow("cmd safety_center supported").toBooleanStrict()
}
private val safetyCenterEnabled: Boolean by lazy {
- executeShellCommandOrThrow("cmd safety_center enabled").toBoolean()
+ shellCommandStdoutOrThrow("cmd safety_center enabled").toBooleanStrict()
}
override fun apply(base: Statement, description: Description): Statement {
@@ -45,13 +45,20 @@ class RequireSafetyCenterRule(private val hostTestClass: BaseHostJUnit4Test) : T
}
/** Returns the package name of Safety Center on the test device. */
- fun getSafetyCenterPackageName(): String =
- executeShellCommandOrThrow("cmd safety_center package-name")
+ fun getSafetyCenterPackageName(): String {
+ return shellCommandStdoutOrThrow("cmd safety_center package-name")
+ }
- private fun executeShellCommandOrThrow(command: String): String {
+ private fun shellCommandStdoutOrThrow(command: String): String {
val result = hostTestClass.device.executeShellV2Command(command)
if (result.status != CommandStatus.SUCCESS) {
- throw IOException("$command exited with status ${result.exitCode}")
+ throw IOException(
+ """Host-side test failed to execute adb shell command on test device.
+ |Command '$command' exited with status code ${result.exitCode}.
+ |This probably means the test device does not have a compatible version of
+ |the Permission Mainline module. Please check the test configuration."""
+ .trimMargin("|")
+ )
}
return result.stdout.trim()
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
index 6511f85f2..0dda827ee 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetySourceTestData.kt
@@ -425,12 +425,25 @@ class SafetySourceTestData(private val context: Context) {
.build()
/** A [PendingIntent] used by the resolving [Action] in [criticalResolvingGeneralIssue]. */
- val criticalIssueActionPendingIntent =
+ val criticalIssueActionPendingIntent = resolvingActionPendingIntent()
+
+ /**
+ * Returns a [PendingIntent] for a resolving [Action] with the given [sourceId], [sourceIssueId]
+ * and [sourceIssueActionId]. Default values are the same as those used by
+ * [criticalIssueActionPendingIntent]. *
+ */
+ fun resolvingActionPendingIntent(
+ sourceId: String = SINGLE_SOURCE_ID,
+ sourceIssueId: String = CRITICAL_ISSUE_ID,
+ sourceIssueActionId: String = CRITICAL_ISSUE_ACTION_ID
+ ) =
broadcastPendingIntent(
Intent(ACTION_RESOLVE_ACTION)
- .putExtra(EXTRA_SOURCE_ID, SINGLE_SOURCE_ID)
- .putExtra(EXTRA_SOURCE_ISSUE_ID, CRITICAL_ISSUE_ID)
- .putExtra(EXTRA_SOURCE_ISSUE_ACTION_ID, CRITICAL_ISSUE_ACTION_ID)
+ .putExtra(EXTRA_SOURCE_ID, sourceId)
+ .putExtra(EXTRA_SOURCE_ISSUE_ID, sourceIssueId)
+ .putExtra(EXTRA_SOURCE_ISSUE_ACTION_ID, sourceIssueActionId)
+ // Identifier is set because intent extras do not disambiguate PendingIntents
+ .setIdentifier(sourceId + sourceIssueId + sourceIssueActionId)
)
/** A resolving Critical [Action] */
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
index 0348a9c52..0e062692a 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt
@@ -22,6 +22,7 @@ import android.os.SystemClock
import android.safetycenter.SafetySourceData
import android.safetycenter.SafetySourceIssue
import android.safetycenter.config.SafetySourcesGroup
+import android.util.Log
import androidx.annotation.RequiresApi
import androidx.test.uiautomator.By
import androidx.test.uiautomator.BySelector
@@ -46,9 +47,9 @@ object UiTestHelper {
const val MORE_ISSUES_LABEL = "More alerts"
private const val DISMISS_ISSUE_LABEL = "Dismiss"
- private val WAIT_TIMEOUT = Duration.ofSeconds(20)
+ private const val TAG = "SafetyCenterUiTestHelper"
- private val TAG = UiTestHelper::class.java.simpleName
+ private val WAIT_TIMEOUT = Duration.ofSeconds(20)
/**
* Waits for the given [selector] to be displayed, and optionally perform a given
@@ -63,11 +64,13 @@ object UiTestHelper {
uiObjectAction(waitFindObject(selector, remaining.toMillis()))
return
} catch (e: StaleObjectException) {
- // Found, but stale before uiObjectAction could be performed, retry
+ Log.w(TAG, "Found stale UI object, retrying", e)
remaining = whenToTimeout - currentElapsedRealtime()
}
}
- throw TimeoutException("Timed out waiting for $selector to be displayed")
+ throw UiDumpUtils.wrapWithUiDump(
+ TimeoutException("Timed out waiting for $selector to be displayed after $WAIT_TIMEOUT")
+ )
}
/** Waits for all the given [textToFind] to be displayed. */
@@ -86,14 +89,16 @@ object UiTestHelper {
/** Waits for the given [selector] not to be displayed. */
fun waitNotDisplayed(selector: BySelector) {
- val timeoutMs = WAIT_TIMEOUT.toMillis()
// TODO(b/294038848): Add scrolling to make sure it is properly gone.
- val view = getUiDevice().wait(Until.gone(selector), timeoutMs)
- if (view == null) {
- throw UiDumpUtils.wrapWithUiDump(
- TimeoutException("View not found after waiting for $timeoutMs $selector")
- )
+ val gone = getUiDevice().wait(Until.gone(selector), WAIT_TIMEOUT.toMillis())
+ if (gone) {
+ return
}
+ throw UiDumpUtils.wrapWithUiDump(
+ TimeoutException(
+ "Timed out waiting for $selector not to be displayed after $WAIT_TIMEOUT"
+ )
+ )
}
/** Waits for all the given [textToFind] not to be displayed. */
@@ -113,11 +118,11 @@ object UiTestHelper {
*/
@RequiresApi(TIRAMISU)
fun waitSourceDataDisplayed(sourceData: SafetySourceData) {
- waitAllTextDisplayed(sourceData.status?.title, sourceData.status?.summary)
-
for (sourceIssue in sourceData.issues) {
waitSourceIssueDisplayed(sourceIssue)
}
+
+ waitAllTextDisplayed(sourceData.status?.title, sourceData.status?.summary)
}
/** Waits for most of the [SafetySourceIssue] information to be displayed. */
@@ -243,6 +248,7 @@ object UiTestHelper {
}
return Pattern.compile(regex)
}
+
private fun currentElapsedRealtime(): Duration =
Duration.ofMillis(SystemClock.elapsedRealtime())
}