diff options
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> & # ሌላ መተግበሪያ መዳረሻ ሰጥተዋል}one{እየነዱ ሳለ የ<xliff:g id="APP_1">%1$s</xliff:g> & # ሌላ መተግበሪያ መዳረሻ ሰጥተዋል}other{እየነዱ ሳለ የ<xliff:g id="APP_1">%1$s</xliff:g> & # ሌላ መተግበሪያ መዳረሻ ሰጥተዋል}}"</string> <string name="go_to_settings" msgid="1053735612211228335">"ወደ ቅንብሮች ሂድ"</string> - <string name="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">"ማይክሮፎን በ<b>ስልክ ጥሪ</b> ላይ ስራ ላይ ውሏል"</string> - <string name="phone_call_uses_microphone_and_camera" msgid="6291898755681748189">"ካሜራ እና ማይክሮፎን በ<b>ቪዲዮ ጥሪ</b> ላይ ስራ ላይ ውለዋል"</string> - <string name="phone_call_uses_camera" msgid="2048417022147857418">"ካሜራ በ<b>ቪዲዮ ጥሪ</b> ላይ ስራ ላይ ውሏል"</string> + <string name="phone_call_uses_microphone" msgid="233569591461187177">"ማይክሮፎን በ<b>ስልክ ጥሪ</b> ላይ ሥራ ላይ ውሏል"</string> + <string name="phone_call_uses_microphone_and_camera" msgid="6291898755681748189">"ካሜራ እና ማይክሮፎን በ<b>ቪዲዮ ጥሪ</b> ላይ ሥራ ላይ ውለዋል"</string> + <string name="phone_call_uses_camera" msgid="2048417022147857418">"ካሜራ በ<b>ቪዲዮ ጥሪ</b> ላይ ሥራ ላይ ውሏል"</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">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> የሰውነትዎ መሠረታዊ ምልክቶች የዳሳሽ ውሂብ እንዲደርስ ይፈቀድለት?"</string> <string name="permgroupbackgroundrequestdetail_sensors" msgid="7726767635834043501">"መተግበሪያውን በማይጠቀሙበት ጊዜ እንኳን ይህ መተግበሪያ የሰውነት ዳሳሽ ውሂብን ሁልጊዜ እንዲደርስ ለመፍቀድ "<annotation id="link">"ወደ ቅንብሮች ይሂዱ።"</annotation></string> - <string name="permgroupupgraderequest_sensors" msgid="7576527638411370468">"መተግበሪያ ስራ ላይ በሚውልበት ጊዜ የሰውነት ዳሳሽ ውሂብን እንዲደርስ ለ<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> መፍቀድ ይቀጥሉ?"</string> + <string name="permgroupupgraderequest_sensors" msgid="7576527638411370468">"መተግበሪያ ሥራ ላይ በሚውልበት ጊዜ የሰውነት ዳሳሽ ውሂብን እንዲደርስ ለ<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> መፍቀድ ይቀጥሉ?"</string> <string name="permgrouprequest_notifications" msgid="6396739062335106181">"<b><xliff:g id="APP_NAME">%1$s</xliff:g></b> ለእርስዎ ማሳወቂያዎች እንዲልክ ይፈቀድለት?"</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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> 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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>?"</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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>?"</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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> 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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> 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"><b>%1$s</b></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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> 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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>?"</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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>?"</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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> aceda à sua atividade física?"</string> <string name="permgrouprequest_camera" msgid="5123097035410002594">"Permitir que a app <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> 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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> 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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>?"</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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b>?"</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 <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> aceda aos registos de chamadas do seu telemóvel?"</string> <string name="permgrouprequest_phone" msgid="1829234136997316752">"Permitir que a app <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> faça e gira chamadas telefónicas?"</string> <string name="permgrouprequest_sensors" msgid="4397358316850652235">"Permitir que a app <b><xliff:g id="APP_NAME">%1$s</xliff:g></b> 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()) } |