diff options
227 files changed, 5753 insertions, 2875 deletions
diff --git a/PermissionController/OWNERS b/PermissionController/OWNERS index 3872198cb..5a58ec2f5 100644 --- a/PermissionController/OWNERS +++ b/PermissionController/OWNERS @@ -19,5 +19,6 @@ per-file res/** = file:platform/packages/modules/Permission:/SafetyCenter/OWNERS per-file WEAR_OWNERS = file:/PermissionController/WEAR_OWNERS per-file src/com/android/permissioncontroller/permission/ui/wear/** = file:/PermissionController/WEAR_OWNERS per-file src/com/android/permissioncontroller/role/ui/wear/** = file:/PermissionController/WEAR_OWNERS +per-file src/com/android/permissioncontroller/incident/wear/** = file:/PermissionController/WEAR_OWNERS per-file res/*-watch/* = file:/PermissionController/WEAR_OWNERS per-file tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/wear/** = file:/PermissionController/WEAR_OWNERS diff --git a/PermissionController/TEST_MAPPING b/PermissionController/TEST_MAPPING index a34a16034..508105c46 100644 --- a/PermissionController/TEST_MAPPING +++ b/PermissionController/TEST_MAPPING @@ -13,6 +13,9 @@ "file_patterns": ["res/xml/roles\\.xml"] }, { + "name": "CtsRoleMultiUserTestCases" + }, + { "name": "PermissionUiTestCases", "options": [ { @@ -49,6 +52,9 @@ "file_patterns": ["res/xml/roles\\.xml"] }, { + "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]" + }, + { "name": "PermissionControllerMockingTests[com.google.android.permission.apex]", "options": [ { @@ -111,6 +117,9 @@ "file_patterns": ["res/xml/roles\\.xml"] }, { + "name": "CtsRoleMultiUserTestCases" + }, + { "name": "PermissionControllerMockingTests", "options": [ { @@ -215,6 +224,24 @@ "name": "CtsPermissionUiTestCases[com.google.android.permission.apex]" } ], + "wear-presubmit": [ + { + "name": "CtsPermissionUiTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + }, + { + "name": "CtsRoleTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + } + ] + } + ], "imports": [ { "path": "vendor/xts/gts-tests/hostsidetests/permissioncontroller" diff --git a/PermissionController/res/anim/text_switcher_fade_in.xml b/PermissionController/res/anim/text_switcher_fade_in.xml new file mode 100644 index 000000000..b9e2812aa --- /dev/null +++ b/PermissionController/res/anim/text_switcher_fade_in.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:fromAlpha="0.0" android:toAlpha="1.0" + android:startOffset="@android:integer/config_shortAnimTime" + android:duration="@android:integer/config_shortAnimTime" />
\ No newline at end of file diff --git a/PermissionController/res/anim/text_switcher_fade_out.xml b/PermissionController/res/anim/text_switcher_fade_out.xml new file mode 100644 index 000000000..4b7274707 --- /dev/null +++ b/PermissionController/res/anim/text_switcher_fade_out.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<alpha xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/linear_interpolator" + android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_shortAnimTime" />
\ No newline at end of file diff --git a/PermissionController/res/layout-v33/view_status_card.xml b/PermissionController/res/layout-v33/view_status_card.xml index 4915347be..d8ca8b7ea 100644 --- a/PermissionController/res/layout-v33/view_status_card.xml +++ b/PermissionController/res/layout-v33/view_status_card.xml @@ -30,15 +30,34 @@ android:id="@+id/status_title_and_summary" style="?attr/scStatusTitleAndSummaryContainerStyle"> - <TextView + <TextSwitcher android:id="@+id/status_title" - android:text="@string/summary_placeholder" - style="@style/SafetyCenterStatusTitle" /> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inAnimation="@anim/text_switcher_fade_in" + android:outAnimation="@anim/text_switcher_fade_out"> + <TextView + android:text="@string/summary_placeholder" + style="@style/SafetyCenterStatusTitle" /> + <TextView + android:text="@string/summary_placeholder" + style="@style/SafetyCenterStatusTitle" /> + </TextSwitcher> - <TextView + + <TextSwitcher android:id="@+id/status_summary" - android:text="@string/summary_placeholder" - style="@style/SafetyCenterStatusSummary" /> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:inAnimation="@anim/text_switcher_fade_in" + android:outAnimation="@anim/text_switcher_fade_out"> + <TextView + android:text="@string/summary_placeholder" + style="@style/SafetyCenterStatusSummary" /> + <TextView + android:text="@string/summary_placeholder" + style="@style/SafetyCenterStatusSummary" /> + </TextSwitcher> </LinearLayout> <androidx.constraintlayout.widget.Barrier diff --git a/PermissionController/res/layout/permission_history_widget.xml b/PermissionController/res/layout/permission_history_widget.xml index 9bdef7200..f98a1c14b 100644 --- a/PermissionController/res/layout/permission_history_widget.xml +++ b/PermissionController/res/layout/permission_history_widget.xml @@ -28,6 +28,8 @@ android:layout_height="wrap_content" android:minWidth="60dp" android:layout_marginTop="19dp" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" + android:textColor="?android:attr/textColorSecondary" /> <LinearLayout diff --git a/PermissionController/res/values-ca/strings.xml b/PermissionController/res/values-ca/strings.xml index 720c0161d..fd1e23c51 100644 --- a/PermissionController/res/values-ca/strings.xml +++ b/PermissionController/res/values-ca/strings.xml @@ -346,7 +346,7 @@ <string name="no_apps_allowed" msgid="7718822655254468631">"Cap aplicació amb permís"</string> <string name="no_apps_allowed_full" msgid="8011716991498934104">"Cap aplicació té permís per accedir a tots els fitxers"</string> <string name="no_apps_allowed_scoped" msgid="4908850477787659501">"Cap aplicació té permís per accedir només a fitxers multimèdia"</string> - <string name="no_apps_denied" msgid="7663435886986784743">"Cap aplicació amb permís denegat"</string> + <string name="no_apps_denied" msgid="7663435886986784743">"Cap aplicació denegada"</string> <string name="car_permission_selected" msgid="180837028920791596">"Seleccionat"</string> <string name="settings" msgid="5409109923158713323">"Configuració"</string> <string name="accessibility_service_dialog_title_single" msgid="7956432823014102366">"<xliff:g id="SERVICE_NAME">%s</xliff:g> té accés complet al dispositiu"</string> diff --git a/PermissionController/res/values-es/strings.xml b/PermissionController/res/values-es/strings.xml index d73329ef8..84900f171 100644 --- a/PermissionController/res/values-es/strings.xml +++ b/PermissionController/res/values-es/strings.xml @@ -250,7 +250,7 @@ <string name="app_permission_most_recent_denied_summary" msgid="7659497197737708112">"Actualmente denegado / Último acceso: <xliff:g id="TIME_DATE">%1$s</xliff:g>"</string> <string name="app_permission_never_accessed_summary" msgid="401346181461975090">"No ha accedido nunca"</string> <string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"Denegado / Último acceso: Nunca"</string> - <string name="allowed_header" msgid="7769277978004790414">"Permitido"</string> + <string name="allowed_header" msgid="7769277978004790414">"Permitidas"</string> <string name="allowed_always_header" msgid="6455903312589013545">"Permitidas siempre"</string> <string name="allowed_foreground_header" msgid="6845655788447833353">"Permitidas solo mientras se usan"</string> <string name="allowed_storage_scoped" msgid="5383645873719086975">"Pueden acceder solo al contenido multimedia"</string> diff --git a/PermissionController/res/values-et/strings.xml b/PermissionController/res/values-et/strings.xml index 7622870c7..a289032c0 100644 --- a/PermissionController/res/values-et/strings.xml +++ b/PermissionController/res/values-et/strings.xml @@ -207,7 +207,7 @@ <string name="auto_revoke_label" msgid="5068393642936571656">"Eemalda load, kui rakendust ei kasutata"</string> <string name="unused_apps_label" msgid="2595428768404901064">"Eemalda load ja vabasta ruumi"</string> <string name="unused_apps_label_v2" msgid="7058776770056517980">"Kasutamata rakenduse tegevuste peatamine"</string> - <string name="unused_apps_label_v3" msgid="693340578642156657">"Halda kasutamata rakendusi"</string> + <string name="unused_apps_label_v3" msgid="693340578642156657">"Kasutamata rakenduste haldamine"</string> <string name="unused_apps_summary" msgid="8839466950318403115">"Eemaldatakse load, kustutatakse ajutised failid ja peatatakse märguanded"</string> <string name="unused_apps_summary_v2" msgid="5011313200815115802">"Eemalda load, kustuta ajutised failid, peata märguanded ja arhiivi rakendus"</string> <string name="auto_revoke_summary" msgid="5867548789805911683">"Teie andmete kaitsmiseks eemaldatakse selle rakenduse load, kui seda mõne kuu jooksul ei kasutata."</string> diff --git a/PermissionController/res/values-eu/strings.xml b/PermissionController/res/values-eu/strings.xml index 4e81fb5f1..73c1015b6 100644 --- a/PermissionController/res/values-eu/strings.xml +++ b/PermissionController/res/values-eu/strings.xml @@ -60,7 +60,7 @@ <string name="grant_dialog_button_allow_all_files" msgid="4955436994954829894">"Eman fitxategi guztiak kudeatzeko baimena"</string> <string name="grant_dialog_button_allow_media_only" msgid="4832877658422573832">"Eman multimedia-fitxategiak erabiltzeko baimena"</string> <string name="app_permissions_breadcrumb" msgid="5136969550489411650">"Aplikazioak"</string> - <string name="app_permissions" msgid="3369917736607944781">"Aplikazio-baimenak"</string> + <string name="app_permissions" msgid="3369917736607944781">"Aplikazio-baimenaU+2060k"</string> <string name="unused_apps" msgid="2058057455175955094">"Erabiltzen ez diren aplikazioak"</string> <string name="edit_photos_description" msgid="5540108003480078892">"Editatu aplikazio honetarako hautatutako argazkiak"</string> <string name="no_unused_apps" msgid="12809387670415295">"Ez dago erabiltzen ez duzun aplikaziorik"</string> @@ -279,7 +279,7 @@ <string name="post_drive_permission_decision_reminder_summary_1_app_2_permissions" msgid="671791184670801301">"Gidatu bitartean, <xliff:g id="PERMISSION_1">%2$s</xliff:g> eta <xliff:g id="PERMISSION_2">%3$s</xliff:g> erabiltzeko baimena eman diozu <xliff:g id="APP">%1$s</xliff:g> aplikazioari"</string> <string name="post_drive_permission_decision_reminder_summary_1_app_multi_permission" msgid="4080701771111456927">"Gidatu bitartean, <xliff:g id="COUNT">%1$d</xliff:g> baimen eman dizkiozu <xliff:g id="APP">%2$s</xliff:g> aplikazioari"</string> <string name="post_drive_permission_decision_reminder_summary_multi_apps" msgid="5253882771252863902">"{count,plural, =1{Gidatu bitartean, baimenak eman dizkiezu <xliff:g id="APP_0">%1$s</xliff:g> eta beste # aplikaziori}other{Gidatu bitartean, baimenak eman dizkiezu <xliff:g id="APP_1">%1$s</xliff:g> eta beste # aplikaziori}}"</string> - <string name="go_to_settings" msgid="1053735612211228335">"Joan ezarpenetara"</string> + <string name="go_to_settings" msgid="1053735612211228335">"Joan Ezarpenak atalera"</string> <string name="auto_revoke_setting_subtitle" msgid="8631720570723050460">"Aplikazio batzuk ez dira erabili zenbait hilabetez"</string> <string name="permissions_removed_category_title" msgid="1064754271178447643">"Baimenak kendu zaizkien aplikazioak"</string> <string name="permission_removed_page_title" msgid="2627436155091001209">"Baimenak kendu zaizkien aplikazioak"</string> diff --git a/PermissionController/res/values-fa/strings.xml b/PermissionController/res/values-fa/strings.xml index 307c1b5da..f04d8f4c9 100644 --- a/PermissionController/res/values-fa/strings.xml +++ b/PermissionController/res/values-fa/strings.xml @@ -320,7 +320,7 @@ <string name="permission_subtitle_only_in_foreground" msgid="9068389431267377564">"تنها هنگام استفاده از برنامه"</string> <string name="permission_subtitle_media_only" msgid="8917869683764720717">"رسانه"</string> <string name="permission_subtitle_all_files" msgid="4982613338298067862">"همه فایلها"</string> - <string name="permission_subtitle_background" msgid="8916750995309083180">"همیشه مجاز بودن"</string> + <string name="permission_subtitle_background" msgid="8916750995309083180">"همیشه مجاز است"</string> <string name="app_perms_24h_access" msgid="99069906850627181">"آخرین زمان دسترسی: <xliff:g id="TIME_DATE">%1$s</xliff:g>"</string> <string name="app_perms_24h_access_yest" msgid="5411926024794555022">"آخرین زمان دسترسی دیروز ساعت <xliff:g id="TIME_DATE">%1$s</xliff:g>"</string> <string name="app_perms_7d_access" msgid="4945055548894683751">"آخرین دسترسی: <xliff:g id="TIME_DATE_0">%1$s</xliff:g> ساعت <xliff:g id="TIME_DATE_1">%2$s</xliff:g>"</string> @@ -601,7 +601,7 @@ <string name="active_app_usage_2_qs" msgid="6107866785243565283">"<xliff:g id="APP_NAME">%1$s</xliff:g> درحال استفاده از آن است (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="recent_app_usage_2_qs" msgid="3591205954235694403">"اخیراً <xliff:g id="APP_NAME">%1$s</xliff:g> از آن استفاده کرده است (<xliff:g id="ATTRIBUTION_LABEL">%2$s</xliff:g> • <xliff:g id="PROXY_LABEL">%3$s</xliff:g>)"</string> <string name="media_confirm_dialog_positive_button" msgid="9020793594051526399">"تأیید"</string> - <string name="media_confirm_dialog_negative_button" msgid="226987376924861785">"برگشت"</string> + <string name="media_confirm_dialog_negative_button" msgid="226987376924861785">"برگشتن"</string> <string name="media_confirm_dialog_title_a_to_p_aural_allow" msgid="8560601114044699903">"دسترسی به فایلهای دیگر نیز مجاز میشود"</string> <string name="media_confirm_dialog_title_a_to_p_aural_deny" msgid="7841428716317307685">"دسترسی به فایلهای دیگر نیز مجاز نمیشود"</string> <string name="media_confirm_dialog_title_a_to_p_visual_allow" msgid="6469086448310893751">"دسترسی به فایلهای دیگر نیز مجاز میشود"</string> diff --git a/PermissionController/res/values-gu/strings.xml b/PermissionController/res/values-gu/strings.xml index 0ea95ae75..b97892c54 100644 --- a/PermissionController/res/values-gu/strings.xml +++ b/PermissionController/res/values-gu/strings.xml @@ -84,7 +84,7 @@ <string name="storage_supergroup_warning_allow" msgid="103093462784523190">"આ ઍપ Androidના જૂના વર્ઝન માટે ડિઝાઇન કરવામાં આવી હતી. જો તમે આ પરવાનગીને મંજૂરી આપશો, તો (ફોટા, વીડિયો, મ્યુઝિક, ઑડિયો અને અન્ય ફાઇલો સહિત) સંપૂર્ણ સ્ટોરેજના ઍક્સેસની મંજૂરી આપવામાં આવશે."</string> <string name="storage_supergroup_warning_deny" msgid="6420765672683284347">"આ ઍપ Androidના જૂના વર્ઝન માટે ડિઝાઇન કરવામાં આવી હતી. જો તમે આ પરવાનગી નકારો છો, તો (ફોટા, વીડિયો, મ્યુઝિક, ઑડિયો અને અન્ય ફાઇલો સહિત) સંપૂર્ણ સ્ટોરેજના ઍક્સેસની મંજૂરી નકારવામાં આવશે."</string> <string name="default_permission_description" msgid="4624464917726285203">"અજાણી ક્રિયા કરો"</string> - <string name="app_permissions_group_summary" msgid="8788419008958284002">"<xliff:g id="COUNT_1">%2$d</xliff:g> માંથી <xliff:g id="COUNT_0">%1$d</xliff:g> ઍપની મંજૂરી છે"</string> + <string name="app_permissions_group_summary" msgid="8788419008958284002">"<xliff:g id="COUNT_1">%2$d</xliff:g>માંથી <xliff:g id="COUNT_0">%1$d</xliff:g> ઍપને મંજૂરી છે"</string> <string name="app_permissions_group_summary2" msgid="4329922444840521150">"<xliff:g id="COUNT_0">%1$d</xliff:g>/<xliff:g id="COUNT_1">%2$d</xliff:g> ઍપને મંજૂરી છે"</string> <string name="menu_show_system" msgid="4254021607027872504">"સિસ્ટમ બતાવો"</string> <string name="menu_hide_system" msgid="3855390843744028465">"સિસ્ટમ છુપાવો"</string> @@ -579,7 +579,7 @@ <string name="safety_status_preference_title_and_summary_content_description" msgid="3511373256505058464">"સુરક્ષા અને પ્રાઇવસીનું સ્ટેટસ. <xliff:g id="OVERALL_SAFETY_STATUS">%1$s</xliff:g>. <xliff:g id="SUMMARY_OF_DEVICE_STATUS">%2$s</xliff:g>"</string> <string name="security_settings" msgid="3808106921175271317">"સુરક્ષા સેટિંગ"</string> <string name="sensor_permissions_qs" msgid="1022267900031317472">"પરવાનગીઓ"</string> - <string name="safety_privacy_qs_tile_title" msgid="727301867710374052">"સુરક્ષા અને પ્રાઇવસી"</string> + <string name="safety_privacy_qs_tile_title" msgid="727301867710374052">"સિક્યુરિટી અને પ્રાઇવસી"</string> <string name="safety_privacy_qs_tile_subtitle" msgid="3621544532041936749">"સ્ટેટસ ચેક કરો"</string> <string name="privacy_controls_qs" msgid="5780144882040591169">"તમારી પ્રાઇવસીને લગતા નિયંત્રણો"</string> <string name="security_settings_button_label_qs" msgid="8280343822465962330">"વધુ સેટિંગ"</string> diff --git a/PermissionController/res/values-hr/strings.xml b/PermissionController/res/values-hr/strings.xml index 7275d1681..54e0cf989 100644 --- a/PermissionController/res/values-hr/strings.xml +++ b/PermissionController/res/values-hr/strings.xml @@ -251,7 +251,7 @@ <string name="app_permission_never_accessed_summary" msgid="401346181461975090">"Nikad pristupljeno"</string> <string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"Odbijeno/nikad pristupljeno"</string> <string name="allowed_header" msgid="7769277978004790414">"Imaju dopuštenje"</string> - <string name="allowed_always_header" msgid="6455903312589013545">"Imaju dopuštenje cijelo vrijeme"</string> + <string name="allowed_always_header" msgid="6455903312589013545">"Pristup je dopušten cijelo vrijeme"</string> <string name="allowed_foreground_header" msgid="6845655788447833353">"Imaju dopuštenje samo tijekom upotrebe"</string> <string name="allowed_storage_scoped" msgid="5383645873719086975">"Dopušten pristup samo medijima"</string> <string name="allowed_storage_full" msgid="5356699280625693530">"Dopušteno upravljanje svim datotekama"</string> diff --git a/PermissionController/res/values-it/strings.xml b/PermissionController/res/values-it/strings.xml index df7a62b8f..d2b42e28b 100644 --- a/PermissionController/res/values-it/strings.xml +++ b/PermissionController/res/values-it/strings.xml @@ -92,7 +92,7 @@ <string name="menu_show_24_hours_data" msgid="8228054833323380780">"Mostra ultime 24 ore"</string> <string name="manage_permission" msgid="2895385393037061964">"Gestisci autorizzazione"</string> <string name="no_apps" msgid="2412612731628386816">"Nessuna app"</string> - <string name="location_settings" msgid="3624412509133422562">"Geolocalizzazione"</string> + <string name="location_settings" msgid="3624412509133422562">"Impostazioni di localizzazione"</string> <string name="location_warning" msgid="2381649060929040962">"L\'app <xliff:g id="APP_NAME">%1$s</xliff:g> è un fornitore di servizi di geolocalizzazione per questo dispositivo. È possibile modificare l\'accesso alla posizione dalle impostazioni per la geolocalizzazione."</string> <string name="system_warning" msgid="1173400963234358816">"Se rifiuti questa autorizzazione, le funzionalità di base del dispositivo potrebbero non funzionare più come previsto."</string> <string name="deny_read_media_visual_warning" msgid="3982586279917232827">"Questa app è stata progettata per una versione precedente di Android. Se non consenti a questa app di accedere a foto e video, verrà negato anche l\'accesso a musica e altro audio."</string> diff --git a/PermissionController/res/values-iw/strings.xml b/PermissionController/res/values-iw/strings.xml index 9779e6c66..946b0c08b 100644 --- a/PermissionController/res/values-iw/strings.xml +++ b/PermissionController/res/values-iw/strings.xml @@ -207,9 +207,9 @@ <string name="auto_revoke_label" msgid="5068393642936571656">"הסרת ההרשאות כשלא בשימוש"</string> <string name="unused_apps_label" msgid="2595428768404901064">"הסרת הרשאות ופינוי מקום אחסון"</string> <string name="unused_apps_label_v2" msgid="7058776770056517980">"השהיית הפעילות באפליקציה אם אין בה שימוש"</string> - <string name="unused_apps_label_v3" msgid="693340578642156657">"ניהול האפליקציה כשהיא לא בשימוש"</string> + <string name="unused_apps_label_v3" msgid="693340578642156657">"הגבלת האפליקציה כשהיא לא בשימוש"</string> <string name="unused_apps_summary" msgid="8839466950318403115">"הסרת ההרשאות, מחיקה של הקבצים הזמניים, הפסקה של קבלת ההתראות"</string> - <string name="unused_apps_summary_v2" msgid="5011313200815115802">"הסרת ההרשאות, מחיקה של הקבצים הזמניים, הפסקה של קבלת ההתראות והעברת האפליקציה לארכיון"</string> + <string name="unused_apps_summary_v2" msgid="5011313200815115802">"ההרשאות יוסרו, הקבצים הזמניים יימחקו, ההתראות יופסקו והאפליקציה תעבור לארכיון"</string> <string name="auto_revoke_summary" msgid="5867548789805911683">"כדי להגן על הנתונים שלך, אם האפליקציה הזו לא תהיה בשימוש במשך מספר חודשים, ההרשאות שניתנו לה יוסרו."</string> <string name="auto_revoke_summary_with_permissions" msgid="389712086597285013">"כדי להגן על הנתונים שלך, אם האפליקציה לא תהיה בשימוש במשך מספר חודשים, ההרשאות הבאות יוסרו: <xliff:g id="PERMS">%1$s</xliff:g>"</string> <string name="auto_revoked_apps_page_summary" msgid="6594753657893756536">"כדי להגן על הנתונים שלך, הוסרו הרשאות מאפליקציות שלא השתמשת בהן במשך מספר חודשים."</string> @@ -266,11 +266,11 @@ <string name="permission_reminders" msgid="6528257957664832636">"תזכורות להרשאות"</string> <string name="auto_revoke_permission_reminder_notification_title_one" msgid="6690347469376854137">"אפליקציה אחת שמזמן לא השתמשת בה"</string> <string name="auto_revoke_permission_reminder_notification_title_many" msgid="6062217713645069960">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> אפליקציות שמזמן לא השתמשת בהן"</string> - <string name="auto_revoke_permission_reminder_notification_content" msgid="4492228990462107487">"הוסרו הרשאות כדי להגן על הפרטיות שלך. יש להקיש כדי לבדוק"</string> + <string name="auto_revoke_permission_reminder_notification_content" msgid="4492228990462107487">"הוסרו הרשאות כדי להגן על הפרטיות שלך. יש ללחוץ כדי לבדוק"</string> <string name="auto_revoke_permission_notification_title" msgid="2629844160853454657">"הוסרו הרשאות לאפליקציות שמזמן לא השתמשת בהן"</string> - <string name="auto_revoke_permission_notification_content" msgid="5125990886047799375">"יש אפליקציות שלא נעשה בהן שימוש כבר כמה חודשים. אפשר להקיש כדי לבדוק."</string> + <string name="auto_revoke_permission_notification_content" msgid="5125990886047799375">"יש אפליקציות שלא נעשה בהן שימוש כבר כמה חודשים. אפשר ללחוץ כדי לבדוק."</string> <string name="unused_apps_notification_title" msgid="4314832015894238019">"{count,plural, =1{אפליקציה אחת שמזמן לא השתמשת בה}one{# אפליקציות שמזמן לא השתמשת בהן}two{# אפליקציות שמזמן לא השתמשת בהן}other{# אפליקציות שמזמן לא השתמשת בהן}}"</string> - <string name="unused_apps_notification_content" msgid="9195026773244581246">"ההרשאות בוטלו, הקבצים הזמניים הוסרו וההתראות הופסקו. יש להקיש כדי לבדוק."</string> + <string name="unused_apps_notification_content" msgid="9195026773244581246">"ההרשאות בוטלו, הקבצים הזמניים הוסרו וההתראות הופסקו. יש ללחוץ כדי לבדוק."</string> <string name="unused_apps_safety_center_card_title" msgid="5638409355530099149">"בדיקת אפליקציות שההרשאות שלהן הוסרו"</string> <string name="unused_apps_safety_center_card_content" msgid="1088557243627427820">"ההרשאות והקבצים הזמניים של אפליקציות שלא השתמשת בהן זמן מה הוסרו, וההתראות מהן הופסקו."</string> <string name="unused_apps_safety_center_action_title" msgid="8865914432518993194">"בדיקת האפליקציות"</string> @@ -287,7 +287,7 @@ <string name="months_ago" msgid="1766026492610646354">"לפני <xliff:g id="COUNT">%1$d</xliff:g> חודשים"</string> <string name="auto_revoke_preference_summary" msgid="5517958331781391481">"הוסרו הרשאות כדי להגן על הפרטיות שלך"</string> <string name="background_location_access_reminder_notification_title" msgid="1140797924301941262">"האפליקציה <xliff:g id="APP_NAME">%s</xliff:g> קיבלה גישה ברקע למיקום שלך"</string> - <string name="background_location_access_reminder_notification_content" msgid="7787084707336546245">"האפליקציה הזו יכולה תמיד לגשת למיקום שלך. יש להקיש כדי לשנות את ההגדרה הזו."</string> + <string name="background_location_access_reminder_notification_content" msgid="7787084707336546245">"האפליקציה הזו יכולה תמיד לגשת למיקום שלך. יש ללחוץ כדי לשנות את ההגדרה הזו."</string> <string name="notification_listener_reminder_notification_title" msgid="3747210460187479091">"בדיקת אפליקציה עם גישה להתראות"</string> <string name="notification_listener_reminder_notification_content" msgid="831476101108863427">"האפליקציה <xliff:g id="APP_NAME">%s</xliff:g> יכולה לסגור את ההתראות שלך, לפעול לפיהן ולגשת לתוכן בהתראות"</string> <string name="notification_listener_warning_card_content" msgid="7840973324284115893">"האפליקציה הזו יכולה לסגור את ההתראות שלך, לפעול לפיהן ולגשת לתוכן בהתראות. לחלק מהאפליקציות נדרשת גישה כזו כדי לתפקד כמו שצריך."</string> @@ -302,12 +302,12 @@ <string name="accessibility_remove_access_success_label" msgid="4380995302917014670">"הגישה הוסרה"</string> <string name="safety_center_notification_app_label" msgid="2457720616141926534">"מערכת Android"</string> <string name="auto_revoke_after_notification_title" msgid="5417761027669887431">"הוסרו הרשאות הניתנות לאפליקציה כדי להגן על הפרטיות"</string> - <string name="auto_revoke_after_notification_content_one" msgid="6804038707453662753">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%s</xliff:g>. יש להקיש כדי לבדוק."</string> - <string name="auto_revoke_after_notification_content_two" msgid="9108709764831425172">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%s</xliff:g> ובאפליקציה אחת נוספת. יש להקיש כדי לבדוק."</string> - <string name="auto_revoke_after_notification_content_many" msgid="4774106206289751220">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> וב-<xliff:g id="NUMBER_OF_APPS">%2$s</xliff:g> אפליקציות נוספות. יש להקיש כדי לבדוק."</string> + <string name="auto_revoke_after_notification_content_one" msgid="6804038707453662753">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%s</xliff:g>. יש ללחוץ כדי לבדוק."</string> + <string name="auto_revoke_after_notification_content_two" msgid="9108709764831425172">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%s</xliff:g> ובאפליקציה אחת נוספת. יש ללחוץ כדי לבדוק."</string> + <string name="auto_revoke_after_notification_content_many" msgid="4774106206289751220">"כבר כמה חודשים לא נעשה שימוש באפליקציה <xliff:g id="APP_NAME">%1$s</xliff:g> וב-<xliff:g id="NUMBER_OF_APPS">%2$s</xliff:g> אפליקציות נוספות. יש ללחוץ כדי לבדוק."</string> <string name="auto_revoke_before_notification_title_one" msgid="6758024954464359876">"אפליקציה אחת לא נמצאת בשימוש"</string> <string name="auto_revoke_before_notification_title_many" msgid="4415543943846385685">"<xliff:g id="NUMBER_OF_APPS">%s</xliff:g> אפליקציות לא נמצאות בשימוש"</string> - <string name="auto_revoke_before_notification_content_one" msgid="1156635373417068822">"הרשאות יוסרו כדי להגן על הפרטיות שלך. יש להקיש כדי לבדוק."</string> + <string name="auto_revoke_before_notification_content_one" msgid="1156635373417068822">"הרשאות יוסרו כדי להגן על הפרטיות שלך. יש ללחוץ כדי לבדוק."</string> <string name="unused_apps_title" msgid="8589298917717872239">"אפליקציות שמזמן לא השתמשת בהן"</string> <string name="unused_apps_subtitle_after" msgid="2034267519506357898">"הרשאות הוסרו מהאפליקציות"</string> <string name="unused_apps_subtitle_before" msgid="5233302577076132427">"הרשאות יוסרו מהאפליקציות"</string> @@ -358,7 +358,7 @@ <string name="role_assistant_description" msgid="6622458130459922952">"אפליקציות עזרה יכולות לסייע על סמך המידע שמוצג לך במסך. אפליקציות מסוימות תומכות גם בשירותי מרכז אפליקציות וגם בקלט קולי כדי לספק סיוע משולב."</string> <string name="role_browser_label" msgid="2877796144554070207">"אפליקציית ברירת מחדל לדפדפן"</string> <string name="role_browser_short_label" msgid="6745009127123292296">"אפליקציית דפדפן"</string> - <string name="role_browser_description" msgid="3465253637499842671">"אפליקציות שמספקות לך גישה לאינטרנט ומציגות קישורים להקשה"</string> + <string name="role_browser_description" msgid="3465253637499842671">"אפליקציות שמספקות לך גישה לאינטרנט ומציגות קישורים ללחיצה"</string> <string name="role_browser_request_title" msgid="2895200507835937192">"להגדיר את <xliff:g id="APP_NAME">%1$s</xliff:g> כאפליקציית הדפדפן המשמשת כברירת מחדל?"</string> <string name="role_browser_request_description" msgid="5888803407905985941">"אין צורך בהרשאות"</string> <string name="role_dialer_label" msgid="1100224146343237968">"אפליקציית ברירת המחדל לטלפון"</string> diff --git a/PermissionController/res/values-kk/strings.xml b/PermissionController/res/values-kk/strings.xml index 0413f0b54..a5680c1e5 100644 --- a/PermissionController/res/values-kk/strings.xml +++ b/PermissionController/res/values-kk/strings.xml @@ -251,7 +251,7 @@ <string name="app_permission_never_accessed_summary" msgid="401346181461975090">"Ешқашан пайдаланбады"</string> <string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"Тыйым салынған/Ешқашан пайдаланбаған"</string> <string name="allowed_header" msgid="7769277978004790414">"Рұқсат берілгендер"</string> - <string name="allowed_always_header" msgid="6455903312589013545">"Біржола рұқсат берілгендер"</string> + <string name="allowed_always_header" msgid="6455903312589013545">"Біржола рұқсат берілген"</string> <string name="allowed_foreground_header" msgid="6845655788447833353">"Пайдаланғанда ғана рұқсат берілгендер"</string> <string name="allowed_storage_scoped" msgid="5383645873719086975">"Тек мультимедианы пайдалана алатын қолданбалар"</string> <string name="allowed_storage_full" msgid="5356699280625693530">"Барлық файлдарды басқара алатын қолданбалар"</string> diff --git a/PermissionController/res/values-ko/strings.xml b/PermissionController/res/values-ko/strings.xml index dac0845fb..35269a437 100644 --- a/PermissionController/res/values-ko/strings.xml +++ b/PermissionController/res/values-ko/strings.xml @@ -84,7 +84,7 @@ <string name="storage_supergroup_warning_allow" msgid="103093462784523190">"이 앱은 Android 이전 버전에 맞게 설계되었습니다. 저장소 액세스 권한을 부여하면 앱에서 모든 저장소(사진, 동영상, 음악, 오디오 및 기타 파일 포함)에 액세스할 수 있습니다."</string> <string name="storage_supergroup_warning_deny" msgid="6420765672683284347">"이 앱은 Android 이전 버전에 맞게 설계되었습니다. 저장소 액세스 권한을 부여하지 않으면 앱에서 모든 저장소(사진, 동영상, 음악, 오디오 및 기타 파일)에 액세스할 수 없습니다."</string> <string name="default_permission_description" msgid="4624464917726285203">"알 수 없는 작업 실행"</string> - <string name="app_permissions_group_summary" msgid="8788419008958284002">"<xliff:g id="COUNT_0">%1$d</xliff:g>/<xliff:g id="COUNT_1">%2$d</xliff:g>개 앱 허용됨"</string> + <string name="app_permissions_group_summary" msgid="8788419008958284002">"앱 <xliff:g id="COUNT_1">%2$d</xliff:g>개 중 <xliff:g id="COUNT_0">%1$d</xliff:g>개 허용됨"</string> <string name="app_permissions_group_summary2" msgid="4329922444840521150">"앱 <xliff:g id="COUNT_0">%1$d</xliff:g>/<xliff:g id="COUNT_1">%2$d</xliff:g>개에 권한 부여됨"</string> <string name="menu_show_system" msgid="4254021607027872504">"시스템 표시"</string> <string name="menu_hide_system" msgid="3855390843744028465">"시스템 숨기기"</string> diff --git a/PermissionController/res/values-lv/strings.xml b/PermissionController/res/values-lv/strings.xml index f6f852bfd..131aeff27 100644 --- a/PermissionController/res/values-lv/strings.xml +++ b/PermissionController/res/values-lv/strings.xml @@ -456,7 +456,7 @@ <string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> vēlas augšupielādēt atkļūdošanas informāciju."</string> <string name="incident_report_dialog_title" msgid="669104389325204095">"Vai kopīgot atkļūdošanas datus?"</string> <string name="incident_report_dialog_intro" msgid="5897733669850951832">"Sistēmā tika konstatēta problēma."</string> - <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> pieprasa augšupielādēt kļūdas pārskatu no šīs ierīces, kas veikts: <xliff:g id="DATE">%2$s</xliff:g> plkst. <xliff:g id="TIME">%3$s</xliff:g>. Kļūdu pārskatos ir ietverta personas informācija par jūsu ierīci vai lietotnēs reģistrēta informācija, piemēram, lietotājvārdi, atrašanās vietas dati, ierīču identifikatori un tīkla informācija. Kopīgojiet kļūdu pārskatus tikai ar lietotājiem un lietotnēm, kuriem uzticat šo informāciju. Vai atļaut lietotnei <xliff:g id="APP_NAME_1">%4$s</xliff:g> augšupielādēt kļūdas pārskatu?"</string> + <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> pieprasa augšupielādēt kļūdas pārskatu no šīs ierīces, kas veikts: <xliff:g id="DATE">%2$s</xliff:g> plkst. <xliff:g id="TIME">%3$s</xliff:g>. Kļūdu pārskatos ir ietverta personas informācija par jūsu ierīci vai lietotnēs reģistrēta informācija, piemēram, lietotājvārdi, atrašanās vietas dati, ierīču identifikatori un tīkla informācija. Kopīgojiet kļūdu pārskatus tikai ar lietotājiem un lietotnēm, kuriem uzticat šo informāciju. Vai atļaut <xliff:g id="APP_NAME_1">%4$s</xliff:g> augšupielādēt kļūdas pārskatu?"</string> <string name="incident_report_error_dialog_text" msgid="4189647113387092272">"Apstrādājot lietotnes <xliff:g id="APP_NAME">%1$s</xliff:g> kļūdas pārskatu, radās problēma. Tāpēc detalizēto atkļūdošanas datu kopīgošana tika liegta. Atvainojiet par traucējumu!"</string> <string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Atļaut"</string> <string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Neatļaut"</string> diff --git a/PermissionController/res/values-ne/strings.xml b/PermissionController/res/values-ne/strings.xml index 9a7a4739a..9fecf53b3 100644 --- a/PermissionController/res/values-ne/strings.xml +++ b/PermissionController/res/values-ne/strings.xml @@ -644,7 +644,7 @@ <string name="permission_rationale_data_sharing_varies_message" msgid="4224469559084489222">"एपको संस्करण, प्रयोगसम्बन्धी जानकारी, तपाईं बसोबास गर्ने क्षेत्र र तपाईंको उमेरका आधारमा जानकारीको व्यवस्थापनसम्बन्धी अभ्यासहरू फरक हुन सक्छन्। "<annotation id="link">"जानकारी सेयर गर्नेसम्बन्धी अभ्यासका बारेमा थप जानकारी"</annotation></string> <string name="permission_rationale_data_sharing_varies_message_without_link" msgid="4912763761399025094">"एपको संस्करण, प्रयोगसम्बन्धी जानकारी, तपाईं बसोबास गर्ने क्षेत्र र तपाईंको उमेरका आधारमा जानकारीको व्यवस्थापनसम्बन्धी अभ्यासहरू फरक हुन सक्छन्।"</string> <string name="permission_rationale_location_settings_title" msgid="7204145004850190953">"तपाईंको लोकेसन डेटा"</string> - <string name="permission_rationale_permission_settings_message" msgid="631286040979660267"><annotation id="link">"गोपनीयतासम्बन्धी सेटिङ"</annotation>"मा गई यो एपलाई दिइएको अनुमति परिवर्तन गर्नुहोस्"</string> + <string name="permission_rationale_permission_settings_message" msgid="631286040979660267"><annotation id="link">"गोपनीयतासम्बन्धी सेटिङ"</annotation>"मा गई यो एपलाई दिइएको एक्सेस परिवर्तन गर्नुहोस्"</string> <string name="permission_rationale_purpose_app_functionality" msgid="8397736681065841405">"एपका सुविधा उपलब्ध गराउने"</string> <string name="permission_rationale_purpose_analytics" msgid="2070800501189620712">"Analytics"</string> <string name="permission_rationale_purpose_developer_communications" msgid="6453047018892062374">"विकासकर्ताबाट जानकारी प्राप्त गर्ने"</string> diff --git a/PermissionController/res/values-pl/strings.xml b/PermissionController/res/values-pl/strings.xml index ae98edba8..491216726 100644 --- a/PermissionController/res/values-pl/strings.xml +++ b/PermissionController/res/values-pl/strings.xml @@ -250,13 +250,13 @@ <string name="app_permission_most_recent_denied_summary" msgid="7659497197737708112">"Aktualnie odmowa / ostatni dostęp: <xliff:g id="TIME_DATE">%1$s</xliff:g>"</string> <string name="app_permission_never_accessed_summary" msgid="401346181461975090">"Nigdy nie użyto"</string> <string name="app_permission_never_accessed_denied_summary" msgid="6596000497490905146">"Odmowa / nigdy nie użyto"</string> - <string name="allowed_header" msgid="7769277978004790414">"Mają dostęp"</string> + <string name="allowed_header" msgid="7769277978004790414">"Ma dostęp"</string> <string name="allowed_always_header" msgid="6455903312589013545">"Mają ciągły dostęp"</string> <string name="allowed_foreground_header" msgid="6845655788447833353">"Mają dostęp tylko podczas używania"</string> <string name="allowed_storage_scoped" msgid="5383645873719086975">"Zezwolono na dostęp tylko do multimediów"</string> <string name="allowed_storage_full" msgid="5356699280625693530">"Zezwolono na zarządzanie wszystkimi plikami"</string> <string name="ask_header" msgid="2633816846459944376">"Zawsze pytaj"</string> - <string name="denied_header" msgid="903209608358177654">"Nie mają dostępu"</string> + <string name="denied_header" msgid="903209608358177654">"Nie ma dostępu"</string> <string name="permission_group_name_with_device_name" msgid="8798741850536024820">"<xliff:g id="PERM_GROUP_NAME">%1$s</xliff:g> na tym urządzeniu: <xliff:g id="DEVICE_NAME">%2$s</xliff:g>"</string> <string name="storage_footer_hyperlink_text" msgid="8873343987957834810">"Zobacz więcej aplikacji z dostępem do wszystkich plików"</string> <string name="days" msgid="609563020985571393">"{count,plural, =1{1 dzień}few{# dni}many{# dni}other{# dnia}}"</string> diff --git a/PermissionController/res/values-ro/strings.xml b/PermissionController/res/values-ro/strings.xml index 6544022cf..d75d6c6de 100644 --- a/PermissionController/res/values-ro/strings.xml +++ b/PermissionController/res/values-ro/strings.xml @@ -456,7 +456,7 @@ <string name="incident_report_notification_text" msgid="3376480583513587923">"<xliff:g id="APP_NAME">%1$s</xliff:g> dorește să încarce informațiile despre remedierea erorilor."</string> <string name="incident_report_dialog_title" msgid="669104389325204095">"Trimiți datele despre remedierea erorilor?"</string> <string name="incident_report_dialog_intro" msgid="5897733669850951832">"Sistemul a detectat o problemă."</string> - <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> solicită încărcarea unui raport de erori de pe acest dispozitiv creat în data de <xliff:g id="DATE">%2$s</xliff:g> la <xliff:g id="TIME">%3$s</xliff:g>. Rapoartele de erori conțin informații cu caracter personal despre dispozitiv sau înregistrate de aplicații, de exemplu: nume de utilizator, date privind locațiile, identificatori ai dispozitivului și informații despre rețea. Trimite rapoarte de erori doar persoanelor și aplicațiilor de încredere. Permiți ca <xliff:g id="APP_NAME_1">%4$s</xliff:g> să încarce un raport de erori?"</string> + <string name="incident_report_dialog_text" msgid="5675553296891757523">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> solicită încărcarea unui raport de eroare de pe acest dispozitiv creat în data de <xliff:g id="DATE">%2$s</xliff:g> la <xliff:g id="TIME">%3$s</xliff:g>. Rapoartele de erori conțin informații cu caracter personal despre dispozitiv sau înregistrate de aplicații, de exemplu: nume de utilizator, date privind locațiile, identificatori ai dispozitivului și informații despre rețea. Trimite rapoarte de erori doar persoanelor și aplicațiilor de încredere. Permiți ca <xliff:g id="APP_NAME_1">%4$s</xliff:g> să încarce un raport de eroare?"</string> <string name="incident_report_error_dialog_text" msgid="4189647113387092272">"A apărut o eroare la procesarea raportului de eroare pentru <xliff:g id="APP_NAME">%1$s</xliff:g>. Astfel, accesul la datele detaliate de remedierea erorilor a fost refuzat. Ne cerem scuze pentru întrerupere."</string> <string name="incident_report_dialog_allow_label" msgid="2970242967721155239">"Permite"</string> <string name="incident_report_dialog_deny_label" msgid="3535314290677579383">"Refuz"</string> diff --git a/PermissionController/res/values-sk/strings.xml b/PermissionController/res/values-sk/strings.xml index d743a2237..4442bbc85 100644 --- a/PermissionController/res/values-sk/strings.xml +++ b/PermissionController/res/values-sk/strings.xml @@ -206,7 +206,7 @@ <string name="unused_apps_category_title" msgid="2988455616845243901">"Nastavenia nepoužívaných aplikácií"</string> <string name="auto_revoke_label" msgid="5068393642936571656">"Odstrániť povolenia, ak sa aplikácia nepoužíva"</string> <string name="unused_apps_label" msgid="2595428768404901064">"Odstraňovať povol. a uvoľňovať priestor"</string> - <string name="unused_apps_label_v2" msgid="7058776770056517980">"Pozastaviť aktivitu v nepoužívaných apl."</string> + <string name="unused_apps_label_v2" msgid="7058776770056517980">"Pozastaviť aktivitu pri nepoužívaní"</string> <string name="unused_apps_label_v3" msgid="693340578642156657">"Spravovať aplikáciu, ak sa nepoužíva"</string> <string name="unused_apps_summary" msgid="8839466950318403115">"Odstrániť povolenia, vymazať dočasné súbory a zastaviť upozornenia"</string> <string name="unused_apps_summary_v2" msgid="5011313200815115802">"Odstrániť povolenia, vymazať dočasné súbory, zastaviť upozornenia a archivovať aplikáciu"</string> diff --git a/PermissionController/res/values-sl-watch/strings.xml b/PermissionController/res/values-sl-watch/strings.xml index f93ba26b5..3f78007ac 100644 --- a/PermissionController/res/values-sl-watch/strings.xml +++ b/PermissionController/res/values-sl-watch/strings.xml @@ -21,7 +21,7 @@ <string name="preference_show_system_apps" msgid="1055740303992024300">"Prikaz sistemskih aplikacij"</string> <string name="permission_summary_enforced_by_policy" msgid="2352478756952948019">"Ni mogoče sprem."</string> <string name="generic_yes" msgid="2489207724988649846">"Da"</string> - <string name="generic_cancel" msgid="2631708607129269698">"Prekliči"</string> + <string name="generic_cancel" msgid="2631708607129269698">"Preklic"</string> <string name="permission_access_always" msgid="2107115233573823032">"Ves čas"</string> <string name="permission_access_only_foreground" msgid="4412115020089923986">"Med uporabo aplikacije"</string> <string name="app_permission_button_allow_always" msgid="4920899432212307102">"Ves čas"</string> diff --git a/PermissionController/res/values-sl/strings.xml b/PermissionController/res/values-sl/strings.xml index d0317471e..168646ac1 100644 --- a/PermissionController/res/values-sl/strings.xml +++ b/PermissionController/res/values-sl/strings.xml @@ -206,7 +206,7 @@ <string name="unused_apps_category_title" msgid="2988455616845243901">"Nastavitve neuporabljenih aplikacij"</string> <string name="auto_revoke_label" msgid="5068393642936571656">"Odstrani dovoljenja, če aplikacija ni v uporabi"</string> <string name="unused_apps_label" msgid="2595428768404901064">"Odstrani dovoljenja in sprosti prostor"</string> - <string name="unused_apps_label_v2" msgid="7058776770056517980">"Zaustavi dejavnost aplikacije ob neuporabi"</string> + <string name="unused_apps_label_v2" msgid="7058776770056517980">"Zaustavi aplikacijo ob neuporabi"</string> <string name="unused_apps_label_v3" msgid="693340578642156657">"Upravljanje aplikacije ob neuporabi"</string> <string name="unused_apps_summary" msgid="8839466950318403115">"Dovoljenja se odstranijo, začasne datoteke se izbrišejo in prikazovanje obvestil se ustavi."</string> <string name="unused_apps_summary_v2" msgid="5011313200815115802">"Odstranitev dovoljenj, izbris začasnih datotek, ustavitev prikazovanja obvestil in arhiviranje aplikacije"</string> diff --git a/PermissionController/res/values-sq/strings.xml b/PermissionController/res/values-sq/strings.xml index 9979f58ad..793b1beb1 100644 --- a/PermissionController/res/values-sq/strings.xml +++ b/PermissionController/res/values-sq/strings.xml @@ -445,7 +445,7 @@ <string name="car_default_app_selected" msgid="5416420830430644174">"Zgjedhur"</string> <string name="car_default_app_selected_with_info" msgid="1932204186080593500">"Zgjedhur - <xliff:g id="ADDITIONAL_INFO">%1$s</xliff:g>"</string> <string name="special_app_access_search_keyword" msgid="8032347212290774210">"qasje e veçantë e aplikacionit"</string> - <string name="special_app_access" msgid="5019319067120213797">"Qasje e veçantë aplikacioni"</string> + <string name="special_app_access" msgid="5019319067120213797">"Qasja e veçantë e apl."</string> <string name="no_special_app_access" msgid="6950277571805106247">"Jo qasje e veçantë aplikacioni"</string> <string name="special_app_access_no_apps" msgid="4102911722787886970">"Nuk ka aplikacione"</string> <string name="home_missing_work_profile_support" msgid="1756855847669387977">"Profili i punës nuk mbështetet"</string> diff --git a/PermissionController/res/values-uk/strings.xml b/PermissionController/res/values-uk/strings.xml index 7f94a193c..133c1f5e7 100644 --- a/PermissionController/res/values-uk/strings.xml +++ b/PermissionController/res/values-uk/strings.xml @@ -206,7 +206,7 @@ <string name="unused_apps_category_title" msgid="2988455616845243901">"Налаштування невикористовуваних додатків"</string> <string name="auto_revoke_label" msgid="5068393642936571656">"Відкликати дозволи, якщо додаток не використовується"</string> <string name="unused_apps_label" msgid="2595428768404901064">"Вилучати дозволи й звільняти місце"</string> - <string name="unused_apps_label_v2" msgid="7058776770056517980">"Призупиняти роботу в неактивний період"</string> + <string name="unused_apps_label_v2" msgid="7058776770056517980">"Призупиняти, коли додаток неактивний"</string> <string name="unused_apps_label_v3" msgid="693340578642156657">"Керувати невикористовуваним додатком"</string> <string name="unused_apps_summary" msgid="8839466950318403115">"Вилучити дозволи, видалити тимчасові файли й зупинити сповіщення"</string> <string name="unused_apps_summary_v2" msgid="5011313200815115802">"Вилучити дозволи, видалити тимчасові файли, зупинити сповіщення й архівувати додаток"</string> diff --git a/PermissionController/res/values-vi/strings.xml b/PermissionController/res/values-vi/strings.xml index fe798bab0..ecb94368c 100644 --- a/PermissionController/res/values-vi/strings.xml +++ b/PermissionController/res/values-vi/strings.xml @@ -206,7 +206,7 @@ <string name="unused_apps_category_title" msgid="2988455616845243901">"Chế độ cài đặt cho ứng dụng không dùng đến"</string> <string name="auto_revoke_label" msgid="5068393642936571656">"Thu hồi quyền nếu bạn không dùng ứng dụng"</string> <string name="unused_apps_label" msgid="2595428768404901064">"Thu hồi quyền và giải phóng dung lượng"</string> - <string name="unused_apps_label_v2" msgid="7058776770056517980">"Tạm dừng hoạt động của ứng dụng nếu không dùng"</string> + <string name="unused_apps_label_v2" msgid="7058776770056517980">"Dừng hoạt động ứng dụng nếu không dùng"</string> <string name="unused_apps_label_v3" msgid="693340578642156657">"Quản lý ứng dụng nếu không dùng"</string> <string name="unused_apps_summary" msgid="8839466950318403115">"Loại bỏ quyền, xoá tệp tạm thời và dừng thông báo"</string> <string name="unused_apps_summary_v2" msgid="5011313200815115802">"Loại bỏ quyền, xoá tệp tạm thời, dừng thông báo và lưu trữ ứng dụng"</string> diff --git a/PermissionController/res/values-watch/donottranslate.xml b/PermissionController/res/values-watch/donottranslate.xml index 43830a93c..03038ddd1 100644 --- a/PermissionController/res/values-watch/donottranslate.xml +++ b/PermissionController/res/values-watch/donottranslate.xml @@ -71,7 +71,7 @@ <dimen name="wear_compose_material3_numeral_extra_large_font_size">60sp</dimen> <dimen name="wear_compose_material3_title_small_font_size">14sp</dimen> <dimen name="wear_compose_material3_title_medium_font_size">16sp</dimen> - <dimen name="wear_compose_material3_title_large_font_size">20sp</dimen> + <dimen name="wear_compose_material3_title_large_font_size">18sp</dimen> <dimen name="wear_compose_material3_shape_corner_extra_small_size">4dp</dimen> <dimen name="wear_compose_material3_shape_corner_small_size">8dp</dimen> diff --git a/PermissionController/res/values-zh-rCN/strings.xml b/PermissionController/res/values-zh-rCN/strings.xml index ee6f9e3bf..1b76de832 100644 --- a/PermissionController/res/values-zh-rCN/strings.xml +++ b/PermissionController/res/values-zh-rCN/strings.xml @@ -203,13 +203,13 @@ <string name="app_permission_footer_app_permissions_link" msgid="4926890342636587393">"查看“<xliff:g id="APP">%1$s</xliff:g>”的所有权限"</string> <string name="app_permission_footer_permission_apps_link" msgid="3941988129992794327">"查看具有此权限的所有应用"</string> <string name="assistant_mic_label" msgid="1011432357152323896">"显示 Google 助理麦克风使用情况"</string> - <string name="unused_apps_category_title" msgid="2988455616845243901">"针对闲置应用的设置"</string> + <string name="unused_apps_category_title" msgid="2988455616845243901">"闲置应用设置"</string> <string name="auto_revoke_label" msgid="5068393642936571656">"如果未使用此应用,则移除相关权限"</string> <string name="unused_apps_label" msgid="2595428768404901064">"撤消权限并释放空间"</string> <string name="unused_apps_label_v2" msgid="7058776770056517980">"暂停闲置应用的活动"</string> <string name="unused_apps_label_v3" msgid="693340578642156657">"管理闲置应用"</string> - <string name="unused_apps_summary" msgid="8839466950318403115">"移除权限、删除临时文件并停止发送通知"</string> - <string name="unused_apps_summary_v2" msgid="5011313200815115802">"移除权限、删除临时文件、停止发送通知并归档应用"</string> + <string name="unused_apps_summary" msgid="8839466950318403115">"撤消权限、删除临时文件并停收通知"</string> + <string name="unused_apps_summary_v2" msgid="5011313200815115802">"撤消权限、删除临时文件、停收通知并归档应用"</string> <string name="auto_revoke_summary" msgid="5867548789805911683">"为了保护您的数据,如果您连续几个月未使用此应用,系统会移除其权限。"</string> <string name="auto_revoke_summary_with_permissions" msgid="389712086597285013">"为了保护您的数据,如果您连续几个月未使用此应用,系统会移除其以下权限:<xliff:g id="PERMS">%1$s</xliff:g>"</string> <string name="auto_revoked_apps_page_summary" msgid="6594753657893756536">"为了保护您的数据,对于您连续几个月未使用过的应用,系统已将其权限移除。"</string> diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml index 2402ed2bf..204fb74f5 100644 --- a/PermissionController/res/xml/roles.xml +++ b/PermissionController/res/xml/roles.xml @@ -724,10 +724,6 @@ featureFlag="android.app.appfunctions.flags.Flags.enableAppFunctionManager" /> <permission name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED" featureFlag="android.app.appfunctions.flags.Flags.enableAppFunctionManager" /> - <permission name="android.permission.COPY_ACCOUNTS" - featureFlag="android.app.admin.flags.Flags.splitCreateManagedProfileEnabled" /> - <permission name="android.permission.REMOVE_ACCOUNTS" - featureFlag="android.app.admin.flags.Flags.splitCreateManagedProfileEnabled" /> </permissions> </role> @@ -1495,10 +1491,8 @@ <permission name="android.permission.MANAGE_DEVICE_POLICY_SMS" minSdkVersion="35" /> <permission name="android.permission.MANAGE_DEVICE_POLICY_APP_FUNCTIONS" featureFlag="android.app.appfunctions.flags.Flags.enableAppFunctionManager" /> - <permission name="android.permission.COPY_ACCOUNTS" - featureFlag="android.app.admin.flags.Flags.splitCreateManagedProfileEnabled" /> - <permission name="android.permission.REMOVE_ACCOUNTS" - featureFlag="android.app.admin.flags.Flags.splitCreateManagedProfileEnabled" /> + <permission name="android.permission.MANAGE_DEFAULT_APPLICATIONS" minSdkVersion="36" + featureFlag="com.android.permission.flags.Flags.crossUserRoleEnabled" /> </permissions> </role> @@ -1891,7 +1885,7 @@ shortLabel="@string/role_for_testing_profile_group_exclusivity_short_label" showNone="true" uiBehavior="ReservedForTestingProfileGroupExclusivityRoleUiBehavior" - visible="true"/> + visible="false"/> <!--- ~ A role for the vendor package that provides privacy-preserving intelligent processor for diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java index d025d1b03..1dc0aea48 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/AssistantRoleBehavior.java @@ -230,7 +230,7 @@ public class AssistantRoleBehavior implements RoleBehavior { } @Override - public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, + public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { return VisibilityMixin.isVisible("config_showDefaultAssistant", false, user, context); } diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java index 0261e1eee..95b86f0ca 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/BrowserRoleBehavior.java @@ -157,7 +157,7 @@ public class BrowserRoleBehavior implements RoleBehavior { } @Override - public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, + public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { return VisibilityMixin.isVisible("config_showBrowserRole", true, user, context); } diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java index 153f4a6b4..aa0902136 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/DialerRoleBehavior.java @@ -80,7 +80,7 @@ public class DialerRoleBehavior implements RoleBehavior { } @Override - public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, + public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { return VisibilityMixin.isVisible("config_showDialerRole", true, user, context); } diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java index f19c86596..4a52b0b03 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/EmergencyRoleBehavior.java @@ -71,7 +71,7 @@ public class EmergencyRoleBehavior implements RoleBehavior { } @Override - public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, + public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { return VisibilityMixin.isVisible("config_showDefaultEmergency", false, user, context); } diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java index 8c1446b50..b226674a0 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/HomeRoleBehavior.java @@ -186,7 +186,7 @@ public class HomeRoleBehavior implements RoleBehavior { } @Override - public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, + public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { return VisibilityMixin.isVisible("config_showDefaultHome", false, user, context); } diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java index f02b4d90c..5299886f6 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/ReservedForTestingProfileGroupExclusivityRoleBehavior.java @@ -49,7 +49,7 @@ public class ReservedForTestingProfileGroupExclusivityRoleBehavior implements Ro } @Override - public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, + public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { if (RoleFlags.isProfileGroupExclusivityAvailable()) { Context userContext = UserUtils.getUserContext(context, user); diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java index 4aff8a163..ee429c10e 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/SmsRoleBehavior.java @@ -133,7 +133,7 @@ public class SmsRoleBehavior implements RoleBehavior { } @Override - public boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, + public Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { return VisibilityMixin.isVisible("config_showSmsRole", true, user, context); } diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java index 56c4944a0..99145c747 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/AppOp.java @@ -26,6 +26,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.modules.utils.build.SdkLevel; +import com.android.role.controller.util.RoleFlags; import com.android.role.controller.util.PackageUtils; import java.util.Objects; @@ -137,8 +138,8 @@ public class AppOp { return false; } return Build.VERSION.SDK_INT >= mMinSdkVersion - // Workaround to match the value 35 for V in roles.xml before SDK finalization. - || (mMinSdkVersion == 35 && SdkLevel.isAtLeastV()); + // Workaround to match the value 36 for B in roles.xml before SDK finalization. + || (mMinSdkVersion == 36 && RoleFlags.isAtLeastB()); } private boolean isAvailableAsUser(@NonNull String packageName, diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java b/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java index 05b19ff94..889f5263d 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/Permission.java @@ -26,6 +26,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.modules.utils.build.SdkLevel; +import com.android.role.controller.util.RoleFlags; import com.android.role.controller.util.UserUtils; import java.util.Objects; @@ -97,8 +98,8 @@ public class Permission { return false; } if (Build.VERSION.SDK_INT >= mMinSdkVersion - // Workaround to match the value 35 for V in roles.xml before SDK finalization. - || (mMinSdkVersion == 35 && SdkLevel.isAtLeastV())) { + // Workaround to match the value 36 for B in roles.xml before SDK finalization. + || (mMinSdkVersion == 36 && RoleFlags.isAtLeastB())) { return true; } if (Build.VERSION.SDK_INT >= mOptionalMinSdkVersion) { diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java index 1d49b3c1a..5109e505b 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/Role.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/Role.java @@ -502,8 +502,8 @@ public class Role { return false; } return (Build.VERSION.SDK_INT >= mMinSdkVersion - // Workaround to match the value 35 for V in roles.xml before SDK finalization. - || (mMinSdkVersion == 35 && SdkLevel.isAtLeastV())) + // Workaround to match the value 36 for B in roles.xml before SDK finalization. + || (mMinSdkVersion == 36 && RoleFlags.isAtLeastB())) && Build.VERSION.SDK_INT <= mMaxSdkVersion; } @@ -1098,11 +1098,23 @@ public class Role { * @return whether this role should be visible to user */ public boolean isVisibleAsUser(@NonNull UserHandle user, @NonNull Context context) { - RoleBehavior behavior = getBehavior(); - if (behavior == null) { - return isVisible(); + if (mBehavior != null) { + Boolean isVisibleAsUser = mBehavior.isVisibleAsUser(this, user, context); + if (isVisibleAsUser != null) { + if (isVisibleAsUser && mStatic) { + throw new IllegalArgumentException("static=\"true\" is invalid for a visible " + + "role: " + mName); + } + if (isVisibleAsUser && (mDescriptionResource == 0 + || mLabelResource == 0 + || mShortLabelResource == 0)) { + throw new IllegalArgumentException("description, label, and shortLabel are " + + "required for a visible role: " + mName); + } + return isVisibleAsUser; + } } - return isVisible() && behavior.isVisibleAsUser(this, user, context); + return isVisible(); } /** diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java index 86ca8e2ce..3b08265d1 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/RoleBehavior.java @@ -23,7 +23,6 @@ import android.os.UserHandle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.Collections; import java.util.List; /** @@ -129,11 +128,12 @@ public interface RoleBehavior { * @param user the user to check for * @param context the `Context` to retrieve system services * - * @return whether this role should be visible to user + * @return whether this role should be visible to user, or {@code null} if not overridden */ - default boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, + @Nullable + default Boolean isVisibleAsUser(@NonNull Role role, @NonNull UserHandle user, @NonNull Context context) { - return true; + return null; } /** diff --git a/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java b/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java index 4b05554e3..9c3746b79 100644 --- a/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java +++ b/PermissionController/role-controller/java/com/android/role/controller/model/RoleParser.java @@ -413,6 +413,9 @@ public class RoleParser { skipCurrentTag(parser); return null; } + } else if (behavior != null) { + labelResource = getAttributeResourceValue(parser, ATTRIBUTE_LABEL, 0); + shortLabelResource = getAttributeResourceValue(parser, ATTRIBUTE_SHORT_LABEL, 0); } else { labelResource = 0; shortLabelResource = 0; diff --git a/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt b/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt index e6cf094e3..952274d4a 100644 --- a/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt +++ b/PermissionController/src/com/android/permissioncontroller/ecm/EnhancedConfirmationDialogActivity.kt @@ -18,7 +18,6 @@ package com.android.permissioncontroller.ecm import android.annotation.SuppressLint import android.app.AlertDialog -import android.app.AppOpsManager import android.app.Dialog import android.app.ecm.EnhancedConfirmationManager import android.content.Context @@ -55,6 +54,7 @@ import com.android.role.controller.model.Roles class EnhancedConfirmationDialogActivity : FragmentActivity() { companion object { private const val KEY_WAS_CLEAR_RESTRICTION_ALLOWED = "KEY_WAS_CLEAR_RESTRICTION_ALLOWED" + private const val REASON_PHONE_STATE = "phone_state" } private var wasClearRestrictionAllowed: Boolean = false @@ -77,6 +77,7 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { val packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME) val settingIdentifier = intent.getStringExtra(Intent.EXTRA_SUBJECT) val isEcmInApp = intent.getBooleanExtra(EXTRA_IS_ECM_IN_APP, false) + val reason = intent.getStringExtra(Intent.EXTRA_REASON) require(uid != Process.INVALID_UID) { "EXTRA_UID cannot be null or invalid" } require(!packageName.isNullOrEmpty()) { "EXTRA_PACKAGE_NAME cannot be null or empty" } @@ -84,9 +85,9 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { wasClearRestrictionAllowed = setClearRestrictionAllowed(packageName, UserHandle.getUserHandleForUid(uid)) - val setting = Setting.fromIdentifier(this, settingIdentifier, isEcmInApp) + val setting = Setting.fromIdentifier(this, settingIdentifier, isEcmInApp, reason) if ( - SettingType.fromIdentifier(this, settingIdentifier, isEcmInApp) == + SettingType.fromIdentifier(this, settingIdentifier, isEcmInApp, reason) == SettingType.BLOCKED_DUE_TO_PHONE_STATE && !Flags.unknownCallPackageInstallBlockingEnabled() ) { @@ -127,8 +128,10 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { context: Context, settingIdentifier: String, isEcmInApp: Boolean, + reason: String?, ): Setting { - val settingType = SettingType.fromIdentifier(context, settingIdentifier, isEcmInApp) + val settingType = + SettingType.fromIdentifier(context, settingIdentifier, isEcmInApp, reason) val label = when (settingType) { SettingType.PLATFORM_PERMISSION -> @@ -189,10 +192,10 @@ class EnhancedConfirmationDialogActivity : FragmentActivity() { context: Context, settingIdentifier: String, isEcmInApp: Boolean, + restrictionReason: String?, ): SettingType { return when { - settingIdentifier == AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES -> - BLOCKED_DUE_TO_PHONE_STATE + restrictionReason == REASON_PHONE_STATE -> BLOCKED_DUE_TO_PHONE_STATE !isEcmInApp -> OTHER PermissionMapping.isRuntimePlatformPermission(settingIdentifier) && PermissionMapping.getGroupOfPlatformPermission(settingIdentifier) != null -> diff --git a/PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt b/PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt index 8e58d48d9..4c698cda3 100644 --- a/PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/incident/wear/WearConfirmationScreen.kt @@ -29,14 +29,15 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView -import androidx.wear.compose.foundation.lazy.ScalingLazyListState import androidx.wear.compose.material.CircularProgressIndicator -import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog -import com.android.permissioncontroller.permission.ui.wear.elements.SingleButtonAlertDialog +import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionConfirmationDialog +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme @Composable fun WearConfirmationScreen(viewModel: WearConfirmationActivityViewModel) { + val materialUIVersion = ResourceHelper.materialUIVersionInSettings // Wear screen doesn't show incident/bug report's optional reasons and images. val showDialog = viewModel.showDialogLiveData.observeAsState(false) val showDenyReport = viewModel.showDenyReportLiveData.observeAsState(false) @@ -47,27 +48,25 @@ fun WearConfirmationScreen(viewModel: WearConfirmationActivityViewModel) { if (isLoading) { CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } else { - if (showDenyReport.value) { - contentArgs.value?.let { - SingleButtonAlertDialog( - showDialog = showDialog.value, - title = it.title, - message = it.message, - onButtonClick = it.onDenyClick, - scalingLazyListState = ScalingLazyListState(0) + contentArgs.value?.apply { + if (showDenyReport.value) { + WearPermissionConfirmationDialog( + materialUIVersion = materialUIVersion, + show = showDialog.value, + title = title, + message = message, + positiveButtonContent = DialogButtonContent(onClick = onDenyClick), + ) + } else { + WearPermissionConfirmationDialog( + materialUIVersion = materialUIVersion, + show = showDialog.value, + title = title, + message = message, + positiveButtonContent = DialogButtonContent(onClick = onOkClick), + negativeButtonContent = DialogButtonContent(onClick = onCancelClick), ) } - return - } - contentArgs.value?.let { - AlertDialog( - showDialog = showDialog.value, - title = it.title, - message = it.message, - onOKButtonClick = it.onOkClick, - onCancelButtonClick = it.onCancelClick, - scalingLazyListState = ScalingLazyListState(0) - ) } } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt b/PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt index a69b78a06..5ba19f4c0 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/service/v33/SafetyCenterQsTileService.kt @@ -70,7 +70,6 @@ class SafetyCenterQsTileService : TileService() { qsTile.label = getString(R.string.safety_privacy_qs_tile_title) qsTile.subtitle = getString(R.string.safety_privacy_qs_tile_subtitle) qsTile.contentDescription = TextUtils.concat(qsTile.label, ", ", qsTile.subtitle) - qsTile.state = Tile.STATE_ACTIVE qsTile.updateTile() } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java index c1479caf2..a7114f30b 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java @@ -262,6 +262,9 @@ public class GrantPermissionsActivity extends SettingsActivity if (DeviceUtils.isWear(this)) { // Do not grab input focus and hide keyboard. getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + overrideActivityTransition(OVERRIDE_TRANSITION_OPEN, 0, 0); + } } if (PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER.equals(getIntent().getAction())) { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java index c9e9a2eb1..5100b08fd 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/GrantPermissionsWearViewHandler.java @@ -271,6 +271,12 @@ public class GrantPermissionsWearViewHandler implements GrantPermissionsViewHand WearGrantPermissionsScreenKt.setContent(root, mViewModel, + () -> { + if (mResultListener != null) { + mResultListener.onPermissionGrantResult(null, null, CANCELED); + } + return Unit.INSTANCE; + }, id -> { onButtonClicked(id); return Unit.INSTANCE; @@ -278,7 +284,8 @@ public class GrantPermissionsWearViewHandler implements GrantPermissionsViewHand checked -> { onLocationSwitchChanged(checked); return Unit.INSTANCE; - }); + } + ); if (mGroupName != null) { updateScreen(); } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt index 510d19706..ff3c2cbc1 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/LocationProviderDialogScreen.kt @@ -18,49 +18,39 @@ package com.android.permissioncontroller.permission.ui.wear import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.wear.compose.foundation.SwipeToDismissValue -import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState -import androidx.wear.compose.material.ChipDefaults -import androidx.wear.compose.material.MaterialTheme -import androidx.wear.compose.material.SwipeToDismissBox -import com.android.permissioncontroller.permission.ui.wear.elements.Chip +import androidx.wear.compose.material3.Dialog +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionScaffold import com.android.permissioncontroller.permission.ui.wear.model.LocationProviderInterceptDialogArgs -import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion @Composable -fun LocationProviderDialogScreen(args: LocationProviderInterceptDialogArgs?) { - args?.apply { - val state = rememberSwipeToDismissBoxState() - LaunchedEffect(state.currentValue) { - // If the swipe is complete - if (state.currentValue == SwipeToDismissValue.Dismissed) { - onOkButtonClick() - } - } - SwipeToDismissBox(state = state) { isBackground -> +fun LocationProviderDialogScreen( + showDialog: Boolean, + onDismissRequest: () -> Unit, + args: LocationProviderInterceptDialogArgs?, +) { + Dialog(show = showDialog, onDismissRequest = onDismissRequest) { + args?.run { WearPermissionScaffold( - materialUIVersion = WearPermissionMaterialUIVersion.MATERIAL2_5, showTimeText = false, image = iconId, title = stringResource(titleId), subtitle = message, - isLoading = isBackground, + isLoading = false, content = { item { - Chip( + WearPermissionButton( label = stringResource(locationSettingsId), - onClick = onLocationSettingsClick, modifier = Modifier.fillMaxWidth(), - textColor = MaterialTheme.colors.surface, - colors = ChipDefaults.primaryChipColors(), + onClick = onLocationSettingsClick, + style = WearPermissionButtonStyle.Primary, ) } item { - Chip( + WearPermissionButton( label = stringResource(okButtonTitleId), onClick = onOkButtonClick, modifier = Modifier.fillMaxWidth(), diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt index ba37205a6..5919fad0d 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionGroupsScreen.kt @@ -24,17 +24,20 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource -import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState import com.android.permissioncontroller.R -import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog -import com.android.permissioncontroller.permission.ui.wear.elements.Chip import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen -import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip -import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl +import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionConfirmationDialog +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType import com.android.permissioncontroller.permission.ui.wear.model.RevokeDialogArgs +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper +import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion @Composable fun WearAppPermissionGroupsScreen(helper: WearAppPermissionGroupsHelper) { + val materialUIVersion = ResourceHelper.materialUIVersionInSettings val packagePermGroups = helper.viewModel.packagePermGroupsLiveData.observeAsState(null) val autoRevoke = helper.viewModel.autoRevokeLiveData.observeAsState(null) val appPermissionUsages = helper.wearViewModel.appPermissionUsages.observeAsState(emptyList()) @@ -43,6 +46,9 @@ fun WearAppPermissionGroupsScreen(helper: WearAppPermissionGroupsHelper) { helper.locationProviderInterceptDialogViewModel.dialogVisibilityLiveData.observeAsState( false ) + val locationProviderDialogArgs = + helper.locationProviderInterceptDialogViewModel.locationProviderInterceptDialogArgs + .observeAsState(null) var isLoading by remember { mutableStateOf(true) } @@ -50,17 +56,18 @@ fun WearAppPermissionGroupsScreen(helper: WearAppPermissionGroupsHelper) { WearAppPermissionGroupsContent( isLoading, helper.getPermissionGroupChipParams(appPermissionUsages.value), - helper.getAutoRevokeChipParam(autoRevoke.value) + helper.getAutoRevokeChipParam(autoRevoke.value), ) RevokeDialog( + materialUIVersion = materialUIVersion, showDialog = showRevokeDialog.value, - args = helper.revokeDialogViewModel.revokeDialogArgs + args = helper.revokeDialogViewModel.revokeDialogArgs, + ) + LocationProviderDialogScreen( + showDialog = showLocationProviderDialog.value, + onDismissRequest = { helper.locationProviderInterceptDialogViewModel.dismissDialog() }, + args = locationProviderDialogArgs.value, ) - if (showLocationProviderDialog.value) { - LocationProviderDialogScreen( - helper.locationProviderInterceptDialogViewModel.locationProviderInterceptDialogArgs - ) - } } if (isLoading && !packagePermGroups.value.isNullOrEmpty()) { @@ -72,30 +79,32 @@ fun WearAppPermissionGroupsScreen(helper: WearAppPermissionGroupsHelper) { internal fun WearAppPermissionGroupsContent( isLoading: Boolean, permissionGroupChipParams: List<PermissionGroupChipParam>, - autoRevokeChipParam: AutoRevokeChipParam? + autoRevokeChipParam: AutoRevokeChipParam?, ) { ScrollableScreen(title = stringResource(R.string.app_permissions), isLoading = isLoading) { if (permissionGroupChipParams.isEmpty()) { - item { Chip(label = stringResource(R.string.no_permissions), onClick = {}) } + item { + WearPermissionButton(label = stringResource(R.string.no_permissions), onClick = {}) + } } else { for (info in permissionGroupChipParams) { item { if (info.checked != null) { - ToggleChip( - checked = info.checked, + WearPermissionToggleControl( + toggleControl = WearPermissionToggleControlType.Switch, label = info.label, + checked = info.checked, enabled = info.enabled, - toggleControl = ToggleChipToggleControl.Switch, - onCheckedChanged = info.onCheckedChanged + onCheckedChanged = info.onCheckedChanged, ) } else { - Chip( + WearPermissionButton( label = info.label, labelMaxLines = Integer.MAX_VALUE, secondaryLabel = info.summary?.let { info.summary }, secondaryLabelMaxLines = Integer.MAX_VALUE, enabled = info.enabled, - onClick = info.onClick + onClick = info.onClick, ) } } @@ -103,12 +112,12 @@ internal fun WearAppPermissionGroupsContent( autoRevokeChipParam?.let { if (it.visible) { item { - ToggleChip( + WearPermissionToggleControl( checked = it.checked, label = stringResource(it.labelRes), - labelMaxLine = 3, - toggleControl = ToggleChipToggleControl.Switch, - onCheckedChanged = it.onCheckedChanged + labelMaxLines = 3, + toggleControl = WearPermissionToggleControlType.Switch, + onCheckedChanged = it.onCheckedChanged, ) } } @@ -118,14 +127,19 @@ internal fun WearAppPermissionGroupsContent( } @Composable -internal fun RevokeDialog(showDialog: Boolean, args: RevokeDialogArgs?) { - args?.let { - AlertDialog( - showDialog = showDialog, - message = stringResource(it.messageId), - onOKButtonClick = it.onOkButtonClick, - onCancelButtonClick = it.onCancelButtonClick, - scalingLazyListState = rememberScalingLazyListState() +internal fun RevokeDialog( + materialUIVersion: WearPermissionMaterialUIVersion, + showDialog: Boolean, + args: RevokeDialogArgs?, +) { + + args?.run { + WearPermissionConfirmationDialog( + materialUIVersion = materialUIVersion, + show = showDialog, + message = stringResource(messageId), + positiveButtonContent = DialogButtonContent(onClick = onOkButtonClick), + negativeButtonContent = DialogButtonContent(onClick = onCancelButtonClick), ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt index 202ad49bb..8815e7905 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearAppPermissionScreen.kt @@ -24,21 +24,26 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource -import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState import androidx.wear.compose.material.ToggleChipDefaults import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonState import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs -import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog -import com.android.permissioncontroller.permission.ui.wear.elements.ListFooter import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen -import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip -import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl -import com.android.permissioncontroller.permission.ui.wear.elements.toggleChipDisabledColors +import com.android.permissioncontroller.permission.ui.wear.elements.material2.ListFooter +import com.android.permissioncontroller.permission.ui.wear.elements.material2.ToggleChip +import com.android.permissioncontroller.permission.ui.wear.elements.material2.toggleChipDisabledColors +import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionConfirmationDialog +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType +import com.android.permissioncontroller.permission.ui.wear.elements.material3.defaultAlertConfirmIcon +import com.android.permissioncontroller.permission.ui.wear.elements.material3.defaultAlertDismissIcon import com.android.permissioncontroller.permission.ui.wear.model.AppPermissionConfirmDialogViewModel import com.android.permissioncontroller.permission.ui.wear.model.ConfirmDialogArgs +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper +import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion import com.android.settingslib.RestrictedLockUtils @Composable @@ -53,8 +58,9 @@ fun WearAppPermissionScreen( onConfirmDialogCancelButtonClick: () -> Unit, onAdvancedConfirmDialogOkButtonClick: (AdvancedConfirmDialogArgs) -> Unit, onAdvancedConfirmDialogCancelButtonClick: () -> Unit, - onDisabledAllowButtonClick: () -> Unit + onDisabledAllowButtonClick: () -> Unit, ) { + val materialUIVersion = ResourceHelper.materialUIVersionInSettings val buttonState = viewModel.buttonStateLiveData.observeAsState(null) val detailResIds = viewModel.detailResIdLiveData.observeAsState(null) val admin = viewModel.showAdminSupportLiveData.observeAsState(null) @@ -73,19 +79,21 @@ fun WearAppPermissionScreen( onLocationSwitchChanged, onGrantedStateChanged, onFooterClicked, - onDisabledAllowButtonClick + onDisabledAllowButtonClick, ) ConfirmDialog( + materialUIVersion = materialUIVersion, showDialog = showConfirmDialog.value, args = confirmDialogViewModel.confirmDialogArgs, onOkButtonClick = onConfirmDialogOkButtonClick, - onCancelButtonClick = onConfirmDialogCancelButtonClick + onCancelButtonClick = onConfirmDialogCancelButtonClick, ) AdvancedConfirmDialog( + materialUIVersion = materialUIVersion, showDialog = showAdvancedConfirmDialog.value, args = confirmDialogViewModel.advancedConfirmDialogArgs, onOkButtonClick = onAdvancedConfirmDialogOkButtonClick, - onCancelButtonClick = onAdvancedConfirmDialogCancelButtonClick + onCancelButtonClick = onAdvancedConfirmDialogCancelButtonClick, ) } if (isLoading && !buttonState.value.isNullOrEmpty()) { @@ -103,7 +111,7 @@ internal fun WearAppPermissionContent( onLocationSwitchChanged: (Boolean) -> Unit, onGrantedStateChanged: (ButtonType, Boolean) -> Unit, onFooterClicked: (RestrictedLockUtils.EnforcedAdmin) -> Unit, - onDisabledAllowButtonClick: () -> Unit + onDisabledAllowButtonClick: () -> Unit, ) { ScrollableScreen(title = title, isLoading = isLoading) { buttonState?.get(ButtonType.LOCATION_ACCURACY)?.let { @@ -113,9 +121,9 @@ internal fun WearAppPermissionContent( checked = it.isChecked, enabled = it.isEnabled, label = stringResource(R.string.app_permission_location_accuracy), - toggleControl = ToggleChipToggleControl.Switch, + toggleControl = WearPermissionToggleControlType.Switch, onCheckedChanged = onLocationSwitchChanged, - labelMaxLine = Integer.MAX_VALUE + labelMaxLine = Integer.MAX_VALUE, ) } } @@ -133,7 +141,7 @@ internal fun WearAppPermissionContent( toggleChipDisabledColors() }, label = labelsByButton(buttonType), - toggleControl = ToggleChipToggleControl.Radio, + toggleControl = WearPermissionToggleControlType.Radio, onCheckedChanged = { checked -> if (it.isEnabled) { onGrantedStateChanged(buttonType, checked) @@ -141,7 +149,7 @@ internal fun WearAppPermissionContent( onDisabledAllowButtonClick() } }, - labelMaxLine = Integer.MAX_VALUE + labelMaxLine = Integer.MAX_VALUE, ) } } @@ -157,7 +165,7 @@ internal fun WearAppPermissionContent( { onFooterClicked(admin) } } else { null - } + }, ) } } @@ -172,7 +180,7 @@ internal val buttonTypeOrder = ButtonType.ASK_ONCE, ButtonType.ASK, ButtonType.DENY, - ButtonType.DENY_FOREGROUND + ButtonType.DENY_FOREGROUND, ) @Composable @@ -191,45 +199,60 @@ internal fun labelsByButton(buttonType: ButtonType) = @Composable internal fun ConfirmDialog( + materialUIVersion: WearPermissionMaterialUIVersion, showDialog: Boolean, args: ConfirmDialogArgs?, onOkButtonClick: (ConfirmDialogArgs) -> Unit, - onCancelButtonClick: () -> Unit + onCancelButtonClick: () -> Unit, ) { - args?.let { - AlertDialog( - showDialog = showDialog, - message = stringResource(it.messageId), - onOKButtonClick = { onOkButtonClick(it) }, - onCancelButtonClick = onCancelButtonClick, - scalingLazyListState = rememberScalingLazyListState() + args?.run { + WearPermissionConfirmationDialog( + materialUIVersion = materialUIVersion, + show = showDialog, + message = stringResource(messageId), + positiveButtonContent = DialogButtonContent(onClick = { onOkButtonClick(this) }), + negativeButtonContent = DialogButtonContent(onClick = { onCancelButtonClick() }), ) } } @Composable internal fun AdvancedConfirmDialog( + materialUIVersion: WearPermissionMaterialUIVersion, showDialog: Boolean, args: AdvancedConfirmDialogArgs?, onOkButtonClick: (AdvancedConfirmDialogArgs) -> Unit, - onCancelButtonClick: () -> Unit + onCancelButtonClick: () -> Unit, ) { - args?.let { - AlertDialog( - showDialog = showDialog, - title = - if (it.titleId != 0) { - stringResource(it.titleId) - } else { - "" - }, - iconRes = it.iconId, - message = stringResource(it.messageId), - okButtonContentDescription = stringResource(it.positiveButtonTextId), - cancelButtonContentDescription = stringResource(it.negativeButtonTextId), - onOKButtonClick = { onOkButtonClick(it) }, - onCancelButtonClick = onCancelButtonClick, - scalingLazyListState = rememberScalingLazyListState() + args?.run { + val title = + if (titleId != 0) { + stringResource(titleId) + } else { + "" + } + val okButtonIconBuilder = + WearPermissionIconBuilder.defaultAlertConfirmIcon() + .contentDescription(stringResource(positiveButtonTextId)) + val cancelButtonIconBuilder = + WearPermissionIconBuilder.defaultAlertDismissIcon() + .contentDescription(stringResource(negativeButtonTextId)) + WearPermissionConfirmationDialog( + materialUIVersion = materialUIVersion, + show = showDialog, + title = title, + iconRes = WearPermissionIconBuilder.builder(iconId), + message = stringResource(messageId), + positiveButtonContent = + DialogButtonContent( + icon = okButtonIconBuilder, + onClick = { onOkButtonClick(this) }, + ), + negativeButtonContent = + DialogButtonContent( + icon = cancelButtonIconBuilder, + onClick = { onCancelButtonClick() }, + ), ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt index 1c31ec96f..ab19a9665 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearEnhancedConfirmationScreen.kt @@ -33,24 +33,26 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.fragment.app.FragmentActivity import androidx.wear.compose.foundation.SwipeToDismissValue -import androidx.wear.compose.foundation.lazy.ScalingLazyListState import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState import androidx.wear.compose.material.ChipDefaults import androidx.wear.compose.material.CircularProgressIndicator import androidx.wear.compose.material.MaterialTheme import androidx.wear.compose.material.SwipeToDismissBox import com.android.permissioncontroller.R -import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog import com.android.permissioncontroller.permission.ui.wear.elements.CheckYourPhoneScreen import com.android.permissioncontroller.permission.ui.wear.elements.CheckYourPhoneState import com.android.permissioncontroller.permission.ui.wear.elements.CheckYourPhoneState.InProgress import com.android.permissioncontroller.permission.ui.wear.elements.CheckYourPhoneState.Success -import com.android.permissioncontroller.permission.ui.wear.elements.Chip import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen import com.android.permissioncontroller.permission.ui.wear.elements.dismiss import com.android.permissioncontroller.permission.ui.wear.elements.findActivity +import com.android.permissioncontroller.permission.ui.wear.elements.material2.Chip +import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionConfirmationDialog +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder import com.android.permissioncontroller.permission.ui.wear.model.WearEnhancedConfirmationViewModel import com.android.permissioncontroller.permission.ui.wear.model.WearEnhancedConfirmationViewModel.ScreenState +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper @Composable fun WearEnhancedConfirmationScreen( @@ -58,6 +60,7 @@ fun WearEnhancedConfirmationScreen( title: String?, message: CharSequence?, ) { + val materialUIVersion = ResourceHelper.materialUIVersionInSettings var dismissed by remember { mutableStateOf(false) } val context = LocalContext.current val ecmScreenState = remember { viewModel.screenState } @@ -97,7 +100,7 @@ fun WearEnhancedConfirmationScreen( onClick = { dismiss(activity) }, modifier = Modifier.fillMaxWidth(), textColor = MaterialTheme.colors.surface, - colors = ChipDefaults.primaryChipColors() + colors = ChipDefaults.primaryChipColors(), ) } item { @@ -107,27 +110,30 @@ fun WearEnhancedConfirmationScreen( modifier = Modifier.fillMaxWidth(), ) } - } + }, ) @Composable fun ShowCheckYourPhoneDialog(state: CheckYourPhoneState) = CheckYourPhoneScreen( title = stringResource(id = R.string.wear_check_your_phone_title), - state = state + state = state, ) @Composable fun ShowRemoteConnectionErrorDialog() = - AlertDialog( + WearPermissionConfirmationDialog( + materialUIVersion = materialUIVersion, + show = true, title = stringResource(R.string.wear_phone_connection_error), message = stringResource(R.string.wear_phone_connection_should_retry), - iconRes = R.drawable.ic_error, - showDialog = true, - okButtonIcon = R.drawable.ic_refresh, - onOKButtonClick = { viewModel.openUriOnPhone(context) }, - onCancelButtonClick = { dismiss(activity) }, - scalingLazyListState = ScalingLazyListState(1) + iconRes = WearPermissionIconBuilder.builder(R.drawable.ic_error), + positiveButtonContent = + DialogButtonContent( + icon = WearPermissionIconBuilder.builder(R.drawable.ic_refresh), + onClick = { viewModel.openUriOnPhone(context) }, + ), + negativeButtonContent = DialogButtonContent(onClick = { dismiss(activity) }), ) SwipeToDismissBox(state = state) { isBackground -> diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt index 1498b91b6..8287c5e94 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearGrantPermissionsScreen.kt @@ -18,10 +18,14 @@ package com.android.permissioncontroller.permission.ui.wear import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.res.stringResource +import androidx.wear.compose.material3.Dialog import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_ALWAYS_BUTTON import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.ALLOW_BUTTON @@ -38,13 +42,13 @@ import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.N import com.android.permissioncontroller.permission.ui.GrantPermissionsActivity.NO_UPGRADE_OT_BUTTON import com.android.permissioncontroller.permission.ui.wear.GrantPermissionsWearViewHandler.BUTTON_RES_ID_TO_NUM import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen -import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType import com.android.permissioncontroller.permission.ui.wear.model.WearGrantPermissionsViewModel import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper -import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5 import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3 +import kotlinx.coroutines.delay @Composable fun WearGrantPermissionsScreen( @@ -58,13 +62,7 @@ fun WearGrantPermissionsScreen( val locationVisibilities = viewModel.locationVisibilitiesLiveData.observeAsState(emptyList()) val preciseLocationChecked = viewModel.preciseLocationCheckedLiveData.observeAsState(false) val buttonVisibilities = viewModel.buttonVisibilitiesLiveData.observeAsState(emptyList()) - val materialUIVersion = - if (ResourceHelper.material3Enabled) { - MATERIAL3 - } else { - MATERIAL2_5 - } - + val materialUIVersion = ResourceHelper.materialUIVersionInApp ScrollableScreen( materialUIVersion = materialUIVersion, showTimeText = false, @@ -83,7 +81,7 @@ fun WearGrantPermissionsScreen( checked = preciseLocationChecked.value, onCheckedChanged = onLocationSwitchChanged, label = stringResource(R.string.app_permission_location_accuracy), - toggleControl = ToggleChipToggleControl.Switch, + toggleControl = WearPermissionToggleControlType.Switch, modifier = Modifier.fillMaxWidth(), labelMaxLines = Integer.MAX_VALUE, materialUIVersion = materialUIVersion, @@ -119,12 +117,29 @@ fun WearGrantPermissionsScreen( fun setContent( composeView: ComposeView, viewModel: WearGrantPermissionsViewModel, + onCancelled: () -> Unit, onButtonClicked: (Int) -> Unit, onLocationSwitchChanged: (Boolean) -> Unit, ) { composeView.setContent { - WearGrantPermissionsScreen(viewModel, onButtonClicked, onLocationSwitchChanged) + if (ResourceHelper.materialUIVersionInApp == MATERIAL3) { + AsDialog(onCancelled) { + WearGrantPermissionsScreen(viewModel, onButtonClicked, onLocationSwitchChanged) + } + } else { + WearGrantPermissionsScreen(viewModel, onButtonClicked, onLocationSwitchChanged) + } + } +} + +@Composable +private fun AsDialog(onDismissRequest: () -> Unit, content: @Composable () -> Unit) { + val showDialog = remember { mutableStateOf(false) } + LaunchedEffect(Unit) { + delay(300) + showDialog.value = true } + Dialog(show = showDialog.value, onDismissRequest = onDismissRequest, content = content) } @Composable diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionScreen.kt index 1563f6a57..d826e501e 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageCustomPermissionScreen.kt @@ -25,13 +25,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.res.stringResource import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.ui.model.ManageCustomPermissionsViewModel -import com.android.permissioncontroller.permission.ui.wear.elements.Chip import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder @Composable fun WearManageCustomPermissionScreen( viewModel: ManageCustomPermissionsViewModel, - onPermGroupClick: (String) -> Unit + onPermGroupClick: (String) -> Unit, ) { val permissionGroups = viewModel.uiDataLiveData.observeAsState(emptyMap()) var isLoading by remember { mutableStateOf(true) } @@ -39,7 +40,7 @@ fun WearManageCustomPermissionScreen( WearManageCustomPermissionContent( isLoading, getPermGroupChipParams(permissionGroups.value), - onPermGroupClick + onPermGroupClick, ) if (isLoading && permissionGroups.value.isNotEmpty()) { @@ -51,21 +52,21 @@ fun WearManageCustomPermissionScreen( internal fun WearManageCustomPermissionContent( isLoading: Boolean, permGroupChipParams: List<PermGroupChipParam>, - onPermGroupClick: (String) -> Unit + onPermGroupClick: (String) -> Unit, ) { ScrollableScreen( title = stringResource(R.string.additional_permissions), - isLoading = isLoading + isLoading = isLoading, ) { for (params in permGroupChipParams) { item { - Chip( + WearPermissionButton( label = params.label, labelMaxLines = 3, - icon = params.icon, + iconBuilder = params.icon?.let { WearPermissionIconBuilder.builder(it) }, secondaryLabel = params.secondaryLabel, secondaryLabelMaxLines = 3, - onClick = { onPermGroupClick(params.permGroupName) } + onClick = { onPermGroupClick(params.permGroupName) }, ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt index 9aacd65d3..3e347e19c 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearManageStandardPermissionScreen.kt @@ -29,8 +29,9 @@ import androidx.compose.ui.res.stringResource import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.model.livedatatypes.PermGroupPackagesUiInfo import com.android.permissioncontroller.permission.ui.model.ManageStandardPermissionsViewModel -import com.android.permissioncontroller.permission.ui.wear.elements.Chip import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupIcon import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel import com.android.permissioncontroller.permission.utils.StringUtils @@ -42,7 +43,7 @@ fun WearManageStandardPermissionScreen( viewModel: ManageStandardPermissionsViewModel, onPermGroupClick: (String) -> Unit, onCustomPermissionsClick: () -> Unit, - onAutoRevokedClick: () -> Unit + onAutoRevokedClick: () -> Unit, ) { val permissionGroups = viewModel.uiDataLiveData.observeAsState(emptyMap()) val numCustomPermGroups = viewModel.numCustomPermGroups.observeAsState(0) @@ -56,7 +57,7 @@ fun WearManageStandardPermissionScreen( numAutoRevoked.value, onPermGroupClick, onCustomPermissionsClick, - onAutoRevokedClick + onAutoRevokedClick, ) if (isLoading && permissionGroups.value.isNotEmpty()) { @@ -92,7 +93,7 @@ internal fun getPermGroupChipParams( label = getPermGroupLabel(context, it.key).toString(), icon = getPermGroupIcon(context, it.key), secondaryLabel = - stringResource(summary, uiInfo.nonSystemGranted, uiInfo.nonSystemTotal) + stringResource(summary, uiInfo.nonSystemGranted, uiInfo.nonSystemTotal), ) } .sortedWith { lhs, rhs -> collator.compare(lhs.label, rhs.label) } @@ -107,52 +108,52 @@ internal fun WearManageStandardPermissionContent( numAutoRevoked: Int, onPermGroupClick: (String) -> Unit, onCustomPermissionsClick: () -> Unit, - onAutoRevokedClick: () -> Unit + onAutoRevokedClick: () -> Unit, ) { ScrollableScreen( title = stringResource(R.string.app_permission_manager), - isLoading = isLoading + isLoading = isLoading, ) { for (params in permGroupChipParams) { item { - Chip( + WearPermissionButton( label = params.label, labelMaxLines = 3, - icon = params.icon, + iconBuilder = params.icon?.let { WearPermissionIconBuilder.builder(it) }, secondaryLabel = params.secondaryLabel, secondaryLabelMaxLines = 3, - onClick = { onPermGroupClick(params.permGroupName) } + onClick = { onPermGroupClick(params.permGroupName) }, ) } } if (numCustomPermGroups > 0) { item { - Chip( + WearPermissionButton( label = stringResource(R.string.additional_permissions), labelMaxLines = 3, - icon = R.drawable.ic_more_horizontal, + iconBuilder = WearPermissionIconBuilder.builder(R.drawable.ic_more_horizontal), secondaryLabel = StringUtils.getIcuPluralsString( LocalContext.current, R.string.additional_permissions_more, - numCustomPermGroups + numCustomPermGroups, ), secondaryLabelMaxLines = 3, - onClick = onCustomPermissionsClick + onClick = onCustomPermissionsClick, ) } } if (numAutoRevoked > 0) { item { - Chip( + WearPermissionButton( label = stringResource(R.string.auto_revoke_permission_notification_title), labelMaxLines = 3, - icon = R.drawable.ic_info, + iconBuilder = WearPermissionIconBuilder.builder(R.drawable.ic_info), secondaryLabel = stringResource(R.string.auto_revoke_setting_subtitle), secondaryLabelMaxLines = 3, - onClick = onAutoRevokedClick + onClick = onAutoRevokedClick, ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt index 8e779cb8c..2fa6aa7f3 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionAppsScreen.kt @@ -19,7 +19,6 @@ package com.android.permissioncontroller.permission.ui.wear import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState @@ -28,13 +27,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import androidx.wear.compose.material.Text import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.ui.Category -import com.android.permissioncontroller.permission.ui.wear.elements.Chip -import com.android.permissioncontroller.permission.ui.wear.elements.ListSubheader import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionListSubHeader /** Compose the screen associated to a [WearPermissionAppsFragment]. */ @Composable @@ -46,6 +45,10 @@ fun WearPermissionAppsScreen(helper: WearPermissionAppsHelper) { helper.locationProviderDialogViewModel.dialogVisibilityLiveData.observeAsState(false) val appPermissionUsages = helper.wearViewModel.appPermissionUsages.observeAsState(emptyList()) var isLoading by remember { mutableStateOf(true) } + val dialogArgs = + helper.locationProviderDialogViewModel.locationProviderInterceptDialogArgs.observeAsState( + null + ) val title = helper.getTitle() val subTitle = helper.getSubTitle() @@ -53,21 +56,21 @@ fun WearPermissionAppsScreen(helper: WearPermissionAppsHelper) { val chipsByCategory = helper.getChipsByCategory(categorizedApps.value, appPermissionUsages.value) Box(modifier = Modifier.fillMaxSize()) { - val dialogArgs = helper.locationProviderDialogViewModel.locationProviderInterceptDialogArgs - if (showLocationProviderDialog.value && dialogArgs != null) { - LocationProviderDialogScreen(dialogArgs) - } else { - WearPermissionAppsContent( - chipsByCategory = chipsByCategory, - showSystem = showSystem.value, - hasSystemApps = hasSystemApps.value, - title = title, - subtitle = subTitle, - showAlways = showAlways, - isLoading = isLoading, - onShowSystemClick = helper.onShowSystemClick - ) - } + WearPermissionAppsContent( + chipsByCategory = chipsByCategory, + showSystem = showSystem.value, + hasSystemApps = hasSystemApps.value, + title = title, + subtitle = subTitle, + showAlways = showAlways, + isLoading = isLoading, + onShowSystemClick = helper.onShowSystemClick, + ) + LocationProviderDialogScreen( + showDialog = showLocationProviderDialog.value, + onDismissRequest = { helper.locationProviderDialogViewModel.dismissDialog() }, + args = dialogArgs.value, + ) } if (isLoading && categorizedApps.value.isNotEmpty()) { isLoading = false @@ -84,7 +87,7 @@ internal fun WearPermissionAppsContent( subtitle: String, showAlways: Boolean, isLoading: Boolean, - onShowSystemClick: (showSystem: Boolean) -> Unit + onShowSystemClick: (showSystem: Boolean) -> Unit, ) { ScrollableScreen(title = title, subtitle = subtitle, isLoading = isLoading) { val firstItemIndex = categoryOrder.indexOfFirst { !chipsByCategory[it].isNullOrEmpty() } @@ -94,29 +97,22 @@ internal fun WearPermissionAppsContent( continue } item { - ListSubheader( - modifier = - Modifier.padding( - top = if (index == firstItemIndex) 0.dp else 12.dp, - bottom = 4.dp, - start = 14.dp, - end = 14.dp - ) - ) { + WearPermissionListSubHeader(isFirstItemInAList = index == firstItemIndex) { Text(text = stringResource(getCategoryString(category, showAlways))) } } chips.forEach { item { - Chip( + WearPermissionButton( label = it.title, labelMaxLines = Int.MAX_VALUE, secondaryLabel = it.summary, secondaryLabelMaxLines = Int.MAX_VALUE, - icon = it.icon, + iconBuilder = + it.icon?.let { icon -> WearPermissionIconBuilder.builder(icon) }, enabled = it.enabled, onClick = { it.onClick() }, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), ) } } @@ -124,7 +120,7 @@ internal fun WearPermissionAppsContent( if (hasSystemApps) { item { - Chip( + WearPermissionButton( label = if (showSystem) { stringResource(R.string.menu_hide_system) @@ -150,6 +146,7 @@ internal fun getCategoryString(category: String, showAlways: Boolean) = } else { R.string.allowed_header } + Category.ALLOWED_FOREGROUND.categoryName -> R.string.allowed_foreground_header Category.ASK.categoryName -> R.string.ask_header Category.DENIED.categoryName -> R.string.denied_header @@ -163,5 +160,5 @@ internal val categoryOrder = Category.ALLOWED.categoryName, Category.ALLOWED_FOREGROUND.categoryName, Category.ASK.categoryName, - Category.DENIED.categoryName + Category.DENIED.categoryName, ) diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt index 1259c1ab5..63a6cd5a5 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageDetailsScreen.kt @@ -37,15 +37,15 @@ import com.android.permissioncontroller.permission.ui.model.v31.BasePermissionUs import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.AppPermissionAccessUiInfo import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel.PermissionUsageDetailsUiState -import com.android.permissioncontroller.permission.ui.wear.elements.Chip import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen +import com.android.permissioncontroller.permission.ui.wear.elements.material2.Chip import com.android.permissioncontroller.permission.utils.KotlinUtils @RequiresApi(Build.VERSION_CODES.S) @Composable fun WearPermissionUsageDetailsScreen( permissionGroup: String, - viewModel: BasePermissionUsageDetailsViewModel + viewModel: BasePermissionUsageDetailsViewModel, ) { val context = LocalContext.current val uiData = viewModel.getPermissionUsagesDetailsInfoUiLiveData().observeAsState(null) @@ -56,7 +56,7 @@ fun WearPermissionUsageDetailsScreen( val subtitle = stringResource( R.string.permission_group_usage_title, - KotlinUtils.getPermGroupLabel(context, permissionGroup) + KotlinUtils.getPermGroupLabel(context, permissionGroup), ) val hasSystemApps: Boolean = @@ -80,7 +80,7 @@ fun WearPermissionUsageDetailsScreen( uiInfo.accessStartTime, uiInfo.accessEndTime, uiInfo.showingAttribution, - uiInfo.attributionTags + uiInfo.attributionTags, ) context.startActivityAsUser(intent, uiInfo.userHandle) } @@ -108,7 +108,7 @@ fun WearPermissionUsageDetailsScreen( onShowSystemClick, appPermissionAccessUiInfoList, onChipClick, - onManagePermissionClick + onManagePermissionClick, ) if (isLoading && uiData.value != null) { @@ -126,7 +126,7 @@ internal fun WearPermissionUsageDetailsContent( onShowSystemClick: (Boolean) -> Unit, appPermissionAccessUiInfoList: List<AppPermissionAccessUiInfo>, onChipClick: (AppPermissionAccessUiInfo) -> Unit, - onManagePermissionClick: () -> Unit + onManagePermissionClick: () -> Unit, ) { ScrollableScreen(title = title, subtitle = subtitle, isLoading = isLoading) { if (appPermissionAccessUiInfoList.isEmpty()) { @@ -142,7 +142,7 @@ internal fun WearPermissionUsageDetailsContent( .format(uiInfo.accessEndTime), secondaryLabelMaxLines = Int.MAX_VALUE, icon = uiInfo.badgedPackageIcon, - onClick = { onChipClick(uiInfo) } + onClick = { onChipClick(uiInfo) }, ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt index f83d3338d..20e0dd69b 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearPermissionUsageScreen.kt @@ -32,17 +32,14 @@ import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.ui.handheld.v31.PermissionUsageControlPreference import com.android.permissioncontroller.permission.ui.viewmodel.v31.PermissionUsageViewModel import com.android.permissioncontroller.permission.ui.viewmodel.v31.PermissionUsagesUiState -import com.android.permissioncontroller.permission.ui.wear.elements.Chip import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen +import com.android.permissioncontroller.permission.ui.wear.elements.material2.Chip import com.android.permissioncontroller.permission.utils.Utils import java.text.Collator @RequiresApi(Build.VERSION_CODES.S) @Composable -fun WearPermissionUsageScreen( - sessionId: Long, - viewModel: PermissionUsageViewModel, -) { +fun WearPermissionUsageScreen(sessionId: Long, viewModel: PermissionUsageViewModel) { val context = LocalContext.current val permissionUsagesUiData = viewModel.permissionUsagesUiLiveData.observeAsState(null) val showSystem = viewModel.showSystemAppsLiveData.observeAsState(false) @@ -97,7 +94,7 @@ fun WearPermissionUsageScreen( hasSystemApps, showSystem.value, onShowSystemClick, - permissionGroupPreferences + permissionGroupPreferences, ) if (isLoading && isDataLoaded) { @@ -111,11 +108,11 @@ internal fun WearPermissionUsageContent( hasSystemApps: Boolean, showSystem: Boolean, onShowSystemClick: (Boolean) -> Unit, - permissionGroupPreferences: List<PermissionUsageControlPreference> + permissionGroupPreferences: List<PermissionUsageControlPreference>, ) { ScrollableScreen( title = stringResource(R.string.permission_usage_title), - isLoading = isLoading + isLoading = isLoading, ) { if (permissionGroupPreferences.isEmpty()) { item { Chip(label = stringResource(R.string.no_permissions), onClick = {}) } @@ -129,7 +126,7 @@ internal fun WearPermissionUsageContent( secondaryLabelMaxLines = Int.MAX_VALUE, icon = preference.icon, enabled = preference.isEnabled, - onClick = { preference.performClick() } + onClick = { preference.performClick() }, ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt index 423fa7759..9170b7d20 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/WearUnusedAppsScreen.kt @@ -25,9 +25,9 @@ import com.android.permissioncontroller.R import com.android.permissioncontroller.hibernation.isHibernationEnabled import com.android.permissioncontroller.permission.ui.model.UnusedAppsViewModel.UnusedPeriod import com.android.permissioncontroller.permission.ui.model.UnusedAppsViewModel.UnusedPeriod.Companion.allPeriods -import com.android.permissioncontroller.permission.ui.wear.elements.Chip -import com.android.permissioncontroller.permission.ui.wear.elements.Icon import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen +import com.android.permissioncontroller.permission.ui.wear.elements.material2.Chip +import com.android.permissioncontroller.permission.ui.wear.elements.material2.Icon import com.android.permissioncontroller.permission.ui.wear.model.WearUnusedAppsViewModel @Composable @@ -43,7 +43,7 @@ fun WearUnusedAppsScreen(viewModel: WearUnusedAppsViewModel) { showTimeText = true, title = getScreenTitle(), isLoading = loading.value, - subtitle = getSubTitle(!infoMsgCategoryVisibility.value) + subtitle = getSubTitle(!infoMsgCategoryVisibility.value), ) { for (period in allPeriods) { if (!unusedAppChips.value.containsKey(period)) { @@ -62,7 +62,7 @@ fun WearUnusedAppsScreen(viewModel: WearUnusedAppsViewModel) { secondaryLabel = unusedAppChip.summary, icon = unusedAppChip.icon, iconContentDescription = unusedAppChip.contentDescription, - onClick = unusedAppChip.onClick + onClick = unusedAppChip.onClick, ) } } @@ -108,5 +108,5 @@ private fun posByPeriod(period: UnusedPeriod) = private fun categoryTitleByPeriod(period: UnusedPeriod) = MessageFormat.format( stringResource(R.string.last_opened_category_title), - mapOf("count" to period.months) + mapOf("count" to period.months), ) diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt index 6ce7df125..bfa46ae55 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ScrollableScreen.kt @@ -19,56 +19,20 @@ package com.android.permissioncontroller.permission.ui.wear.elements import android.app.Activity import android.content.Context import android.content.ContextWrapper -import android.graphics.drawable.Drawable -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import androidx.fragment.app.FragmentActivity -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.lifecycle.repeatOnLifecycle import androidx.wear.compose.foundation.SwipeToDismissValue -import androidx.wear.compose.foundation.lazy.ScalingLazyColumn -import androidx.wear.compose.foundation.lazy.ScalingLazyListScope -import androidx.wear.compose.foundation.lazy.ScalingLazyListState import androidx.wear.compose.foundation.rememberSwipeToDismissBoxState -import androidx.wear.compose.material.CircularProgressIndicator -import androidx.wear.compose.material.MaterialTheme -import androidx.wear.compose.material.PositionIndicator -import androidx.wear.compose.material.Scaffold import androidx.wear.compose.material.SwipeToDismissBox -import androidx.wear.compose.material.Text -import androidx.wear.compose.material.TimeText -import androidx.wear.compose.material.Vignette -import androidx.wear.compose.material.VignettePosition -import androidx.wear.compose.material.scrollAway import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionScaffold -import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rotaryWithScroll +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion -import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5 -import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme /** * Screen that contains a list of items defined using the [content] parameter, adds the time text @@ -78,7 +42,7 @@ import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionT */ @Composable fun ScrollableScreen( - materialUIVersion: WearPermissionMaterialUIVersion = MATERIAL2_5, + materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings, showTimeText: Boolean = true, title: String? = null, subtitle: CharSequence? = null, @@ -86,7 +50,7 @@ fun ScrollableScreen( isLoading: Boolean = false, titleTestTag: String? = null, subtitleTestTag: String? = null, - content: ScalingLazyListScope.() -> Unit, + content: ListScopeWrapper.() -> Unit, ) { var dismissed by remember { mutableStateOf(false) } val activity = LocalContext.current.findActivity() @@ -135,207 +99,6 @@ fun ScrollableScreen( } } -@Composable -internal fun Wear2Scaffold( - showTimeText: Boolean, - title: String?, - subtitle: CharSequence?, - image: Any?, - isLoading: Boolean, - content: ScalingLazyListScope.() -> Unit, - titleTestTag: String? = null, - subtitleTestTag: String? = null, -) { - val itemsSpacedBy = 4.dp - val screenWidth = LocalConfiguration.current.screenWidthDp - val screenHeight = LocalConfiguration.current.screenHeightDp - val scrollContentHorizontalPadding = (screenWidth * 0.052).dp - val titleHorizontalPadding = (screenWidth * 0.0884).dp - val subtitleHorizontalPadding = (screenWidth * 0.0416).dp - val scrollContentTopPadding = (screenHeight * 0.1456).dp - itemsSpacedBy - val scrollContentBottomPadding = (screenHeight * 0.3636).dp - val titleBottomPadding = - if (subtitle == null) { - 8.dp - } else { - 4.dp - } - val subtitleBottomPadding = 8.dp - val timeTextTopPadding = - if (showTimeText) { - 1.dp - } else { - 0.dp - } - val titlePaddingValues = - PaddingValues( - start = titleHorizontalPadding, - top = 4.dp, - bottom = titleBottomPadding, - end = titleHorizontalPadding, - ) - val subTitlePaddingValues = - PaddingValues( - start = subtitleHorizontalPadding, - top = 4.dp, - bottom = subtitleBottomPadding, - end = subtitleHorizontalPadding, - ) - val initialCenterIndex = 0 - val centerHeightDp = Dp(LocalConfiguration.current.screenHeightDp / 2.0f) - // We are adding TimeText's padding to create a smooth scrolling - val initialCenterItemScrollOffset = scrollContentTopPadding + timeTextTopPadding - val scrollAwayOffset = centerHeightDp - initialCenterItemScrollOffset - val focusRequester = remember { FocusRequester() } - val listState = remember { ScalingLazyListState(initialCenterItemIndex = initialCenterIndex) } - LaunchedEffect(title) { - listState.animateScrollToItem(index = 0) // Scroll to the top when triggerValue changes - } - WearPermissionTheme { - Scaffold( - // TODO: Use a rotary modifier from Wear Compose once Wear Compose 1.4 is landed. - // (b/325560444) - modifier = - Modifier.rotaryWithScroll( - scrollableState = listState, - focusRequester = focusRequester, - ), - timeText = { - if (showTimeText && !isLoading) { - TimeText( - modifier = - Modifier.scrollAway(listState, initialCenterIndex, scrollAwayOffset) - .padding(top = timeTextTopPadding) - ) - } - }, - vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) }, - positionIndicator = - if (!isLoading) { - { PositionIndicator(scalingLazyListState = listState) } - } else { - null - }, - ) { - Box(modifier = Modifier.fillMaxSize()) { - if (isLoading) { - CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) - } else { - val iconColor = chipDefaultColors().iconColor(true).value - ScalingLazyColumn( - modifier = Modifier.fillMaxWidth(), - state = listState, - // Set autoCentering to null to avoid adding extra padding based on the - // content. - autoCentering = null, - contentPadding = - PaddingValues( - start = scrollContentHorizontalPadding, - end = scrollContentHorizontalPadding, - top = scrollContentTopPadding, - bottom = scrollContentBottomPadding, - ), - ) { - staticItem() - image?.let { - val imageModifier = Modifier.size(24.dp) - when (image) { - is Int -> - item { - Image( - painter = painterResource(id = image), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = imageModifier, - colorFilter = ColorFilter.tint(iconColor), - ) - } - is Drawable -> - item { - Image( - painter = rememberDrawablePainter(image), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = imageModifier, - colorFilter = ColorFilter.tint(iconColor), - ) - } - else -> {} - } - } - if (title != null) { - item { - var modifier: Modifier = Modifier - if (titleTestTag != null) { - modifier = modifier.testTag(titleTestTag) - } - ListHeader(modifier = Modifier.padding(titlePaddingValues)) { - Text( - text = title, - textAlign = TextAlign.Center, - modifier = modifier, - ) - } - } - } - if (subtitle != null) { - item { - var modifier: Modifier = - Modifier.align(Alignment.Center).padding(subTitlePaddingValues) - if (subtitleTestTag != null) { - modifier = modifier.testTag(subtitleTestTag) - } - AnnotatedText( - text = subtitle, - style = - MaterialTheme.typography.body2.copy( - color = MaterialTheme.colors.onSurfaceVariant - ), - modifier = modifier, - shouldCapitalize = true, - ) - } - } - - content() - } - RequestFocusOnResume(focusRequester = focusRequester) - } - } - } - } -} - -private fun ScalingLazyListScope.staticItem() { - /* - This empty item helps to ensure accurate scroll offset calculation. If auto centering is enabled - initial item's(first item for us) center matches the center of the screen. Scroll offset is 0 at - that point. - - if auto centering is not enabled, initial item will start at the top of the screen with the - scroll offset equal to ScreenHeight/2 - scrollContentTopPadding - firstItemHeight/2. - - We need to this offset value to properly move time text.That is the scroll-away offset of the - Time Text is equal to the scroll offset of the list at initial position. - - It is easier to calculate if we know the values of ScreenHeight, ScrollContentTopPadding and - FirstItem's height. ScreenHeight and ScrollContentPadding are constants but height of the - FirstItem depends on the content. Instead of measuring the height, we can simplify the - calculation with an empty item with 0dp height. - */ - item {} -} - -@Composable -private fun RequestFocusOnResume(focusRequester: FocusRequester) { - val lifecycleOwner = LocalLifecycleOwner.current - LaunchedEffect(Unit) { - lifecycleOwner.repeatOnLifecycle(state = Lifecycle.State.RESUMED) { - focusRequester.requestFocus() - } - } -} - internal fun dismiss(activity: Activity) { if (activity is FragmentActivity) { if (!activity.supportFragmentManager.popBackStackImmediate()) { @@ -364,3 +127,7 @@ internal fun Context.findActivity(): Activity { } throw IllegalStateException("The screen should be called in the context of an Activity") } + +interface ListScopeWrapper { + fun item(key: Any? = null, contentType: Any? = null, content: @Composable () -> Unit) +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt deleted file mode 100644 index b6f6db4d3..000000000 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChipToggleControl.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.permissioncontroller.permission.ui.wear.elements - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.semantics.role -import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.semantics.stateDescription -import com.android.permissioncontroller.R - -enum class ToggleChipToggleControl { - Switch, - Radio, - Checkbox, -} - -@Composable -fun Modifier.toggleControlSemantics( - toggleControl: ToggleChipToggleControl, - checked: Boolean, -): Modifier { - val semanticsRole = - when (toggleControl) { - ToggleChipToggleControl.Switch -> Role.Switch - ToggleChipToggleControl.Radio -> Role.RadioButton - ToggleChipToggleControl.Checkbox -> Role.Checkbox - } - val stateDescriptionSemantics = - stringResource( - if (checked) { - R.string.on - } else { - R.string.off - } - ) - - return semantics { - role = semanticsRole - stateDescription = stateDescriptionSemantics - } -} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/AlertDialog.kt index c07d2ba9e..a49d8822e 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/AlertDialog.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/AlertDialog.kt @@ -14,20 +14,17 @@ * limitations under the License. */ -package com.android.permissioncontroller.permission.ui.wear.elements +package com.android.permissioncontroller.permission.ui.wear.elements.material2 import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.heading +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -35,15 +32,15 @@ import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.lazy.ScalingLazyListScope import androidx.wear.compose.foundation.lazy.ScalingLazyListState -import androidx.wear.compose.material.Icon import androidx.wear.compose.material.LocalTextStyle import androidx.wear.compose.material.MaterialTheme import androidx.wear.compose.material.Text -import androidx.wear.compose.material.dialog.Alert import androidx.wear.compose.material.dialog.Dialog -import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnDefaults -import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState -import com.android.permissioncontroller.permission.ui.wear.elements.layout.rememberColumnState +import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumnDefaults +import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumnState +import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.rememberColumnState +import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder /** * This component is an alternative to [AlertContent], providing the following: @@ -54,95 +51,44 @@ import com.android.permissioncontroller.permission.ui.wear.elements.layout.remem */ @Composable fun AlertDialog( + title: String? = null, message: String, - iconRes: Int? = null, - okButtonIcon: Any = Icons.Default.Check, - cancelButtonIcon: Any = Icons.Default.Close, - onCancelButtonClick: () -> Unit, - onOKButtonClick: () -> Unit, + positiveButtonContent: DialogButtonContent?, + negativeButtonContent: DialogButtonContent?, showDialog: Boolean, - scalingLazyListState: ScalingLazyListState, modifier: Modifier = Modifier, - title: String? = null, - okButtonContentDescription: String = stringResource(android.R.string.ok), - cancelButtonContentDescription: String = stringResource(android.R.string.cancel) + iconRes: WearPermissionIconBuilder? = null, + scalingLazyListState: ScalingLazyListState, ) { val focusManager = LocalFocusManager.current Dialog( showDialog = showDialog, onDismissRequest = { focusManager.clearFocus() - onCancelButtonClick() + negativeButtonContent?.onClick?.invoke() }, scrollState = scalingLazyListState, - modifier = modifier - ) { - AlertContent( - title = title, - icon = { AlertIcon(iconRes) }, - message = message, - okButtonIcon = okButtonIcon, - cancelButtonIcon = cancelButtonIcon, - onCancel = onCancelButtonClick, - onOk = onOKButtonClick, - okButtonContentDescription = okButtonContentDescription, - cancelButtonContentDescription = cancelButtonContentDescription - ) - } -} - -/** - * This component is an alternative to [Alert], providing the following: - * - a convenient way of passing a title and a message; - * - default one button; - * - wrapped in a [Dialog]; - */ -@Composable -fun SingleButtonAlertDialog( - message: String, - iconRes: Int? = null, - okButtonIcon: Any = Icons.Default.Check, - onButtonClick: () -> Unit, - showDialog: Boolean, - scalingLazyListState: ScalingLazyListState, - modifier: Modifier = Modifier, - title: String? = null, - buttonContentDescription: String = stringResource(android.R.string.ok) -) { - Dialog( - showDialog = showDialog, - onDismissRequest = {}, - scrollState = scalingLazyListState, - modifier = modifier + modifier = modifier, ) { AlertContent( title = title, - icon = { AlertIcon(iconRes) }, + icon = { iconRes?.build() }, message = message, - okButtonIcon = okButtonIcon, - onOk = onButtonClick, - okButtonContentDescription = buttonContentDescription + positiveButtonContent = positiveButtonContent, + negativeButtonContent = negativeButtonContent, ) } } @Composable fun AlertContent( - onCancel: (() -> Unit)? = null, - onOk: (() -> Unit)? = null, icon: @Composable (() -> Unit)? = null, title: String? = null, message: String? = null, - okButtonIcon: Any = Icons.Default.Check, - cancelButtonIcon: Any = Icons.Default.Close, - okButtonContentDescription: String = stringResource(android.R.string.ok), - cancelButtonContentDescription: String = stringResource(android.R.string.cancel), + positiveButtonContent: DialogButtonContent?, + negativeButtonContent: DialogButtonContent?, state: ScalingLazyColumnState = - rememberColumnState( - ScalingLazyColumnDefaults.responsive( - additionalPaddingAtBottom = 0.dp, - ), - ), + rememberColumnState(ScalingLazyColumnDefaults.responsive(additionalPaddingAtBottom = 0.dp)), showPositionIndicator: Boolean = true, content: (ScalingLazyListScope.() -> Unit)? = null, ) { @@ -155,7 +101,7 @@ fun AlertContent( title?.let { { Text( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth().semantics() { heading() }, text = it, color = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, @@ -185,7 +131,7 @@ fun AlertContent( maxWidth = (maxScreenWidthPx * (1f - totalPaddingPercentage * 2f / 100f)) - .toInt(), + .toInt() ), ) .lineCount @@ -200,21 +146,9 @@ fun AlertContent( } }, content = content, - onOk = onOk, - onCancel = onCancel, - okButtonIcon = okButtonIcon, - cancelButtonIcon = cancelButtonIcon, - okButtonContentDescription = okButtonContentDescription, - cancelButtonContentDescription = cancelButtonContentDescription, + positiveButtonContent = positiveButtonContent, + negativeButtonContent = negativeButtonContent, state = state, showPositionIndicator = showPositionIndicator, ) } - -@Composable -private fun AlertIcon(iconRes: Int?) = - if (iconRes != null && iconRes != 0) { - Icon(painter = painterResource(iconRes), contentDescription = null) - } else { - null - } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Chip.kt index 40f097c67..15542ec20 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Chip.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Chip.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.permissioncontroller.permission.ui.wear.elements +package com.android.permissioncontroller.permission.ui.wear.elements.material2 import android.graphics.drawable.Drawable import androidx.annotation.StringRes @@ -46,6 +46,7 @@ import androidx.wear.compose.material.Icon import androidx.wear.compose.material.MaterialTheme import androidx.wear.compose.material.Text import androidx.wear.compose.material.contentColorFor +import com.android.permissioncontroller.permission.ui.wear.elements.rememberDrawablePainter /** * This component is an alternative to [Chip], providing the following: @@ -67,7 +68,7 @@ fun Chip( textColor: Color = MaterialTheme.colors.onSurface, iconColor: Color = Color.Unspecified, colors: ChipColors = chipDefaultColors(), - enabled: Boolean = true + enabled: Boolean = true, ) { val iconParam: (@Composable BoxScope.() -> Unit)? = icon?.let { @@ -87,21 +88,21 @@ fun Chip( imageVector = icon, tint = iconColor, contentDescription = iconContentDescription, - modifier = iconModifier + modifier = iconModifier, ) is Int -> Icon( painter = painterResource(id = icon), tint = iconColor, contentDescription = iconContentDescription, - modifier = iconModifier + modifier = iconModifier, ) is Drawable -> Icon( painter = rememberDrawablePainter(icon), tint = iconColor, contentDescription = iconContentDescription, - modifier = iconModifier + modifier = iconModifier, ) else -> {} } @@ -120,7 +121,7 @@ fun Chip( largeIcon = largeIcon, textColor = textColor, colors = colors, - enabled = enabled + enabled = enabled, ) } @@ -143,7 +144,7 @@ fun Chip( textColor: Color = MaterialTheme.colors.onSurface, iconColor: Color = Color.Unspecified, colors: ChipColors = chipDefaultColors(), - enabled: Boolean = true + enabled: Boolean = true, ) { Chip( label = stringResource(id = labelId), @@ -157,7 +158,7 @@ fun Chip( textColor = textColor, iconColor = iconColor, colors = colors, - enabled = enabled + enabled = enabled, ) } @@ -180,7 +181,7 @@ fun Chip( textColor: Color = MaterialTheme.colors.onSurface, secondaryTextColor: Color = MaterialTheme.colors.primary, colors: ChipColors = chipDefaultColors(), - enabled: Boolean = true + enabled: Boolean = true, ) { val hasSecondaryLabel = secondaryLabel != null val hasIcon = icon != null @@ -196,8 +197,8 @@ fun Chip( style = MaterialTheme.typography.button.copy( fontWeight = FontWeight.W600, - hyphens = Hyphens.Auto - ) + hyphens = Hyphens.Auto, + ), ) } @@ -209,7 +210,7 @@ fun Chip( color = secondaryTextColor, overflow = TextOverflow.Ellipsis, maxLines = secondaryLabelMaxLines ?: 1, - style = MaterialTheme.typography.caption2 + style = MaterialTheme.typography.caption2, ) } } @@ -221,7 +222,7 @@ fun Chip( start = 10.dp, top = verticalPadding, end = ChipDefaults.ChipHorizontalPadding, - bottom = verticalPadding + bottom = verticalPadding, ) } else { ChipDefaults.ContentPadding @@ -236,7 +237,7 @@ fun Chip( colors = colors, enabled = enabled, contentPadding = contentPadding, - shape = RoundedCornerShape(26.dp) + shape = RoundedCornerShape(26.dp), ) } @@ -258,6 +259,6 @@ fun chipDisabledColors(): ChipColors { backgroundColor = backgroundColor.copy(alpha = ContentAlpha.disabled), contentColor = contentColor.copy(alpha = ContentAlpha.disabled), secondaryContentColor = secondaryContentColor.copy(alpha = ContentAlpha.disabled), - iconColor = iconColor.copy(alpha = ContentAlpha.disabled) + iconColor = iconColor.copy(alpha = ContentAlpha.disabled), ) } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Icon.kt index 1a304b37e..3cfac7eef 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/Icon.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Icon.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.permissioncontroller.permission.ui.wear.elements +package com.android.permissioncontroller.permission.ui.wear.elements.material2 import android.graphics.drawable.Drawable import androidx.annotation.DrawableRes @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.wear.compose.material.Icon import androidx.wear.compose.material.LocalContentAlpha import androidx.wear.compose.material.LocalContentColor +import com.android.permissioncontroller.permission.ui.wear.elements.rememberDrawablePainter /** * This component is an alternative to [Icon], providing the following: @@ -40,7 +41,7 @@ public fun Icon( contentDescription: String?, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), - rtlMode: IconRtlMode = IconRtlMode.Default + rtlMode: IconRtlMode = IconRtlMode.Default, ) { val shouldMirror = rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl @@ -48,7 +49,7 @@ public fun Icon( modifier = modifier.scale(scaleX = if (shouldMirror) -1f else 1f, scaleY = 1f), imageVector = imageVector, contentDescription = contentDescription, - tint = tint + tint = tint, ) } @@ -62,7 +63,7 @@ public fun Icon( contentDescription: String?, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), - rtlMode: IconRtlMode = IconRtlMode.Default + rtlMode: IconRtlMode = IconRtlMode.Default, ) { val shouldMirror = rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl @@ -71,7 +72,7 @@ public fun Icon( painter = painterResource(id = id), contentDescription = contentDescription, modifier = modifier.scale(scaleX = if (shouldMirror) -1f else 1f, scaleY = 1f), - tint = tint + tint = tint, ) } @@ -86,7 +87,7 @@ fun Icon( contentDescription: String?, modifier: Modifier = Modifier, tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current), - rtlMode: IconRtlMode = IconRtlMode.Default + rtlMode: IconRtlMode = IconRtlMode.Default, ) { val shouldMirror = rtlMode == IconRtlMode.Mirrored && LocalLayoutDirection.current == LayoutDirection.Rtl @@ -98,7 +99,7 @@ fun Icon( imageVector = icon, modifier = iconModifier, contentDescription = contentDescription, - tint = tint + tint = tint, ) } is Int -> { @@ -106,7 +107,7 @@ fun Icon( painter = painterResource(id = icon), contentDescription = contentDescription, modifier = iconModifier, - tint = tint + tint = tint, ) } is Drawable -> { @@ -114,7 +115,7 @@ fun Icon( painter = rememberDrawablePainter(icon), contentDescription = contentDescription, modifier = iconModifier, - tint = tint + tint = tint, ) } else -> throw IllegalArgumentException("Type not supported.") @@ -123,5 +124,5 @@ fun Icon( public enum class IconRtlMode { Default, - Mirrored + Mirrored, } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ListFooter.kt index 5ed912ec6..4f6d47faf 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListFooter.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ListFooter.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.permissioncontroller.permission.ui.wear.elements +package com.android.permissioncontroller.permission.ui.wear.elements.material2 import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Row @@ -52,7 +52,7 @@ fun ListFooter(description: String, iconRes: Int? = null, onClick: (() -> Unit)? contentDescription = null, modifier = Modifier.size(LeadingIconSize, LeadingIconSize) - .align(Alignment.CenterVertically) + .align(Alignment.CenterVertically), ) Spacer(modifier = Modifier.width(LeadingIconEndSpacing)) } @@ -62,7 +62,7 @@ fun ListFooter(description: String, iconRes: Int? = null, onClick: (() -> Unit)? textAlign = TextAlign.Start, overflow = TextOverflow.Ellipsis, color = MaterialTheme.colors.onSurfaceVariant, - style = MaterialTheme.typography.caption2 + style = MaterialTheme.typography.caption2, ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ListHeader.kt index 0a2a3937c..2d3eb0d52 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ListHeader.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.permissioncontroller.permission.ui.wear.elements +package com.android.permissioncontroller.permission.ui.wear.elements.material2 import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -60,7 +60,7 @@ fun ListHeader( modifier: Modifier = Modifier, backgroundColor: Color = Color.Transparent, contentColor: Color = MaterialTheme.colors.onBackground, - content: @Composable RowScope.() -> Unit + content: @Composable RowScope.() -> Unit, ) { Row( horizontalArrangement = Arrangement.Center, @@ -69,14 +69,14 @@ fun ListHeader( mergeDescendants = true ) { heading() - } + }, ) { CompositionLocalProvider( LocalContentColor provides contentColor, LocalTextStyle provides MaterialTheme.typography.title3.copy( fontWeight = FontWeight.W600, - hyphens = Hyphens.Auto + hyphens = Hyphens.Auto, ), ) { content() @@ -111,7 +111,7 @@ fun ListSubheader( .fillMaxWidth() .wrapContentSize(align = Alignment.CenterStart) .background(backgroundColor) - .semantics(mergeDescendants = true) { heading() } + .semantics(mergeDescendants = true) { heading() }, ) { CompositionLocalProvider( LocalContentColor provides contentColor, @@ -120,7 +120,7 @@ fun ListSubheader( if (icon != null) { Box( modifier = Modifier.wrapContentSize(align = Alignment.CenterStart), - content = icon + content = icon, ) Spacer(modifier = Modifier.width(6.dp)) } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ResponsiveDialog.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ResponsiveDialog.kt index e1e869f71..4cb3ab615 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ResponsiveDialog.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ResponsiveDialog.kt @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package com.android.permissioncontroller.permission.ui.wear.elements +package com.android.permissioncontroller.permission.ui.wear.elements.material2 import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement.spacedBy @@ -29,15 +28,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -49,13 +44,14 @@ import androidx.wear.compose.material.LocalTextStyle import androidx.wear.compose.material.MaterialTheme import androidx.wear.compose.material.PositionIndicator import androidx.wear.compose.material.Scaffold -import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumn -import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnDefaults.responsive -import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState -import com.android.permissioncontroller.permission.ui.wear.elements.layout.rememberColumnState - -// This file is a copy of ResponsiveDialogContent.kt from Horologist (go/horologist), -// remove it once after wear compose supports large screen dialogs. +import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumn +import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumnDefaults.responsive +import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumnState +import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.rememberColumnState +import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder +import com.android.permissioncontroller.permission.ui.wear.elements.material3.defaultAlertConfirmIcon +import com.android.permissioncontroller.permission.ui.wear.elements.material3.defaultAlertDismissIcon @Composable fun ResponsiveDialogContent( @@ -63,18 +59,11 @@ fun ResponsiveDialogContent( icon: @Composable (() -> Unit)? = null, title: @Composable (() -> Unit)? = null, message: @Composable (() -> Unit)? = null, - okButtonIcon: Any = Icons.Default.Check, - cancelButtonIcon: Any = Icons.Default.Close, - onOk: (() -> Unit)? = null, - onCancel: (() -> Unit)? = null, - okButtonContentDescription: String = stringResource(android.R.string.ok), - cancelButtonContentDescription: String = stringResource(android.R.string.cancel), + positiveButtonContent: DialogButtonContent? = null, + negativeButtonContent: DialogButtonContent? = null, state: ScalingLazyColumnState = rememberColumnState( - responsive( - firstItemIsFullWidth = icon == null, - additionalPaddingAtBottom = 0.dp, - ), + responsive(firstItemIsFullWidth = icon == null, additionalPaddingAtBottom = 0.dp) ), showPositionIndicator: Boolean = true, content: (ScalingLazyListScope.() -> Unit)? = null, @@ -89,9 +78,7 @@ fun ResponsiveDialogContent( timeText = {}, ) { // This will be applied only to the content. - CompositionLocalProvider( - LocalTextStyle provides MaterialTheme.typography.body2, - ) { + CompositionLocalProvider(LocalTextStyle provides MaterialTheme.typography.body2) { ScalingLazyColumn(columnState = state) { icon?.let { item { @@ -107,11 +94,11 @@ fun ResponsiveDialogContent( item { CompositionLocalProvider( LocalTextStyle provides - MaterialTheme.typography.title3.copy(fontWeight = FontWeight.W600), + MaterialTheme.typography.title3.copy(fontWeight = FontWeight.W600) ) { Box( Modifier.fillMaxWidth(titleMaxWidthFraction) - .padding(bottom = 8.dp), // 12.dp below icon + .padding(bottom = 8.dp) // 12.dp below icon ) { it() } @@ -123,22 +110,20 @@ fun ResponsiveDialogContent( item { Spacer(Modifier.height(20.dp)) } } message?.let { - item { - Box( - Modifier.fillMaxWidth(messageMaxWidthFraction), - ) { - it() - } - } + item { Box(Modifier.fillMaxWidth(messageMaxWidthFraction)) { it() } } } content?.let { it() } - if (onOk != null || onCancel != null) { + if (positiveButtonContent != null || negativeButtonContent != null) { item { val width = LocalConfiguration.current.screenWidthDp // Single buttons, or buttons on smaller screens are not meant to be // responsive. val buttonWidth = - if (width < 225 || onOk == null || onCancel == null) { + if ( + width < 225 || + positiveButtonContent == null || + negativeButtonContent == null + ) { ButtonDefaults.DefaultButtonSize } else { // 14.56% on top of 5.2% margin on the sides, 12.dp between. @@ -147,25 +132,30 @@ fun ResponsiveDialogContent( Row( Modifier.fillMaxWidth() .padding( - top = if (content != null || message != null) 12.dp else 0.dp, + top = if (content != null || message != null) 12.dp else 0.dp ), horizontalArrangement = spacedBy(12.dp, Alignment.CenterHorizontally), verticalAlignment = Alignment.CenterVertically, ) { - onCancel?.let { + negativeButtonContent?.run { ResponsiveButton( - icon = cancelButtonIcon, - cancelButtonContentDescription, - onClick = it, + this.icon + ?: WearPermissionIconBuilder.defaultAlertDismissIcon() + .tint( + ChipDefaults.secondaryChipColors() + .contentColor(true) + .value + ), + onClick, buttonWidth, ChipDefaults.secondaryChipColors(), ) } - onOk?.let { + positiveButtonContent?.run { ResponsiveButton( - icon = okButtonIcon, - okButtonContentDescription, - onClick = it, + this.icon + ?: WearPermissionIconBuilder.defaultAlertConfirmIcon(), + onClick, buttonWidth, ) } @@ -179,8 +169,7 @@ fun ResponsiveDialogContent( @Composable private fun ResponsiveButton( - icon: Any, - contentDescription: String, + icon: WearPermissionIconBuilder, onClick: () -> Unit, buttonWidth: Dp, colors: ChipColors = ChipDefaults.primaryChipColors(), @@ -188,12 +177,9 @@ private fun ResponsiveButton( androidx.wear.compose.material.Chip( label = { Box(Modifier.fillMaxWidth()) { - Icon( - icon = icon, - contentDescription = contentDescription, - modifier = - Modifier.size(ButtonDefaults.DefaultIconSize).align(Alignment.Center), - ) + icon + .modifier(Modifier.size(ButtonDefaults.DefaultIconSize).align(Alignment.Center)) + .build() } }, contentPadding = PaddingValues(0.dp), @@ -210,19 +196,10 @@ internal const val titleExtraHorizontalPadding = 8.84f // Fraction of the max available width that message should take (after global and message padding) internal val messageMaxWidthFraction = - 1f - - 2f * - calculatePaddingFraction( - messageExtraHorizontalPadding, - ) + 1f - 2f * calculatePaddingFraction(messageExtraHorizontalPadding) // Fraction of the max available width that title should take (after global and message padding) -internal val titleMaxWidthFraction = - 1f - - 2f * - calculatePaddingFraction( - titleExtraHorizontalPadding, - ) +internal val titleMaxWidthFraction = 1f - 2f * calculatePaddingFraction(titleExtraHorizontalPadding) // Calculate total padding given global padding and additional padding required inside that. internal fun calculatePaddingFraction(extraPadding: Float) = diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ToggleChip.kt index 2e89586c9..bfb5d114d 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ToggleChip.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/ToggleChip.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.permissioncontroller.permission.ui.wear.elements +package com.android.permissioncontroller.permission.ui.wear.elements.material2 import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.BoxScope @@ -29,6 +29,11 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.wear.compose.material.ChipDefaults @@ -39,6 +44,8 @@ import androidx.wear.compose.material.ToggleChip import androidx.wear.compose.material.ToggleChipColors import androidx.wear.compose.material.ToggleChipDefaults import androidx.wear.compose.material.contentColorFor +import com.android.permissioncontroller.R +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType /** * This component is an alternative to [ToggleChip], providing the following: @@ -52,7 +59,7 @@ fun ToggleChip( onCheckedChanged: (Boolean) -> Unit, label: String, labelMaxLine: Int? = null, - toggleControl: ToggleChipToggleControl, + toggleControl: WearPermissionToggleControlType, modifier: Modifier = Modifier, icon: Any? = null, iconColor: Color = Color.Unspecified, @@ -92,15 +99,16 @@ fun ToggleChip( Icon( imageVector = when (toggleControl) { - ToggleChipToggleControl.Switch -> ToggleChipDefaults.switchIcon(checked) - ToggleChipToggleControl.Radio -> ToggleChipDefaults.radioIcon(checked) - ToggleChipToggleControl.Checkbox -> ToggleChipDefaults.checkboxIcon(checked) + WearPermissionToggleControlType.Switch -> ToggleChipDefaults.switchIcon(checked) + WearPermissionToggleControlType.Radio -> ToggleChipDefaults.radioIcon(checked) + WearPermissionToggleControlType.Checkbox -> + ToggleChipDefaults.checkboxIcon(checked) }, contentDescription = null, // This potentially be removed once this issue is addressed: // https://issuetracker.google.com/issues/287087138 rtlMode = - if (toggleControl == ToggleChipToggleControl.Switch) { + if (toggleControl == WearPermissionToggleControlType.Switch) { IconRtlMode.Mirrored } else { IconRtlMode.Default @@ -127,7 +135,7 @@ fun ToggleChip( checked = checked, onCheckedChange = { newChecked -> // Radio buttons cannot be toggled off by tapping on it again. - if (toggleControl != ToggleChipToggleControl.Radio || newChecked) { + if (toggleControl != WearPermissionToggleControlType.Radio || newChecked) { onCheckedChanged.invoke(newChecked) } }, @@ -219,3 +227,29 @@ fun toggleChipBackgroundColors(): ToggleChipColors { uncheckedToggleControlColor = uncheckedToggleControlColor, ) } + +@Composable +fun Modifier.toggleControlSemantics( + toggleControl: WearPermissionToggleControlType, + checked: Boolean, +): Modifier { + val semanticsRole = + when (toggleControl) { + WearPermissionToggleControlType.Switch -> Role.Switch + WearPermissionToggleControlType.Radio -> Role.RadioButton + WearPermissionToggleControlType.Checkbox -> Role.Checkbox + } + val stateDescriptionSemantics = + stringResource( + if (checked) { + R.string.on + } else { + R.string.off + } + ) + + return semantics { + role = semanticsRole + stateDescription = stateDescriptionSemantics + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Wear2Scaffold.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Wear2Scaffold.kt new file mode 100644 index 000000000..866b7f012 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/Wear2Scaffold.kt @@ -0,0 +1,258 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.permissioncontroller.permission.ui.wear.elements.material2 + +import android.graphics.drawable.Drawable +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.repeatOnLifecycle +import androidx.wear.compose.foundation.lazy.ScalingLazyColumn +import androidx.wear.compose.foundation.lazy.ScalingLazyListScope +import androidx.wear.compose.foundation.lazy.ScalingLazyListState +import androidx.wear.compose.material.CircularProgressIndicator +import androidx.wear.compose.material.MaterialTheme +import androidx.wear.compose.material.PositionIndicator +import androidx.wear.compose.material.Scaffold +import androidx.wear.compose.material.Text +import androidx.wear.compose.material.TimeText +import androidx.wear.compose.material.Vignette +import androidx.wear.compose.material.VignettePosition +import androidx.wear.compose.material.scrollAway +import com.android.permissioncontroller.permission.ui.wear.elements.AnnotatedText +import com.android.permissioncontroller.permission.ui.wear.elements.rememberDrawablePainter +import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme + +/** + * This component is wrapper on material 2 scaffold component. It helps with time text, scroll + * indicator and standard list elements like title, icon and subtitle. + */ +@Composable +fun Wear2Scaffold( + showTimeText: Boolean, + title: String?, + subtitle: CharSequence?, + image: Any?, + isLoading: Boolean, + content: ScalingLazyListScope.() -> Unit, + titleTestTag: String? = null, + subtitleTestTag: String? = null, +) { + val itemsSpacedBy = 4.dp + val screenWidth = LocalConfiguration.current.screenWidthDp + val screenHeight = LocalConfiguration.current.screenHeightDp + val scrollContentHorizontalPadding = (screenWidth * 0.052).dp + val titleHorizontalPadding = (screenWidth * 0.0884).dp + val subtitleHorizontalPadding = (screenWidth * 0.0416).dp + val scrollContentTopPadding = (screenHeight * 0.1456).dp - itemsSpacedBy + val scrollContentBottomPadding = (screenHeight * 0.3636).dp + val titleBottomPadding = + if (subtitle == null) { + 8.dp + } else { + 4.dp + } + val subtitleBottomPadding = 8.dp + val timeTextTopPadding = + if (showTimeText) { + 1.dp + } else { + 0.dp + } + val titlePaddingValues = + PaddingValues( + start = titleHorizontalPadding, + top = 4.dp, + bottom = titleBottomPadding, + end = titleHorizontalPadding, + ) + val subTitlePaddingValues = + PaddingValues( + start = subtitleHorizontalPadding, + top = 4.dp, + bottom = subtitleBottomPadding, + end = subtitleHorizontalPadding, + ) + val initialCenterIndex = 0 + val centerHeightDp = Dp(LocalConfiguration.current.screenHeightDp / 2.0f) + // We are adding TimeText's padding to create a smooth scrolling + val initialCenterItemScrollOffset = scrollContentTopPadding + timeTextTopPadding + val scrollAwayOffset = centerHeightDp - initialCenterItemScrollOffset + val focusRequester = remember { FocusRequester() } + val listState = remember { ScalingLazyListState(initialCenterItemIndex = initialCenterIndex) } + LaunchedEffect(title) { + listState.animateScrollToItem(index = 0) // Scroll to the top when triggerValue changes + } + WearPermissionTheme { + Scaffold( + modifier = Modifier.focusRequester(focusRequester), + timeText = { + if (showTimeText && !isLoading) { + TimeText( + modifier = + Modifier.scrollAway(listState, initialCenterIndex, scrollAwayOffset) + .padding(top = timeTextTopPadding) + ) + } + }, + vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) }, + positionIndicator = + if (!isLoading) { + { PositionIndicator(scalingLazyListState = listState) } + } else { + null + }, + ) { + Box(modifier = Modifier.fillMaxSize()) { + if (isLoading) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } else { + val iconColor = chipDefaultColors().iconColor(true).value + ScalingLazyColumn( + modifier = Modifier.fillMaxWidth(), + state = listState, + // Set autoCentering to null to avoid adding extra padding based on the + // content. + autoCentering = null, + contentPadding = + PaddingValues( + start = scrollContentHorizontalPadding, + end = scrollContentHorizontalPadding, + top = scrollContentTopPadding, + bottom = scrollContentBottomPadding, + ), + ) { + staticItem() + image?.let { + val imageModifier = Modifier.size(24.dp) + when (image) { + is Int -> + item { + Image( + painter = painterResource(id = image), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = imageModifier, + colorFilter = ColorFilter.tint(iconColor), + ) + } + is Drawable -> + item { + Image( + painter = rememberDrawablePainter(image), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = imageModifier, + colorFilter = ColorFilter.tint(iconColor), + ) + } + else -> {} + } + } + if (title != null) { + item { + var modifier: Modifier = Modifier + if (titleTestTag != null) { + modifier = modifier.testTag(titleTestTag) + } + ListHeader(modifier = Modifier.padding(titlePaddingValues)) { + Text( + text = title, + textAlign = TextAlign.Center, + modifier = modifier, + ) + } + } + } + if (subtitle != null) { + item { + var modifier: Modifier = + Modifier.align(Alignment.Center).padding(subTitlePaddingValues) + if (subtitleTestTag != null) { + modifier = modifier.testTag(subtitleTestTag) + } + AnnotatedText( + text = subtitle, + style = + MaterialTheme.typography.body2.copy( + color = MaterialTheme.colors.onSurfaceVariant + ), + modifier = modifier, + shouldCapitalize = true, + ) + } + } + + content() + } + RequestFocusOnResume(focusRequester = focusRequester) + } + } + } + } +} + +private fun ScalingLazyListScope.staticItem() { + /* + This empty item helps to ensure accurate scroll offset calculation. If auto centering is enabled + initial item's(first item for us) center matches the center of the screen. Scroll offset is 0 at + that point. + + if auto centering is not enabled, initial item will start at the top of the screen with the + scroll offset equal to ScreenHeight/2 - scrollContentTopPadding - firstItemHeight/2. + + We need to this offset value to properly move time text.That is the scroll-away offset of the + Time Text is equal to the scroll offset of the list at initial position. + + It is easier to calculate if we know the values of ScreenHeight, ScrollContentTopPadding and + FirstItem's height. ScreenHeight and ScrollContentPadding are constants but height of the + FirstItem depends on the content. Instead of measuring the height, we can simplify the + calculation with an empty item with 0dp height. + */ + item {} +} + +@Composable +private fun RequestFocusOnResume(focusRequester: FocusRequester) { + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(Unit) { + lifecycleOwner.repeatOnLifecycle(state = Lifecycle.State.RESUMED) { + focusRequester.requestFocus() + } + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnDefaults.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/layout/ScalingLazyColumnDefaults.kt index 550f1dc24..c06fdaf14 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnDefaults.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/layout/ScalingLazyColumnDefaults.kt @@ -16,7 +16,7 @@ @file:Suppress("ObjectLiteralToLambda") -package com.android.permissioncontroller.permission.ui.wear.elements.layout +package com.android.permissioncontroller.permission.ui.wear.elements.material2.layout import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues @@ -33,7 +33,7 @@ import androidx.wear.compose.foundation.lazy.ScalingLazyColumnDefaults import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType import androidx.wear.compose.foundation.lazy.ScalingParams import androidx.wear.compose.material.ChipDefaults -import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState.RotaryMode +import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumnState.RotaryMode import kotlin.math.sqrt // This file's content is copied from ScalingLazyColumnDefaults.kt from Horologist (go/horologist), @@ -63,10 +63,7 @@ object ScalingLazyColumnDefaults { firstItemIsFullWidth: Boolean = true, additionalPaddingAtBottom: Dp = 10.dp, verticalArrangement: Arrangement.Vertical = - Arrangement.spacedBy( - space = 4.dp, - alignment = Alignment.Top, - ), + Arrangement.spacedBy(space = 4.dp, alignment = Alignment.Top), horizontalPaddingPercent: Float = 0.052f, rotaryMode: RotaryMode? = RotaryMode.Scroll, hapticsEnabled: Boolean = true, @@ -145,7 +142,7 @@ object ScalingLazyColumnDefaults { return (radius - sqrt( (radius - childViewHeight + childViewWidth * 0.5f) * - (radius - childViewWidth * 0.5f), + (radius - childViewWidth * 0.5f) ) - childViewHeight * 0.5f) .dp @@ -225,10 +222,7 @@ object ScalingLazyColumnDefaults { last.bottomPaddingDp * height + first.paddingCorrection } else { if (configuration.isScreenRound) { - calculateVerticalOffsetForChip( - screenWidthDp, - horizontalPercent, - ) + 10.dp + calculateVerticalOffsetForChip(screenWidthDp, horizontalPercent) + 10.dp } else { 0.dp } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnState.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/layout/ScalingLazyColumnState.kt index 0603647b1..0e669f6ff 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/layout/ScalingLazyColumnState.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material2/layout/ScalingLazyColumnState.kt @@ -17,7 +17,7 @@ @file:Suppress("ObjectLiteralToLambda") @file:OptIn(ExperimentalWearFoundationApi::class) -package com.android.permissioncontroller.permission.ui.wear.elements.layout +package com.android.permissioncontroller.permission.ui.wear.elements.material2.layout import androidx.compose.foundation.MutatePriority import androidx.compose.foundation.gestures.FlingBehavior @@ -42,14 +42,8 @@ import androidx.wear.compose.foundation.lazy.ScalingLazyListAnchorType import androidx.wear.compose.foundation.lazy.ScalingLazyListScope import androidx.wear.compose.foundation.lazy.ScalingLazyListState import androidx.wear.compose.foundation.lazy.ScalingParams -import androidx.wear.compose.foundation.rememberActiveFocusRequester -import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnDefaults.responsiveScalingParams -import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState.RotaryMode -import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rememberDisabledHaptic -import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rememberRotaryHapticHandler -import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rotaryWithScroll -import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.rotaryWithSnap -import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput.toRotaryScrollAdapter +import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumnDefaults.responsiveScalingParams +import com.android.permissioncontroller.permission.ui.wear.elements.material2.layout.ScalingLazyColumnState.RotaryMode // This file is a copy of ScalingLazyColumnState.kt from Horologist (go/horologist), // remove it once after wear compose supports large screen dialogs. @@ -61,10 +55,7 @@ import com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput. class ScalingLazyColumnState( val initialScrollPosition: ScrollPosition = ScrollPosition(1, 0), val autoCentering: AutoCenteringParams? = - AutoCenteringParams( - initialScrollPosition.index, - initialScrollPosition.offsetPx, - ), + AutoCenteringParams(initialScrollPosition.index, initialScrollPosition.offsetPx), val anchorType: ScalingLazyListAnchorType = ScalingLazyListAnchorType.ItemCenter, val contentPadding: PaddingValues = PaddingValues(horizontal = 10.dp), val rotaryMode: RotaryMode? = RotaryMode.Scroll, @@ -120,10 +111,7 @@ class ScalingLazyColumnState( data object Scroll : RotaryMode } - data class ScrollPosition( - val index: Int, - val offsetPx: Int, - ) + data class ScrollPosition(val index: Int, val offsetPx: Int) fun interface Factory { @Composable fun create(): ScalingLazyColumnState @@ -133,7 +121,7 @@ class ScalingLazyColumnState( // @Deprecated("Replaced by rememberResponsiveColumnState") @Composable fun rememberColumnState( - factory: ScalingLazyColumnState.Factory = ScalingLazyColumnDefaults.responsive(), + factory: ScalingLazyColumnState.Factory = ScalingLazyColumnDefaults.responsive() ): ScalingLazyColumnState { val columnState = factory.create() @@ -150,10 +138,7 @@ fun rememberResponsiveColumnState( last = ScalingLazyColumnDefaults.ItemType.Unspecified, ), verticalArrangement: Arrangement.Vertical = - Arrangement.spacedBy( - space = 4.dp, - alignment = Alignment.Top, - ), + Arrangement.spacedBy(space = 4.dp, alignment = Alignment.Top), rotaryMode: RotaryMode? = RotaryMode.Scroll, hapticsEnabled: Boolean = true, reverseLayout: Boolean = false, @@ -173,10 +158,7 @@ fun rememberResponsiveColumnState( val topScreenOffsetPx = screenHeightPx / 2 - topPaddingPx val initialScrollPosition = - ScalingLazyColumnState.ScrollPosition( - index = 0, - offsetPx = topScreenOffsetPx, - ) + ScalingLazyColumnState.ScrollPosition(index = 0, offsetPx = topScreenOffsetPx) val columnState = ScalingLazyColumnState( @@ -204,36 +186,8 @@ fun ScalingLazyColumn( modifier: Modifier = Modifier, content: ScalingLazyListScope.() -> Unit, ) { - val focusRequester = rememberActiveFocusRequester() - - val rotaryHaptics = - if (columnState.hapticsEnabled) { - rememberRotaryHapticHandler(columnState.state) - } else { - rememberDisabledHaptic() - } - - val modifierWithRotary = - when (columnState.rotaryMode) { - RotaryMode.Snap -> - modifier.rotaryWithSnap( - focusRequester = focusRequester, - rotaryScrollAdapter = columnState.state.toRotaryScrollAdapter(), - reverseDirection = columnState.reverseLayout, - rotaryHaptics = rotaryHaptics, - ) - RotaryMode.Scroll -> - modifier.rotaryWithScroll( - focusRequester = focusRequester, - scrollableState = columnState.state, - reverseDirection = columnState.reverseLayout, - rotaryHaptics = rotaryHaptics, - ) - else -> modifier - } - ScalingLazyColumn( - modifier = modifierWithRotary.fillMaxSize(), + modifier = modifier.fillMaxSize(), state = columnState.state, contentPadding = columnState.contentPadding, reverseLayout = columnState.reverseLayout, diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt index 79a8963d8..9a89e0809 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButton.kt @@ -33,7 +33,8 @@ import androidx.wear.compose.material3.ButtonDefaults import androidx.wear.compose.material3.LocalTextConfiguration import androidx.wear.compose.material3.LocalTextStyle import androidx.wear.compose.material3.Text -import com.android.permissioncontroller.permission.ui.wear.elements.Chip +import com.android.permissioncontroller.permission.ui.wear.elements.material2.Chip +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion /** @@ -45,8 +46,7 @@ import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionM fun WearPermissionButton( label: String, modifier: Modifier = Modifier, - materialUIVersion: WearPermissionMaterialUIVersion = - WearPermissionMaterialUIVersion.MATERIAL2_5, + materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings, iconBuilder: WearPermissionIconBuilder? = null, labelMaxLines: Int? = null, secondaryLabel: String? = null, @@ -63,7 +63,7 @@ fun WearPermissionButton( modifier = modifier, secondaryLabel = secondaryLabel, secondaryLabelMaxLines = secondaryLabelMaxLines, - icon = { iconBuilder?.build() }, + icon = iconBuilder?.let { { iconBuilder.build() } }, largeIcon = false, colors = style.material2ChipColors(), enabled = enabled, diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt index 504c69bb0..36d3f9f33 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionButtonStyle.kt @@ -21,8 +21,8 @@ import androidx.wear.compose.material.ChipColors import androidx.wear.compose.material.ChipDefaults import androidx.wear.compose.material3.ButtonColors import androidx.wear.compose.material3.ButtonDefaults -import com.android.permissioncontroller.permission.ui.wear.elements.chipDefaultColors -import com.android.permissioncontroller.permission.ui.wear.elements.chipDisabledColors +import com.android.permissioncontroller.permission.ui.wear.elements.material2.chipDefaultColors +import com.android.permissioncontroller.permission.ui.wear.elements.material2.chipDisabledColors import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle.DisabledLike import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle.Primary import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle.Secondary diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionConfirmationDialog.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionConfirmationDialog.kt new file mode 100644 index 000000000..0e1bf1fbe --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionConfirmationDialog.kt @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.permissioncontroller.permission.ui.wear.elements.material3 + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState +import androidx.wear.compose.material3.AlertDialog as Material3AlertDialog +import androidx.wear.compose.material3.AlertDialogDefaults +import androidx.wear.compose.material3.Text +import com.android.permissioncontroller.permission.ui.wear.elements.material2.AlertDialog +import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion + +data class DialogButtonContent( + val icon: WearPermissionIconBuilder? = null, + val onClick: (() -> Unit), +) + +@Composable +fun WearPermissionConfirmationDialog( + materialUIVersion: WearPermissionMaterialUIVersion = + WearPermissionMaterialUIVersion.MATERIAL2_5, + show: Boolean, + iconRes: WearPermissionIconBuilder? = null, + title: String? = null, + message: String? = null, + positiveButtonContent: DialogButtonContent? = null, + negativeButtonContent: DialogButtonContent? = null, +) { + + if (materialUIVersion == WearPermissionMaterialUIVersion.MATERIAL3) { + if ( + (positiveButtonContent == null && negativeButtonContent != null) || + (positiveButtonContent != null && negativeButtonContent == null) + ) { + val edgeButtonContent = (positiveButtonContent ?: negativeButtonContent)!! + WearPermissionConfirmationDialogInternal( + show = show, + edgeButtonContent = edgeButtonContent, + iconRes = iconRes, + title = title, + message = message, + ) + } else { + WearPermissionConfirmationDialogInternal( + show = show, + positiveButtonContent = positiveButtonContent, + negativeButtonContent = negativeButtonContent, + iconRes = iconRes, + title = title, + message = message, + ) + } + } else { + AlertDialog( + title = title, + iconRes = iconRes, + message = message ?: "", + positiveButtonContent = positiveButtonContent, + negativeButtonContent = negativeButtonContent, + showDialog = show, + scalingLazyListState = rememberScalingLazyListState(), + ) + } +} + +@Composable +private fun WearPermissionConfirmationDialogInternal( + show: Boolean, + edgeButtonContent: DialogButtonContent, + iconRes: WearPermissionIconBuilder?, + title: String?, + message: String?, +) { + val edgeIcon: @Composable RowScope.() -> Unit = + edgeButtonContent.icon?.let { + { it.modifier(Modifier.size(36.dp).align(Alignment.CenterVertically)).build() } + } ?: AlertDialogDefaults.ConfirmIcon + + Material3AlertDialog( + visible = show, + onDismissRequest = edgeButtonContent.onClick, + edgeButton = { + AlertDialogDefaults.EdgeButton(onClick = edgeButtonContent.onClick, content = edgeIcon) + }, + icon = { iconRes?.build() }, + title = title?.let { { Text(text = title) } } ?: {}, + text = message?.let { { Text(text = message) } }, + ) +} + +@Composable +private fun WearPermissionConfirmationDialogInternal( + show: Boolean, + positiveButtonContent: DialogButtonContent?, + negativeButtonContent: DialogButtonContent?, + iconRes: WearPermissionIconBuilder?, + title: String?, + message: String?, +) { + val positiveButton: (@Composable RowScope.() -> Unit)? = + positiveButtonContent?.let { + { + val positiveIcon: @Composable RowScope.() -> Unit = + positiveButtonContent.icon?.let { + { + it.modifier(Modifier.size(36.dp).align(Alignment.CenterVertically)) + .build() + } + } ?: AlertDialogDefaults.ConfirmIcon + + AlertDialogDefaults.ConfirmButton( + onClick = positiveButtonContent.onClick, + content = positiveIcon, + ) + } + } + + val negativeButton: (@Composable RowScope.() -> Unit)? = + negativeButtonContent?.let { + { + val negativeIcon: @Composable RowScope.() -> Unit = + negativeButtonContent.icon?.let { + { + it.modifier(Modifier.size(36.dp).align(Alignment.CenterVertically)) + .build() + } + } ?: AlertDialogDefaults.DismissIcon + + AlertDialogDefaults.DismissButton( + onClick = negativeButtonContent.onClick, + content = negativeIcon, + ) + } + } + + Material3AlertDialog( + visible = show, + onDismissRequest = negativeButtonContent?.onClick ?: {}, + confirmButton = positiveButton ?: {}, + dismissButton = negativeButton ?: {}, + icon = { iconRes?.build() }, + title = title?.let { { Text(text = title) } } ?: {}, + text = message?.let { { Text(text = message) } }, + ) +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt index b7521d073..52674b50d 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionIconBuilder.kt @@ -17,12 +17,16 @@ package com.android.permissioncontroller.permission.ui.wear.elements.material3 import android.graphics.drawable.Drawable import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.wear.compose.material3.Icon import androidx.wear.compose.material3.IconButtonDefaults import com.android.permissioncontroller.permission.ui.wear.elements.rememberDrawablePainter @@ -66,7 +70,7 @@ class WearPermissionIconBuilder private constructor() { } fun modifier(modifier: Modifier): WearPermissionIconBuilder { - this.modifier then modifier + this.modifier = modifier then this.modifier return this } @@ -99,3 +103,11 @@ class WearPermissionIconBuilder private constructor() { fun builder(icon: Any) = WearPermissionIconBuilder().apply { iconResource = icon } } } + +@Composable +fun WearPermissionIconBuilder.Companion.defaultAlertConfirmIcon() = + builder(Icons.Default.Check).contentDescription((stringResource(android.R.string.ok))) + +@Composable +fun WearPermissionIconBuilder.Companion.defaultAlertDismissIcon() = + builder(Icons.Default.Close).contentDescription((stringResource(android.R.string.cancel))) diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt index cd18b5b09..35efe5db1 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListFooter.kt @@ -21,7 +21,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.unit.dp import androidx.wear.compose.material3.ButtonDefaults -import com.android.permissioncontroller.permission.ui.wear.elements.ListFooter +import com.android.permissioncontroller.permission.ui.wear.elements.material2.ListFooter import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion /** This component is creates a transparent styled button to use as a list footer. */ diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListSubHeader.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListSubHeader.kt new file mode 100644 index 000000000..ddcd93326 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionListSubHeader.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.permissioncontroller.permission.ui.wear.elements.material3 + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeightIn +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp +import androidx.wear.compose.material3.ListSubHeader +import com.android.permissioncontroller.permission.ui.wear.elements.material2.ListSubheader +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper +import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion + +/* +This component is simplified wrapper over ListSubHeader with quick padding adjustments + */ +@Composable +fun WearPermissionListSubHeader( + wearPermissionMaterialUIVersion: WearPermissionMaterialUIVersion = + ResourceHelper.materialUIVersionInSettings, + isFirstItemInAList: Boolean, + label: @Composable RowScope.() -> Unit, +) { + val screenWidth = LocalConfiguration.current.screenWidthDp + val screenHeight = LocalConfiguration.current.screenHeightDp + val subtitlePaddingDefaults = + WearPermissionScaffoldPaddingDefaults( + screenWidth = screenWidth, + screenHeight = screenHeight, + ) + .subHeaderPaddingValues(needsLargePadding = !isFirstItemInAList) + + if (wearPermissionMaterialUIVersion == WearPermissionMaterialUIVersion.MATERIAL3) { + ListSubHeader( + modifier = Modifier.requiredHeightIn(1.dp), // We do not want default min height + contentPadding = subtitlePaddingDefaults, + label = label, + ) + } else { + ListSubheader(modifier = Modifier.padding(subtitlePaddingDefaults), label = label) + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt index 9a926f5a3..35bdf583e 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffold.kt @@ -17,6 +17,7 @@ package com.android.permissioncontroller.permission.ui.wear.elements.material3 import android.graphics.drawable.Drawable import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.PaddingValues @@ -33,41 +34,61 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.Hyphens import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.wear.compose.foundation.ScrollInfoProvider import androidx.wear.compose.foundation.lazy.ScalingLazyListScope +import androidx.wear.compose.foundation.lazy.TransformingLazyColumn +import androidx.wear.compose.foundation.lazy.TransformingLazyColumnScope +import androidx.wear.compose.foundation.lazy.TransformingLazyColumnState +import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState import androidx.wear.compose.material3.AppScaffold import androidx.wear.compose.material3.CircularProgressIndicator +import androidx.wear.compose.material3.IconButtonDefaults import androidx.wear.compose.material3.ListHeader import androidx.wear.compose.material3.MaterialTheme import androidx.wear.compose.material3.ScreenScaffold import androidx.wear.compose.material3.ScrollIndicator import androidx.wear.compose.material3.Text import androidx.wear.compose.material3.TimeText +import androidx.wear.compose.material3.lazy.scrollTransform import com.android.permissioncontroller.permission.ui.wear.elements.AnnotatedText -import com.android.permissioncontroller.permission.ui.wear.elements.Wear2Scaffold -import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumn -import com.android.permissioncontroller.permission.ui.wear.elements.layout.ScalingLazyColumnState -import com.android.permissioncontroller.permission.ui.wear.elements.layout.rememberResponsiveColumnState +import com.android.permissioncontroller.permission.ui.wear.elements.ListScopeWrapper +import com.android.permissioncontroller.permission.ui.wear.elements.material2.Wear2Scaffold import com.android.permissioncontroller.permission.ui.wear.elements.rememberDrawablePainter +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5 import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionTheme +private class TransformingScopeConverter(private val scope: TransformingLazyColumnScope) : + ListScopeWrapper { + override fun item(key: Any?, contentType: Any?, content: @Composable () -> Unit) { + // TODO:https://buganizer.corp.google.com/issues/389093588. + scope.item { Box(modifier = Modifier.scrollTransform(this)) { content() } } + } +} + +private class ScalingScopeConverter(private val scope: ScalingLazyListScope) : ListScopeWrapper { + override fun item(key: Any?, contentType: Any?, content: @Composable () -> Unit) { + scope.item { content() } + } +} + /** * This component is wrapper on material scaffold component. It helps with time text, scroll * indicator and standard list elements like title, icon and subtitle. */ @Composable internal fun WearPermissionScaffold( - materialUIVersion: WearPermissionMaterialUIVersion = MATERIAL2_5, + materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings, showTimeText: Boolean, title: String?, subtitle: CharSequence?, image: Any?, isLoading: Boolean, - content: ScalingLazyListScope.() -> Unit, + content: ListScopeWrapper.() -> Unit, titleTestTag: String? = null, subtitleTestTag: String? = null, ) { @@ -79,7 +100,7 @@ internal fun WearPermissionScaffold( subtitle, image, isLoading, - content, + { content.invoke(ScalingScopeConverter(this)) }, titleTestTag, subtitleTestTag, ) @@ -90,7 +111,7 @@ internal fun WearPermissionScaffold( subtitle, image, isLoading, - content, + { content.invoke(TransformingScopeConverter(this)) }, titleTestTag, subtitleTestTag, ) @@ -104,7 +125,7 @@ private fun WearPermissionScaffoldInternal( subtitle: CharSequence?, image: Any?, isLoading: Boolean, - content: ScalingLazyListScope.() -> Unit, + content: TransformingLazyColumnScope.() -> Unit, titleTestTag: String? = null, subtitleTestTag: String? = null, ) { @@ -114,14 +135,12 @@ private fun WearPermissionScaffoldInternal( WearPermissionScaffoldPaddingDefaults( screenWidth = screenWidth, screenHeight = screenHeight, - titleNeedsLargePadding = subtitle == null, ) - val columnState = - rememberResponsiveColumnState(contentPadding = { paddingDefaults.scrollContentPadding }) + val columnState = rememberTransformingLazyColumnState() WearPermissionTheme(version = WearPermissionMaterialUIVersion.MATERIAL3) { AppScaffold(timeText = wearPermissionTimeText(showTimeText && !isLoading)) { ScreenScaffold( - scrollInfoProvider = ScrollInfoProvider(columnState.state), + scrollInfoProvider = ScrollInfoProvider(columnState), scrollIndicator = wearPermissionScrollIndicator(!isLoading, columnState), ) { Box(modifier = Modifier.fillMaxSize()) { @@ -129,11 +148,13 @@ private fun WearPermissionScaffoldInternal( CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) } else { ScrollingView( + contentPadding = paddingDefaults.scrollContentPadding, columnState = columnState, icon = painterFromImage(image), title = title, titleTestTag = titleTestTag, - titlePaddingValues = paddingDefaults.titlePaddingValues, + titlePaddingValues = + paddingDefaults.titlePaddingValues(subtitle == null), subtitle = subtitle, subtitleTestTag = subtitleTestTag, subTitlePaddingValues = paddingDefaults.subTitlePaddingValues, @@ -146,45 +167,10 @@ private fun WearPermissionScaffoldInternal( } } -private class WearPermissionScaffoldPaddingDefaults( - screenWidth: Int, - screenHeight: Int, - titleNeedsLargePadding: Boolean, -) { - private val firstSpacerItemHeight = 0.dp - private val scrollContentHorizontalPadding = (screenWidth * 0.052).dp - private val titleHorizontalPadding = (screenWidth * 0.0884).dp - private val subtitleHorizontalPadding = (screenWidth * 0.0416).dp - private val scrollContentTopPadding = (screenHeight * 0.1456).dp - firstSpacerItemHeight - private val scrollContentBottomPadding = (screenHeight * 0.3636).dp - private val defaultItemPadding = 4.dp - private val largeItemPadding = 8.dp - val titlePaddingValues = - PaddingValues( - start = titleHorizontalPadding, - top = defaultItemPadding, - bottom = if (titleNeedsLargePadding) largeItemPadding else defaultItemPadding, - end = titleHorizontalPadding, - ) - val subTitlePaddingValues = - PaddingValues( - start = subtitleHorizontalPadding, - top = defaultItemPadding, - bottom = largeItemPadding, - end = subtitleHorizontalPadding, - ) - val scrollContentPadding = - PaddingValues( - start = scrollContentHorizontalPadding, - end = scrollContentHorizontalPadding, - top = scrollContentTopPadding, - bottom = scrollContentBottomPadding, - ) -} - @Composable private fun BoxScope.ScrollingView( - columnState: ScalingLazyColumnState, + contentPadding: PaddingValues, + columnState: TransformingLazyColumnState, icon: Painter?, title: String?, titleTestTag: String?, @@ -192,16 +178,26 @@ private fun BoxScope.ScrollingView( subtitleTestTag: String?, titlePaddingValues: PaddingValues, subTitlePaddingValues: PaddingValues, - content: ScalingLazyListScope.() -> Unit, + content: TransformingLazyColumnScope.() -> Unit, ) { - ScalingLazyColumn(columnState = columnState) { - iconItem(icon, Modifier.size(24.dp)) - titleItem(text = title, testTag = titleTestTag, contentPaddingValues = titlePaddingValues) - subtitleItem( - text = subtitle, - testTag = subtitleTestTag, - modifier = Modifier.align(Alignment.Center).padding(subTitlePaddingValues), - ) + TransformingLazyColumn( + contentPadding = contentPadding, + state = columnState, + modifier = Modifier.background(MaterialTheme.colorScheme.background), + ) { + with(TransformingScopeConverter(this)) { + iconItem(icon, Modifier.size(IconButtonDefaults.LargeIconSize)) + titleItem( + text = title, + testTag = titleTestTag, + contentPaddingValues = titlePaddingValues, + ) + subtitleItem( + text = subtitle, + testTag = subtitleTestTag, + modifier = Modifier.align(Alignment.Center).padding(subTitlePaddingValues), + ) + } content() } } @@ -216,15 +212,10 @@ private fun wearPermissionTimeText(showTime: Boolean): @Composable () -> Unit { private fun wearPermissionScrollIndicator( showIndicator: Boolean, - columnState: ScalingLazyColumnState, + columnState: TransformingLazyColumnState, ): @Composable (BoxScope.() -> Unit)? { return if (showIndicator) { - { - ScrollIndicator( - modifier = Modifier.align(Alignment.CenterEnd), - state = columnState.state, - ) - } + { ScrollIndicator(modifier = Modifier.align(Alignment.CenterEnd), state = columnState) } } else { null } @@ -246,7 +237,7 @@ private fun Modifier.optionalTestTag(tag: String?): Modifier { return this then testTag(tag) } -private fun ScalingLazyListScope.iconItem(painter: Painter?, modifier: Modifier = Modifier) = +private fun ListScopeWrapper.iconItem(painter: Painter?, modifier: Modifier = Modifier) = painter?.let { item { val iconColor = WearPermissionButtonStyle.Secondary.material3ButtonColors().iconColor @@ -260,14 +251,14 @@ private fun ScalingLazyListScope.iconItem(painter: Painter?, modifier: Modifier } } -private fun ScalingLazyListScope.titleItem( +private fun ListScopeWrapper.titleItem( text: String?, testTag: String?, contentPaddingValues: PaddingValues, modifier: Modifier = Modifier, ) = text?.let { - item { + item(contentType = "header") { ListHeader( modifier = modifier.requiredHeightIn(1.dp), // We do not want default min height contentPadding = contentPaddingValues, @@ -276,12 +267,13 @@ private fun ScalingLazyListScope.titleItem( text = it, textAlign = TextAlign.Center, modifier = Modifier.optionalTestTag(testTag), + style = MaterialTheme.typography.titleLarge.copy(hyphens = Hyphens.Auto), ) } } } -private fun ScalingLazyListScope.subtitleItem( +private fun ListScopeWrapper.subtitleItem( text: CharSequence?, testTag: String?, modifier: Modifier = Modifier, diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffoldPaddingDefaults.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffoldPaddingDefaults.kt new file mode 100644 index 000000000..14eaec4bf --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionScaffoldPaddingDefaults.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.permissioncontroller.permission.ui.wear.elements.material3 + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.ui.unit.dp + +/* A common class for calculating padding for list items as per the latest design. +https://www.figma.com/design/nb1atBKcK3luF8AXWLUe0X/BC25-Settings-on-Wear?node-id=2336-3304&t=n35PgTUC2O8hGSI0-0 */ +data class WearPermissionScaffoldPaddingDefaults( + private val screenWidth: Int, + private val screenHeight: Int, +) { + private val scrollContentHorizontalPadding = (screenWidth * 0.052).dp + private val titleHorizontalPadding = (screenWidth * 0.0520).dp + private val subtitleHorizontalPadding = (screenWidth * 0.0624).dp + private val scrollContentTopPadding = (screenHeight * 0.1664).dp + private val scrollContentBottomPadding = (screenHeight * 0.3646).dp + private val noPadding = 0.dp + private val defaultItemPadding = 4.dp + private val largeItemPadding = 8.dp + private val extraLargePadding = 12.dp + + fun titlePaddingValues(needsLargePadding: Boolean): PaddingValues = + PaddingValues( + start = titleHorizontalPadding, + top = defaultItemPadding, + bottom = if (needsLargePadding) largeItemPadding else defaultItemPadding, + end = titleHorizontalPadding, + ) + + fun subHeaderPaddingValues(needsLargePadding: Boolean): PaddingValues = + PaddingValues( + start = subtitleHorizontalPadding, + top = if (needsLargePadding) extraLargePadding else noPadding, + bottom = largeItemPadding, + end = subtitleHorizontalPadding, + ) + + val subTitlePaddingValues = + PaddingValues( + start = subtitleHorizontalPadding, + top = defaultItemPadding, + bottom = largeItemPadding, + end = subtitleHorizontalPadding, + ) + val scrollContentPadding = + PaddingValues( + start = scrollContentHorizontalPadding, + end = scrollContentHorizontalPadding, + top = scrollContentTopPadding, + bottom = scrollContentBottomPadding, + ) +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt index 9841ca521..d37d17a84 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControl.kt @@ -20,16 +20,26 @@ import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription import androidx.wear.compose.material3.CheckboxButton import androidx.wear.compose.material3.LocalTextConfiguration import androidx.wear.compose.material3.RadioButton import androidx.wear.compose.material3.SwitchButton import androidx.wear.compose.material3.Text -import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip -import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl -import com.android.permissioncontroller.permission.ui.wear.elements.toggleControlSemantics +import com.android.permissioncontroller.R +import com.android.permissioncontroller.permission.ui.wear.elements.material2.ToggleChip +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion +/** Defines various toggle control types. */ +enum class WearPermissionToggleControlType { + Switch, + Radio, + Checkbox, +} + /** * The custom component is a wrapper on different material3 toggle controls. * 1. It provides an unified interface for RadioButton,CheckButton and SwitchButton. @@ -39,14 +49,13 @@ import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionM */ @Composable fun WearPermissionToggleControl( - toggleControl: ToggleChipToggleControl, + toggleControl: WearPermissionToggleControlType, label: String, checked: Boolean, onCheckedChanged: (Boolean) -> Unit, modifier: Modifier = Modifier, labelMaxLines: Int? = null, - materialUIVersion: WearPermissionMaterialUIVersion = - WearPermissionMaterialUIVersion.MATERIAL2_5, + materialUIVersion: WearPermissionMaterialUIVersion = ResourceHelper.materialUIVersionInSettings, iconBuilder: WearPermissionIconBuilder? = null, secondaryLabel: String? = null, secondaryLabelMaxLines: Int? = null, @@ -87,7 +96,7 @@ fun WearPermissionToggleControl( @Composable private fun WearPermissionToggleControlInternal( label: String, - toggleControl: ToggleChipToggleControl, + toggleControl: WearPermissionToggleControlType, checked: Boolean, onCheckedChanged: (Boolean) -> Unit, modifier: Modifier = Modifier, @@ -118,15 +127,19 @@ private fun WearPermissionToggleControlInternal( } val iconParam: (@Composable BoxScope.() -> Unit)? = iconBuilder?.let { { it.build() } } - + val toggleControlStateDescription = + stringResource( + if (checked) { + R.string.on + } else { + R.string.off + } + ) val updatedModifier = - modifier - .fillMaxWidth() - // .heightIn(min = 58.dp) // TODO(b/370783358): This should be a overlaid value - .toggleControlSemantics(toggleControl, checked) + modifier.fillMaxWidth().semantics { stateDescription = toggleControlStateDescription } when (toggleControl) { - ToggleChipToggleControl.Radio -> + WearPermissionToggleControlType.Radio -> RadioButton( selected = checked, onSelect = { @@ -144,7 +157,7 @@ private fun WearPermissionToggleControlInternal( colors = style.radioButtonColorScheme(), ) - ToggleChipToggleControl.Checkbox -> + WearPermissionToggleControlType.Checkbox -> CheckboxButton( checked = checked, onCheckedChange = onCheckedChanged, @@ -156,7 +169,7 @@ private fun WearPermissionToggleControlInternal( colors = style.checkboxColorScheme(), ) - ToggleChipToggleControl.Switch -> + WearPermissionToggleControlType.Switch -> SwitchButton( checked = checked, onCheckedChange = onCheckedChanged, diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt index b5746f019..048a06861 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/material3/WearPermissionToggleControlStyle.kt @@ -25,8 +25,8 @@ import androidx.wear.compose.material3.RadioButtonColors import androidx.wear.compose.material3.RadioButtonDefaults.radioButtonColors import androidx.wear.compose.material3.SwitchButtonColors import androidx.wear.compose.material3.SwitchButtonDefaults.switchButtonColors -import com.android.permissioncontroller.permission.ui.wear.elements.toggleChipBackgroundColors -import com.android.permissioncontroller.permission.ui.wear.elements.toggleChipDisabledColors +import com.android.permissioncontroller.permission.ui.wear.elements.material2.toggleChipBackgroundColors +import com.android.permissioncontroller.permission.ui.wear.elements.material2.toggleChipDisabledColors /** * Defines toggle control styles, It helps in setting the right colors scheme to a toggle control. diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Haptics.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Haptics.kt deleted file mode 100644 index 817bf7efe..000000000 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Haptics.kt +++ /dev/null @@ -1,292 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput - -import android.os.Build -import android.view.View -import androidx.compose.foundation.gestures.ScrollableState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.platform.LocalView -import kotlin.math.abs -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.conflate -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.withContext - -// This file is a copy of Haptics.kt from Horologist (go/horologist), -// remove it once Wear Compose 1.4 is landed (b/325560444). - -private const val DEBUG = false - -/** Debug logging that can be enabled. */ -private inline fun debugLog(generateMsg: () -> String) { - if (DEBUG) { - println("RotaryHaptics: ${generateMsg()}") - } -} - -/** - * Throttling events within specified timeframe. Only first and last events will be received. For a - * flow emitting elements 1 to 30, with a 100ms delay between them: - * ``` - * val flow = flow { - * for (i in 1..30) { - * delay(100) - * emit(i) - * } - * } - * ``` - * - * With timeframe=1000 only those integers will be received: 1, 10, 20, 30 . - */ -internal fun <T> Flow<T>.throttleLatest(timeframe: Long): Flow<T> = flow { - conflate().collect { - emit(it) - delay(timeframe) - } -} - -/** Handles haptics for rotary usage */ -interface RotaryHapticHandler { - - /** Handles haptics when scroll is used */ - fun handleScrollHaptic(scrollDelta: Float) - - /** Handles haptics when scroll with snap is used */ - fun handleSnapHaptic(scrollDelta: Float) -} - -/** - * Default implementation of [RotaryHapticHandler]. It handles haptic feedback based on the - * [scrollableState], scrolled pixels and [hapticsThresholdPx]. Haptic is not fired in this class, - * instead it's sent to [hapticsChannel] where it'll performed later. - * - * @param scrollableState Haptic performed based on this state - * @param hapticsChannel Channel to which haptic events will be sent - * @param hapticsThresholdPx A scroll threshold after which haptic is produced. - */ -class DefaultRotaryHapticHandler( - private val scrollableState: ScrollableState, - private val hapticsChannel: Channel<RotaryHapticsType>, - private val hapticsThresholdPx: Long = 50, -) : RotaryHapticHandler { - - private var overscrollHapticTriggered = false - private var currScrollPosition = 0f - private var prevHapticsPosition = 0f - - override fun handleScrollHaptic(scrollDelta: Float) { - if ( - (scrollDelta > 0 && !scrollableState.canScrollForward) || - (scrollDelta < 0 && !scrollableState.canScrollBackward) - ) { - if (!overscrollHapticTriggered) { - trySendHaptic(RotaryHapticsType.ScrollLimit) - overscrollHapticTriggered = true - } - } else { - overscrollHapticTriggered = false - currScrollPosition += scrollDelta - val diff = abs(currScrollPosition - prevHapticsPosition) - - if (diff >= hapticsThresholdPx) { - trySendHaptic(RotaryHapticsType.ScrollTick) - prevHapticsPosition = currScrollPosition - } - } - } - - override fun handleSnapHaptic(scrollDelta: Float) { - if ( - (scrollDelta > 0 && !scrollableState.canScrollForward) || - (scrollDelta < 0 && !scrollableState.canScrollBackward) - ) { - if (!overscrollHapticTriggered) { - trySendHaptic(RotaryHapticsType.ScrollLimit) - overscrollHapticTriggered = true - } - } else { - overscrollHapticTriggered = false - trySendHaptic(RotaryHapticsType.ScrollItemFocus) - } - } - - private fun trySendHaptic(rotaryHapticsType: RotaryHapticsType) { - // Ok to ignore the ChannelResult because we default to capacity = 2 and DROP_OLDEST - @Suppress("UNUSED_VARIABLE") val unused = hapticsChannel.trySend(rotaryHapticsType) - } -} - -/** Interface for Rotary haptic feedback */ -interface RotaryHapticFeedback { - fun performHapticFeedback(type: RotaryHapticsType) -} - -/** Rotary haptic types */ -@JvmInline -value class RotaryHapticsType(private val type: Int) { - companion object { - /** - * A scroll ticking haptic. Similar to texture haptic - performed each time when a - * scrollable content is scrolled by a certain distance - */ - val ScrollTick: RotaryHapticsType = RotaryHapticsType(1) - - /** - * An item focus (snap) haptic. Performed when a scrollable content is snapped to a specific - * item. - */ - val ScrollItemFocus: RotaryHapticsType = RotaryHapticsType(2) - - /** - * A limit(overscroll) haptic. Performed when a list reaches the limit (start or end) and - * can't scroll further - */ - val ScrollLimit: RotaryHapticsType = RotaryHapticsType(3) - } -} - -/** Remember disabled haptics handler */ -@Composable -fun rememberDisabledHaptic(): RotaryHapticHandler = remember { - object : RotaryHapticHandler { - - override fun handleScrollHaptic(scrollDelta: Float) { - // Do nothing - } - - override fun handleSnapHaptic(scrollDelta: Float) { - // Do nothing - } - } -} - -/** - * Remember rotary haptic handler. - * - * @param scrollableState A scrollableState, used to determine whether the end of the scrollable was - * reached or not. - * @param throttleThresholdMs Throttling events within specified timeframe. Only first and last - * events will be received. Check [throttleLatest] for more info. - * @param hapticsThresholdPx A scroll threshold after which haptic is produced. - * @param hapticsChannel Channel to which haptic events will be sent - * @param rotaryHaptics Interface for Rotary haptic feedback which performs haptics - */ -@Composable -fun rememberRotaryHapticHandler( - scrollableState: ScrollableState, - throttleThresholdMs: Long = 30, - hapticsThresholdPx: Long = 50, - hapticsChannel: Channel<RotaryHapticsType> = rememberHapticChannel(), - rotaryHaptics: RotaryHapticFeedback = rememberDefaultRotaryHapticFeedback(), -): RotaryHapticHandler { - return remember(scrollableState, hapticsChannel, rotaryHaptics) { - DefaultRotaryHapticHandler(scrollableState, hapticsChannel, hapticsThresholdPx) - } - .apply { - LaunchedEffect(hapticsChannel) { - hapticsChannel.receiveAsFlow().throttleLatest(throttleThresholdMs).collect { - hapticType -> - // 'withContext' launches performHapticFeedback in a separate thread, - // as otherwise it produces a visible lag (b/219776664) - val currentTime = System.currentTimeMillis() - debugLog { "Haptics started" } - withContext(Dispatchers.Default) { - debugLog { - "Performing haptics, delay: " + - "${System.currentTimeMillis() - currentTime}" - } - rotaryHaptics.performHapticFeedback(hapticType) - } - } - } - } -} - -@Composable -private fun rememberHapticChannel() = remember { - Channel<RotaryHapticsType>( - capacity = 2, - onBufferOverflow = BufferOverflow.DROP_OLDEST, - ) -} - -@Composable -public fun rememberDefaultRotaryHapticFeedback(): RotaryHapticFeedback = - LocalView.current.let { view -> remember { findDeviceSpecificHapticFeedback(view) } } - -internal fun findDeviceSpecificHapticFeedback(view: View): RotaryHapticFeedback = - if (isSamsungWatch()) { - SamsungWatchHapticFeedback(view) - } else { - DefaultRotaryHapticFeedback(view) - } - -/** Default Rotary implementation for [RotaryHapticFeedback] */ -class DefaultRotaryHapticFeedback(private val view: View) : RotaryHapticFeedback { - - override fun performHapticFeedback( - type: RotaryHapticsType, - ) { - when (type) { - RotaryHapticsType.ScrollItemFocus -> { - view.performHapticFeedback(SCROLL_ITEM_FOCUS) - } - RotaryHapticsType.ScrollTick -> { - view.performHapticFeedback(SCROLL_TICK) - } - RotaryHapticsType.ScrollLimit -> { - view.performHapticFeedback(SCROLL_LIMIT) - } - } - } - - private companion object { - // Hidden constants from HapticFeedbackConstants - const val SCROLL_TICK: Int = 18 - const val SCROLL_ITEM_FOCUS: Int = 19 - const val SCROLL_LIMIT: Int = 20 - } -} - -/** Implementation of [RotaryHapticFeedback] for Samsung devices */ -private class SamsungWatchHapticFeedback(private val view: View) : RotaryHapticFeedback { - override fun performHapticFeedback( - type: RotaryHapticsType, - ) { - when (type) { - RotaryHapticsType.ScrollItemFocus -> { - view.performHapticFeedback(102) - } - RotaryHapticsType.ScrollTick -> { - view.performHapticFeedback(102) - } - RotaryHapticsType.ScrollLimit -> { - view.performHapticFeedback(50107) - } - } - } -} - -private fun isSamsungWatch(): Boolean = Build.MANUFACTURER.contains("Samsung", ignoreCase = true) diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Rotary.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Rotary.kt deleted file mode 100644 index 19a6ea671..000000000 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/Rotary.kt +++ /dev/null @@ -1,1232 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput - -import android.view.ViewConfiguration -import androidx.compose.animation.core.AnimationState -import androidx.compose.animation.core.CubicBezierEasing -import androidx.compose.animation.core.Easing -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.SpringSpec -import androidx.compose.animation.core.animateTo -import androidx.compose.animation.core.copy -import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween -import androidx.compose.foundation.MutatePriority -import androidx.compose.foundation.focusable -import androidx.compose.foundation.gestures.FlingBehavior -import androidx.compose.foundation.gestures.ScrollableDefaults -import androidx.compose.foundation.gestures.ScrollableState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshots.Snapshot -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.input.rotary.RotaryInputModifierNode -import androidx.compose.ui.input.rotary.RotaryScrollEvent -import androidx.compose.ui.node.ModifierNodeElement -import androidx.compose.ui.platform.InspectorInfo -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.platform.debugInspectorInfo -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.util.fastSumBy -import androidx.compose.ui.util.lerp -import androidx.wear.compose.foundation.ExperimentalWearFoundationApi -import androidx.wear.compose.foundation.lazy.ScalingLazyListState -import androidx.wear.compose.foundation.rememberActiveFocusRequester -import kotlin.math.abs -import kotlin.math.absoluteValue -import kotlin.math.sign -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.async -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.flow.transformLatest -import kotlinx.coroutines.launch - -// This file is a copy of Rotary.kt from Horologist (go/horologist), -// remove it once Wear Compose 1.4 is landed (b/325560444). - -/** - * A modifier which connects rotary events with scrollable. This modifier supports scroll with - * fling. - * - * @param scrollableState Scrollable state which will be scrolled while receiving rotary events - * @param focusRequester Requests the focus for rotary input. By default comes from - * [rememberActiveFocusRequester], which is used with [HierarchicalFocusCoordinator] - * @param flingBehavior Logic describing fling behavior. If null fling will not happen. - * @param rotaryHaptics Class which will handle haptic feedback - * @param reverseDirection Reverse the direction of scrolling. Should be aligned with Scrollable - * `reverseDirection` parameter - */ -@OptIn(ExperimentalWearFoundationApi::class) -@Suppress("ComposableModifierFactory") -@Composable -fun Modifier.rotaryWithScroll( - scrollableState: ScrollableState, - focusRequester: FocusRequester = rememberActiveFocusRequester(), - flingBehavior: FlingBehavior? = ScrollableDefaults.flingBehavior(), - rotaryHaptics: RotaryHapticHandler = rememberRotaryHapticHandler(scrollableState), - reverseDirection: Boolean = false, -): Modifier = - rotaryHandler( - rotaryScrollHandler = - RotaryDefaults.rememberFlingHandler(scrollableState, flingBehavior), - reverseDirection = reverseDirection, - rotaryHaptics = rotaryHaptics, - inspectorInfo = - debugInspectorInfo { - name = "rotaryWithFling" - properties["scrollableState"] = scrollableState - properties["focusRequester"] = focusRequester - properties["flingBehavior"] = flingBehavior - properties["rotaryHaptics"] = rotaryHaptics - properties["reverseDirection"] = reverseDirection - }, - ) - .focusRequester(focusRequester) - .focusable() - -/** - * A modifier which connects rotary events with scrollable. This modifier supports snap. - * - * @param focusRequester Requests the focus for rotary input. By default comes from - * [rememberActiveFocusRequester], which is used with [HierarchicalFocusCoordinator] - * @param rotaryScrollAdapter A connection between scrollable objects and rotary events - * @param rotaryHaptics Class which will handle haptic feedback - * @param reverseDirection Reverse the direction of scrolling. Should be aligned with Scrollable - * `reverseDirection` parameter - */ -@OptIn(ExperimentalWearFoundationApi::class) -@Suppress("ComposableModifierFactory") -@Composable -fun Modifier.rotaryWithSnap( - rotaryScrollAdapter: RotaryScrollAdapter, - focusRequester: FocusRequester = rememberActiveFocusRequester(), - snapParameters: SnapParameters = RotaryDefaults.snapParametersDefault, - rotaryHaptics: RotaryHapticHandler = - rememberRotaryHapticHandler(rotaryScrollAdapter.scrollableState), - reverseDirection: Boolean = false, -): Modifier = - rotaryHandler( - rotaryScrollHandler = - RotaryDefaults.rememberSnapHandler(rotaryScrollAdapter, snapParameters), - reverseDirection = reverseDirection, - rotaryHaptics = rotaryHaptics, - inspectorInfo = - debugInspectorInfo { - name = "rotaryWithFling" - properties["rotaryScrollAdapter"] = rotaryScrollAdapter - properties["focusRequester"] = focusRequester - properties["snapParameters"] = snapParameters - properties["rotaryHaptics"] = rotaryHaptics - properties["reverseDirection"] = reverseDirection - }, - ) - .focusRequester(focusRequester) - .focusable() - -/** An extension function for creating [RotaryScrollAdapter] from [ScalingLazyListState] */ -@Composable -fun ScalingLazyListState.toRotaryScrollAdapter(): RotaryScrollAdapter = - remember(this) { ScalingLazyColumnRotaryScrollAdapter(this) } - -/** An implementation of rotary scroll adapter for [ScalingLazyColumn] */ -class ScalingLazyColumnRotaryScrollAdapter( - override val scrollableState: ScalingLazyListState, -) : RotaryScrollAdapter { - - /** Calculates an average height of an item by taking an average from visible items height. */ - override fun averageItemSize(): Float { - val visibleItems = scrollableState.layoutInfo.visibleItemsInfo - return (visibleItems.fastSumBy { it.unadjustedSize } / visibleItems.size).toFloat() - } - - /** Current (centred) item index */ - override fun currentItemIndex(): Int = scrollableState.centerItemIndex - - /** An offset from the item centre */ - override fun currentItemOffset(): Float = scrollableState.centerItemScrollOffset.toFloat() - - /** The total count of items in ScalingLazyColumn */ - override fun totalItemsCount(): Int = scrollableState.layoutInfo.totalItemsCount -} - -/** An adapter which connects scrollableState to Rotary */ -interface RotaryScrollAdapter { - - /** A scrollable state. Used for performing scroll when Rotary events received */ - val scrollableState: ScrollableState - - /** Average size of an item. Used for estimating the scrollable distance */ - fun averageItemSize(): Float - - /** A current item index. Used for scrolling */ - fun currentItemIndex(): Int - - /** An offset from the centre or the border of the current item. */ - fun currentItemOffset(): Float - - /** The total count of items in [scrollableState] */ - fun totalItemsCount(): Int -} - -/** Defaults for rotary modifiers */ -object RotaryDefaults { - - /** Returns default [SnapParameters] */ - val snapParametersDefault: SnapParameters = - SnapParameters( - snapOffset = 0, - thresholdDivider = 1.5f, - resistanceFactor = 3f, - ) - - /** Returns whether the input is Low-res (a bezel) or high-res(a crown/rsb). */ - @Composable - fun isLowResInput(): Boolean = - LocalContext.current.packageManager.hasSystemFeature( - "android.hardware.rotaryencoder.lowres" - ) - - /** - * Handles scroll with fling. - * - * @param scrollableState Scrollable state which will be scrolled while receiving rotary events - * @param flingBehavior Logic describing Fling behavior. If null - fling will not happen - * @param isLowRes Whether the input is Low-res (a bezel) or high-res(a crown/rsb) - */ - @Composable - internal fun rememberFlingHandler( - scrollableState: ScrollableState, - flingBehavior: FlingBehavior? = null, - isLowRes: Boolean = isLowResInput(), - ): RotaryScrollHandler { - val viewConfiguration = ViewConfiguration.get(LocalContext.current) - - return remember(scrollableState, flingBehavior, isLowRes) { - // Remove unnecessary recompositions by disabling tracking of changes inside of - // this block. This algorithm properly reads all updated values and - // don't need recomposition when those values change. - Snapshot.withoutReadObservation { - debugLog { "isLowRes : $isLowRes" } - fun rotaryFlingBehavior() = - flingBehavior?.run { - RotaryFlingBehavior( - scrollableState, - flingBehavior, - viewConfiguration, - flingTimeframe = - if (isLowRes) lowResFlingTimeframe else highResFlingTimeframe, - ) - } - - fun scrollBehavior() = RotaryScrollBehavior(scrollableState) - - if (isLowRes) { - LowResRotaryScrollHandler( - rotaryFlingBehaviorFactory = { rotaryFlingBehavior() }, - scrollBehaviorFactory = { scrollBehavior() }, - ) - } else { - HighResRotaryScrollHandler( - rotaryFlingBehaviorFactory = { rotaryFlingBehavior() }, - scrollBehaviorFactory = { scrollBehavior() }, - ) - } - } - } - } - - /** - * Handles scroll with snap - * - * @param rotaryScrollAdapter A connection between scrollable objects and rotary events - * @param snapParameters Snap parameters - */ - @Composable - internal fun rememberSnapHandler( - rotaryScrollAdapter: RotaryScrollAdapter, - snapParameters: SnapParameters = snapParametersDefault, - isLowRes: Boolean = isLowResInput(), - ): RotaryScrollHandler { - return remember(rotaryScrollAdapter, snapParameters) { - // Remove unnecessary recompositions by disabling tracking of changes inside of - // this block. This algorithm properly reads all updated values and - // don't need recomposition when those values change. - Snapshot.withoutReadObservation { - debugLog { "isLowRes : $isLowRes" } - if (isLowRes) { - LowResSnapHandler( - snapBehaviourFactory = { - RotarySnapBehavior(rotaryScrollAdapter, snapParameters) - }, - ) - } else { - HighResSnapHandler( - resistanceFactor = snapParameters.resistanceFactor, - thresholdBehaviorFactory = { - ThresholdBehavior( - rotaryScrollAdapter, - snapParameters.thresholdDivider, - ) - }, - snapBehaviourFactory = { - RotarySnapBehavior(rotaryScrollAdapter, snapParameters) - }, - scrollBehaviourFactory = { - RotaryScrollBehavior(rotaryScrollAdapter.scrollableState) - }, - ) - } - } - } - } - - private val lowResFlingTimeframe: Long = 100L - private val highResFlingTimeframe: Long = 30L -} - -/** - * Parameters used for snapping - * - * @param snapOffset an optional offset to be applied when snapping the item. After the snap the - * snapped items offset will be [snapOffset]. - */ -class SnapParameters( - val snapOffset: Int, - val thresholdDivider: Float, - val resistanceFactor: Float, -) { - /** Returns a snapping offset in [Dp] */ - @Composable - fun snapOffsetDp(): Dp { - return with(LocalDensity.current) { snapOffset.toDp() } - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - - other as SnapParameters - - if (snapOffset != other.snapOffset) return false - if (thresholdDivider != other.thresholdDivider) return false - if (resistanceFactor != other.resistanceFactor) return false - - return true - } - - override fun hashCode(): Int { - var result = snapOffset - result = 31 * result + thresholdDivider.hashCode() - result = 31 * result + resistanceFactor.hashCode() - return result - } -} - -/** An interface for handling scroll events */ -internal interface RotaryScrollHandler { - /** - * Handles scrolling events - * - * @param coroutineScope A scope for performing async actions - * @param event A scrollable event from rotary input, containing scrollable delta and timestamp - * @param rotaryHaptics - */ - suspend fun handleScrollEvent( - coroutineScope: CoroutineScope, - event: TimestampedDelta, - rotaryHaptics: RotaryHapticHandler, - ) -} - -/** - * Class responsible for Fling behaviour with rotary. It tracks and produces the fling when - * necessary - */ -internal class RotaryFlingBehavior( - private val scrollableState: ScrollableState, - private val flingBehavior: FlingBehavior, - viewConfiguration: ViewConfiguration, - private val flingTimeframe: Long, -) { - - // A time range during which the fling is valid. - // For simplicity it's twice as long as [flingTimeframe] - private val timeRangeToFling = flingTimeframe * 2 - - // A default fling factor for making fling slower - private val flingScaleFactor = 0.7f - - private var previousVelocity = 0f - - private val rotaryVelocityTracker = RotaryVelocityTracker() - - private val minFlingSpeed = viewConfiguration.scaledMinimumFlingVelocity.toFloat() - private val maxFlingSpeed = viewConfiguration.scaledMaximumFlingVelocity.toFloat() - private var latestEventTimestamp: Long = 0 - - private var flingVelocity: Float = 0f - private var flingTimestamp: Long = 0 - - /** Starts a new fling tracking session with specified timestamp */ - fun startFlingTracking(timestamp: Long) { - rotaryVelocityTracker.start(timestamp) - latestEventTimestamp = timestamp - previousVelocity = 0f - } - - /** Observing new event within a fling tracking session with new timestamp and delta */ - fun observeEvent(timestamp: Long, delta: Float) { - rotaryVelocityTracker.move(timestamp, delta) - latestEventTimestamp = timestamp - } - - /** Performing fling if necessary and calling [beforeFling] lambda before it is triggered */ - suspend fun trackFling(beforeFling: () -> Unit) { - val currentVelocity = rotaryVelocityTracker.velocity - debugLog { "currentVelocity: $currentVelocity" } - - if (abs(currentVelocity) >= abs(previousVelocity)) { - flingTimestamp = latestEventTimestamp - flingVelocity = currentVelocity * flingScaleFactor - } - previousVelocity = currentVelocity - - // Waiting for a fixed amount of time before checking the fling - delay(flingTimeframe) - - // For making a fling 2 criteria should be met: - // 1) no more than - // `rangeToFling` ms should pass between last fling detection - // and the time of last motion event - // 2) flingVelocity should exceed the minFlingSpeed - debugLog { - "Check fling: flingVelocity: $flingVelocity " + - "minFlingSpeed: $minFlingSpeed, maxFlingSpeed: $maxFlingSpeed" - } - if ( - latestEventTimestamp - flingTimestamp < timeRangeToFling && - abs(flingVelocity) > minFlingSpeed - ) { - // Stops scrollAnimationCoroutine because a fling will be performed - beforeFling() - val velocity = flingVelocity.coerceIn(-maxFlingSpeed, maxFlingSpeed) - scrollableState.scroll(MutatePriority.UserInput) { - with(flingBehavior) { - debugLog { "Flinging with velocity $velocity" } - performFling(velocity) - } - } - } - } -} - -/** - * A rotary event object which contains a [timestamp] of the rotary event and a scrolled [delta]. - */ -internal data class TimestampedDelta(val timestamp: Long, val delta: Float) - -/** - * This class does a smooth animation when the scroll by N pixels is done. This animation works well - * on Rsb(high-res) and Bezel(low-res) devices. - */ -internal class RotaryScrollBehavior( - private val scrollableState: ScrollableState, -) { - private var sequentialAnimation = false - private var scrollAnimation = AnimationState(0f) - private var prevPosition = 0f - - /** Handles scroll event to [targetValue] */ - suspend fun handleEvent(targetValue: Float) { - scrollableState.scroll(MutatePriority.UserInput) { - debugLog { "ScrollAnimation value before start: ${scrollAnimation.value}" } - - scrollAnimation.animateTo( - targetValue, - animationSpec = spring(), - sequentialAnimation = sequentialAnimation, - ) { - val delta = value - prevPosition - debugLog { "Animated by $delta, value: $value" } - scrollBy(delta) - prevPosition = value - sequentialAnimation = value != this.targetValue - } - } - } -} - -/** - * A helper class for snapping with rotary. Uses animateScrollToItem method for snapping to the Nth - * item. - */ -internal class RotarySnapBehavior( - private val rotaryScrollAdapter: RotaryScrollAdapter, - private val snapParameters: SnapParameters, -) { - private var snapTarget: Int = rotaryScrollAdapter.currentItemIndex() - private var sequentialSnap: Boolean = false - - private var anim = AnimationState(0f) - private var expectedDistance = 0f - - private val defaultStiffness = 200f - private var snapTargetUpdated = true - - /** - * Preparing snapping. This method should be called before [snapToTargetItem] is called. - * - * Snapping is done for current + [moveForElements] items. - * - * If [sequentialSnap] is true, items are summed up together. For example, if - * [prepareSnapForItems] is called with [moveForElements] = 2, 3, 5 -> then the snapping will - * happen to current + 10 items - * - * If [sequentialSnap] is false, then [moveForElements] are not summed up together. - */ - fun prepareSnapForItems(moveForElements: Int, sequentialSnap: Boolean) { - this.sequentialSnap = sequentialSnap - if (sequentialSnap) { - snapTarget += moveForElements - } else { - snapTarget = rotaryScrollAdapter.currentItemIndex() + moveForElements - } - snapTargetUpdated = true - snapTarget = snapTarget.coerceIn(0 until rotaryScrollAdapter.totalItemsCount()) - } - - /** Performs snapping to the closest item. */ - suspend fun snapToClosestItem() { - // Snapping to the closest item by using performFling method with 0 speed - rotaryScrollAdapter.scrollableState.scroll(MutatePriority.UserInput) { - debugLog { "snap to closest item" } - var prevPosition = 0f - AnimationState(0f).animateTo( - targetValue = -rotaryScrollAdapter.currentItemOffset(), - animationSpec = tween(durationMillis = 100, easing = FastOutSlowInEasing), - ) { - val animDelta = value - prevPosition - scrollBy(animDelta) - prevPosition = value - } - snapTarget = rotaryScrollAdapter.currentItemIndex() - } - } - - /** Returns true if top edge was reached */ - fun topEdgeReached(): Boolean = snapTarget <= 0 - - /** Returns true if bottom edge was reached */ - fun bottomEdgeReached(): Boolean = snapTarget >= rotaryScrollAdapter.totalItemsCount() - 1 - - /** Performs snapping to the specified in [prepareSnapForItems] element */ - suspend fun snapToTargetItem() { - if (sequentialSnap) { - anim = anim.copy(0f) - } else { - anim = AnimationState(0f) - } - rotaryScrollAdapter.scrollableState.scroll(MutatePriority.UserInput) { - // If snapTargetUpdated is true - then the target was updated so we - // need to do snap again - while (snapTargetUpdated) { - snapTargetUpdated = false - var latestCenterItem: Int - var continueFirstScroll = true - debugLog { "snapTarget $snapTarget" } - while (continueFirstScroll) { - latestCenterItem = rotaryScrollAdapter.currentItemIndex() - anim = anim.copy(0f) - expectedDistance = expectedDistanceTo(snapTarget, snapParameters.snapOffset) - debugLog { - "expectedDistance = $expectedDistance, " + - "scrollableState.centerItemScrollOffset " + - "${rotaryScrollAdapter.currentItemOffset()}" - } - continueFirstScroll = false - var prevPosition = 0f - - anim.animateTo( - expectedDistance, - animationSpec = - SpringSpec( - stiffness = defaultStiffness, - visibilityThreshold = 0.1f, - ), - sequentialAnimation = (anim.velocity != 0f), - ) { - val animDelta = value - prevPosition - debugLog { - "First animation, value:$value, velocity:$velocity, " + - "animDelta:$animDelta" - } - - // Exit animation if snap target was updated - if (snapTargetUpdated) cancelAnimation() - - scrollBy(animDelta) - prevPosition = value - - if (latestCenterItem != rotaryScrollAdapter.currentItemIndex()) { - continueFirstScroll = true - cancelAnimation() - return@animateTo - } - - debugLog { "centerItemIndex = ${rotaryScrollAdapter.currentItemIndex()}" } - if (rotaryScrollAdapter.currentItemIndex() == snapTarget) { - debugLog { "Target is visible. Cancelling first animation" } - debugLog { - "scrollableState.centerItemScrollOffset " + - "${rotaryScrollAdapter.currentItemOffset()}" - } - expectedDistance = -rotaryScrollAdapter.currentItemOffset() - continueFirstScroll = false - cancelAnimation() - return@animateTo - } - } - } - // Exit animation if snap target was updated - if (snapTargetUpdated) continue - - anim = anim.copy(0f) - var prevPosition = 0f - anim.animateTo( - expectedDistance, - animationSpec = - SpringSpec( - stiffness = defaultStiffness, - visibilityThreshold = 0.1f, - ), - sequentialAnimation = (anim.velocity != 0f), - ) { - // Exit animation if snap target was updated - if (snapTargetUpdated) cancelAnimation() - - val animDelta = value - prevPosition - debugLog { "Final animation. velocity:$velocity, animDelta:$animDelta" } - scrollBy(animDelta) - prevPosition = value - } - } - } - } - - private fun expectedDistanceTo(index: Int, targetScrollOffset: Int): Float { - val averageSize = rotaryScrollAdapter.averageItemSize() - val indexesDiff = index - rotaryScrollAdapter.currentItemIndex() - debugLog { "Average size $averageSize" } - return (averageSize * indexesDiff) + targetScrollOffset - - rotaryScrollAdapter.currentItemOffset() - } -} - -/** - * A modifier which handles rotary events. It accepts ScrollHandler as the input - a class where - * main logic about how scroll should be handled is lying - */ -internal fun Modifier.rotaryHandler( - rotaryScrollHandler: RotaryScrollHandler, - reverseDirection: Boolean, - rotaryHaptics: RotaryHapticHandler, - inspectorInfo: InspectorInfo.() -> Unit, -): Modifier = - this then - RotaryHandlerElement( - rotaryScrollHandler, - reverseDirection, - rotaryHaptics, - inspectorInfo, - ) - -/** - * Batching requests for scrolling events. This function combines all events together (except first) - * within specified timeframe. Should help with performance on high-res devices. - */ -@OptIn(ExperimentalCoroutinesApi::class) -internal fun Flow<TimestampedDelta>.batchRequestsWithinTimeframe( - timeframe: Long -): Flow<TimestampedDelta> { - var delta = 0f - var lastTimestamp = -timeframe - return if (timeframe == 0L) { - this - } else { - this.transformLatest { - delta += it.delta - debugLog { "Batching requests. delta:$delta" } - if (lastTimestamp + timeframe <= it.timestamp) { - lastTimestamp = it.timestamp - debugLog { "No events before, delta= $delta" } - emit(TimestampedDelta(it.timestamp, delta)) - } else { - delay(timeframe) - debugLog { "After delay, delta= $delta" } - if (delta > 0f) { - emit(TimestampedDelta(it.timestamp, delta)) - } - } - delta = 0f - } - } -} - -/** - * A scroll handler for RSB(high-res) without snapping and with or without fling A list is scrolled - * by the number of pixels received from the rotary device. - * - * This class is a little bit different from LowResScrollHandler class - it has a filtering for - * events which are coming with wrong sign ( this happens to rsb devices, especially at the end of - * the scroll) - * - * This scroll handler supports fling. It can be set with [RotaryFlingBehavior]. - */ -internal class HighResRotaryScrollHandler( - private val rotaryFlingBehaviorFactory: () -> RotaryFlingBehavior?, - private val scrollBehaviorFactory: () -> RotaryScrollBehavior, - private val hapticsThreshold: Long = 50, -) : RotaryScrollHandler { - - // This constant is specific for high-res devices. Because that input values - // can sometimes come with different sign, we have to filter them in this threshold - private val gestureThresholdTime = 200L - private var scrollJob: Job = CompletableDeferred<Unit>() - private var flingJob: Job = CompletableDeferred<Unit>() - - private var previousScrollEventTime = 0L - private var rotaryScrollDistance = 0f - - private var rotaryFlingBehavior: RotaryFlingBehavior? = rotaryFlingBehaviorFactory() - private var scrollBehavior: RotaryScrollBehavior = scrollBehaviorFactory() - - override suspend fun handleScrollEvent( - coroutineScope: CoroutineScope, - event: TimestampedDelta, - rotaryHaptics: RotaryHapticHandler, - ) { - val time = event.timestamp - val isOppositeScrollValue = isOppositeValueAfterScroll(event.delta) - - if (isNewScrollEvent(time)) { - debugLog { "New scroll event" } - resetTracking(time) - rotaryScrollDistance = event.delta - } else { - // Due to the physics of Rotary side button, some events might come - // with an opposite axis value - either at the start or at the end of the motion. - // We don't want to use these values for fling calculations. - if (!isOppositeScrollValue) { - rotaryFlingBehavior?.observeEvent(event.timestamp, event.delta) - } else { - debugLog { "Opposite value after scroll :${event.delta}" } - } - rotaryScrollDistance += event.delta - } - - scrollJob.cancel() - - rotaryHaptics.handleScrollHaptic(event.delta) - debugLog { "Rotary scroll distance: $rotaryScrollDistance" } - - previousScrollEventTime = time - scrollJob = coroutineScope.async { scrollBehavior.handleEvent(rotaryScrollDistance) } - - if (rotaryFlingBehavior != null) { - flingJob.cancel() - flingJob = - coroutineScope.async { - rotaryFlingBehavior?.trackFling( - beforeFling = { - debugLog { "Calling before fling section" } - scrollJob.cancel() - scrollBehavior = scrollBehaviorFactory() - } - ) - } - } - } - - private fun isOppositeValueAfterScroll(delta: Float): Boolean = - rotaryScrollDistance * delta < 0f && (abs(delta) < abs(rotaryScrollDistance)) - - private fun isNewScrollEvent(timestamp: Long): Boolean { - val timeDelta = timestamp - previousScrollEventTime - return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime - } - - private fun resetTracking(timestamp: Long) { - scrollBehavior = scrollBehaviorFactory() - rotaryFlingBehavior = rotaryFlingBehaviorFactory() - rotaryFlingBehavior?.startFlingTracking(timestamp) - } -} - -/** - * A scroll handler for Bezel(low-res) without snapping. This scroll handler supports fling. It can - * be set with RotaryFlingBehavior. - */ -internal class LowResRotaryScrollHandler( - private val rotaryFlingBehaviorFactory: () -> RotaryFlingBehavior?, - private val scrollBehaviorFactory: () -> RotaryScrollBehavior, -) : RotaryScrollHandler { - - private val gestureThresholdTime = 200L - private var previousScrollEventTime = 0L - private var rotaryScrollDistance = 0f - - private var scrollJob: Job = CompletableDeferred<Unit>() - private var flingJob: Job = CompletableDeferred<Unit>() - - private var rotaryFlingBehavior: RotaryFlingBehavior? = rotaryFlingBehaviorFactory() - private var scrollBehavior: RotaryScrollBehavior = scrollBehaviorFactory() - - override suspend fun handleScrollEvent( - coroutineScope: CoroutineScope, - event: TimestampedDelta, - rotaryHaptics: RotaryHapticHandler, - ) { - val time = event.timestamp - - if (isNewScrollEvent(time)) { - resetTracking(time) - rotaryScrollDistance = event.delta - } else { - rotaryFlingBehavior?.observeEvent(event.timestamp, event.delta) - rotaryScrollDistance += event.delta - } - - scrollJob.cancel() - flingJob.cancel() - - rotaryHaptics.handleScrollHaptic(event.delta) - debugLog { "Rotary scroll distance: $rotaryScrollDistance" } - - previousScrollEventTime = time - scrollJob = coroutineScope.async { scrollBehavior.handleEvent(rotaryScrollDistance) } - - flingJob = - coroutineScope.async { - rotaryFlingBehavior?.trackFling( - beforeFling = { - debugLog { "Calling before fling section" } - scrollJob.cancel() - scrollBehavior = scrollBehaviorFactory() - }, - ) - } - } - - private fun isNewScrollEvent(timestamp: Long): Boolean { - val timeDelta = timestamp - previousScrollEventTime - return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime - } - - private fun resetTracking(timestamp: Long) { - scrollBehavior = scrollBehaviorFactory() - debugLog { "Velocity tracker reset" } - rotaryFlingBehavior = rotaryFlingBehaviorFactory() - rotaryFlingBehavior?.startFlingTracking(timestamp) - } -} - -/** - * A scroll handler for RSB(high-res) with snapping and without fling Snapping happens after a - * threshold is reached ( set in [RotarySnapBehavior]) - * - * This scroll handler doesn't support fling. - */ -internal class HighResSnapHandler( - private val resistanceFactor: Float, - private val thresholdBehaviorFactory: () -> ThresholdBehavior, - private val snapBehaviourFactory: () -> RotarySnapBehavior, - private val scrollBehaviourFactory: () -> RotaryScrollBehavior, -) : RotaryScrollHandler { - private val gestureThresholdTime = 200L - private val snapDelay = 100L - private val maxSnapsPerEvent = 2 - - private var scrollJob: Job = CompletableDeferred<Unit>() - private var snapJob: Job = CompletableDeferred<Unit>() - - private var previousScrollEventTime = 0L - private var snapAccumulator = 0f - private var rotaryScrollDistance = 0f - private var scrollInProgress = false - - private var snapBehaviour = snapBehaviourFactory() - private var scrollBehaviour = scrollBehaviourFactory() - private var thresholdBehavior = thresholdBehaviorFactory() - - private val scrollEasing: Easing = CubicBezierEasing(0.0f, 0.0f, 0.5f, 1.0f) - - override suspend fun handleScrollEvent( - coroutineScope: CoroutineScope, - event: TimestampedDelta, - rotaryHaptics: RotaryHapticHandler, - ) { - val time = event.timestamp - - if (isNewScrollEvent(time)) { - debugLog { "New scroll event" } - resetTracking() - snapJob.cancel() - snapBehaviour = snapBehaviourFactory() - scrollBehaviour = scrollBehaviourFactory() - thresholdBehavior = thresholdBehaviorFactory() - thresholdBehavior.startThresholdTracking(time) - snapAccumulator = 0f - rotaryScrollDistance = 0f - } - - if (!isOppositeValueAfterScroll(event.delta)) { - thresholdBehavior.observeEvent(event.timestamp, event.delta) - } else { - debugLog { "Opposite value after scroll :${event.delta}" } - } - - thresholdBehavior.applySmoothing() - val snapThreshold = thresholdBehavior.snapThreshold() - - snapAccumulator += event.delta - if (!snapJob.isActive) { - val resistanceCoeff = - 1 - scrollEasing.transform(rotaryScrollDistance.absoluteValue / snapThreshold) - rotaryScrollDistance += event.delta * resistanceCoeff - } - - debugLog { "Snap accumulator: $snapAccumulator" } - debugLog { "Rotary scroll distance: $rotaryScrollDistance" } - - debugLog { "snapThreshold: $snapThreshold" } - previousScrollEventTime = time - - if (abs(snapAccumulator) > snapThreshold) { - scrollInProgress = false - scrollBehaviour = scrollBehaviourFactory() - scrollJob.cancel() - - val snapDistance = - (snapAccumulator / snapThreshold) - .toInt() - .coerceIn(-maxSnapsPerEvent..maxSnapsPerEvent) - snapAccumulator -= snapThreshold * snapDistance - val sequentialSnap = snapJob.isActive - - debugLog { - "Snap threshold reached: snapDistance:$snapDistance, " + - "sequentialSnap: $sequentialSnap, " + - "snap accumulator remaining: $snapAccumulator" - } - if ( - (!snapBehaviour.topEdgeReached() && snapDistance < 0) || - (!snapBehaviour.bottomEdgeReached() && snapDistance > 0) - ) { - rotaryHaptics.handleSnapHaptic(event.delta) - } - - snapBehaviour.prepareSnapForItems(snapDistance, sequentialSnap) - if (!snapJob.isActive) { - snapJob.cancel() - snapJob = - coroutineScope.async { - debugLog { "Snap started" } - try { - snapBehaviour.snapToTargetItem() - } finally { - debugLog { "Snap called finally" } - } - } - } - rotaryScrollDistance = 0f - } else { - if (!snapJob.isActive) { - scrollJob.cancel() - debugLog { "Scrolling for $rotaryScrollDistance/$resistanceFactor px" } - scrollJob = - coroutineScope.async { - scrollBehaviour.handleEvent(rotaryScrollDistance / resistanceFactor) - } - delay(snapDelay) - scrollInProgress = false - scrollBehaviour = scrollBehaviourFactory() - rotaryScrollDistance = 0f - snapAccumulator = 0f - snapBehaviour.prepareSnapForItems(0, false) - - snapJob.cancel() - snapJob = coroutineScope.async { snapBehaviour.snapToClosestItem() } - } - } - } - - private fun isOppositeValueAfterScroll(delta: Float): Boolean = - sign(rotaryScrollDistance) * sign(delta) == -1f && (abs(delta) < abs(rotaryScrollDistance)) - - private fun isNewScrollEvent(timestamp: Long): Boolean { - val timeDelta = timestamp - previousScrollEventTime - return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime - } - - private fun resetTracking() { - scrollInProgress = true - } -} - -/** - * A scroll handler for RSB(high-res) with snapping and without fling Snapping happens after a - * threshold is reached ( set in [RotarySnapBehavior]) - * - * This scroll handler doesn't support fling. - */ -internal class LowResSnapHandler( - private val snapBehaviourFactory: () -> RotarySnapBehavior, -) : RotaryScrollHandler { - private val gestureThresholdTime = 200L - - private var snapJob: Job = CompletableDeferred<Unit>() - - private var previousScrollEventTime = 0L - private var snapAccumulator = 0f - private var scrollInProgress = false - - private var snapBehaviour = snapBehaviourFactory() - - override suspend fun handleScrollEvent( - coroutineScope: CoroutineScope, - event: TimestampedDelta, - rotaryHaptics: RotaryHapticHandler, - ) { - val time = event.timestamp - - if (isNewScrollEvent(time)) { - debugLog { "New scroll event" } - resetTracking() - snapJob.cancel() - snapBehaviour = snapBehaviourFactory() - snapAccumulator = 0f - } - - snapAccumulator += event.delta - - debugLog { "Snap accumulator: $snapAccumulator" } - - previousScrollEventTime = time - - if (abs(snapAccumulator) > 1f) { - scrollInProgress = false - - val snapDistance = sign(snapAccumulator).toInt() - rotaryHaptics.handleSnapHaptic(event.delta) - val sequentialSnap = snapJob.isActive - debugLog { - "Snap threshold reached: snapDistance:$snapDistance, " + - "sequentialSnap: $sequentialSnap, " + - "snap accumulator: $snapAccumulator" - } - - snapBehaviour.prepareSnapForItems(snapDistance, sequentialSnap) - if (!snapJob.isActive) { - snapJob.cancel() - snapJob = - coroutineScope.async { - debugLog { "Snap started" } - try { - snapBehaviour.snapToTargetItem() - } finally { - debugLog { "Snap called finally" } - } - } - } - snapAccumulator = 0f - } - } - - private fun isNewScrollEvent(timestamp: Long): Boolean { - val timeDelta = timestamp - previousScrollEventTime - return previousScrollEventTime == 0L || timeDelta > gestureThresholdTime - } - - private fun resetTracking() { - scrollInProgress = true - } -} - -internal class ThresholdBehavior( - private val rotaryScrollAdapter: RotaryScrollAdapter, - private val thresholdDivider: Float, - private val minVelocity: Float = 300f, - private val maxVelocity: Float = 3000f, - private val smoothingConstant: Float = 0.4f, -) { - private val thresholdDividerEasing: Easing = CubicBezierEasing(0.5f, 0.0f, 0.5f, 1.0f) - - private val rotaryVelocityTracker = RotaryVelocityTracker() - - private var smoothedVelocity = 0f - - fun startThresholdTracking(time: Long) { - rotaryVelocityTracker.start(time) - smoothedVelocity = 0f - } - - fun observeEvent(timestamp: Long, delta: Float) { - rotaryVelocityTracker.move(timestamp, delta) - } - - fun applySmoothing() { - if (rotaryVelocityTracker.velocity != 0.0f) { - // smooth the velocity - smoothedVelocity = - exponentialSmoothing( - currentVelocity = rotaryVelocityTracker.velocity.absoluteValue, - prevVelocity = smoothedVelocity, - smoothingConstant = smoothingConstant, - ) - } - debugLog { "rotaryVelocityTracker velocity: ${rotaryVelocityTracker.velocity}" } - debugLog { "SmoothedVelocity: $smoothedVelocity" } - } - - fun snapThreshold(): Float { - val thresholdDividerFraction = - thresholdDividerEasing.transform( - inverseLerp( - minVelocity, - maxVelocity, - smoothedVelocity, - ), - ) - return rotaryScrollAdapter.averageItemSize() / - lerp( - 1f, - thresholdDivider, - thresholdDividerFraction, - ) - } - - private fun exponentialSmoothing( - currentVelocity: Float, - prevVelocity: Float, - smoothingConstant: Float, - ): Float = smoothingConstant * currentVelocity + (1 - smoothingConstant) * prevVelocity -} - -private data class RotaryHandlerElement( - private val rotaryScrollHandler: RotaryScrollHandler, - private val reverseDirection: Boolean, - private val rotaryHaptics: RotaryHapticHandler, - private val inspectorInfo: InspectorInfo.() -> Unit, -) : ModifierNodeElement<RotaryInputNode>() { - override fun create(): RotaryInputNode = - RotaryInputNode( - rotaryScrollHandler, - reverseDirection, - rotaryHaptics, - ) - - override fun update(node: RotaryInputNode) { - debugLog { "Update launched!" } - node.rotaryScrollHandler = rotaryScrollHandler - node.reverseDirection = reverseDirection - node.rotaryHaptics = rotaryHaptics - } - - override fun InspectorInfo.inspectableProperties() { - inspectorInfo() - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - - other as RotaryHandlerElement - - if (rotaryScrollHandler != other.rotaryScrollHandler) return false - if (reverseDirection != other.reverseDirection) return false - if (rotaryHaptics != other.rotaryHaptics) return false - if (inspectorInfo != other.inspectorInfo) return false - - return true - } - - override fun hashCode(): Int { - var result = rotaryScrollHandler.hashCode() - result = 31 * result + reverseDirection.hashCode() - result = 31 * result + rotaryHaptics.hashCode() - result = 31 * result + inspectorInfo.hashCode() - return result - } -} - -private class RotaryInputNode( - var rotaryScrollHandler: RotaryScrollHandler, - var reverseDirection: Boolean, - var rotaryHaptics: RotaryHapticHandler, -) : RotaryInputModifierNode, Modifier.Node() { - - val channel = Channel<TimestampedDelta>(capacity = Channel.CONFLATED) - val flow = channel.receiveAsFlow() - - override fun onAttach() { - coroutineScope.launch { - flow.collectLatest { - debugLog { - "Scroll event received: " + "delta:${it.delta}, timestamp:${it.timestamp}" - } - rotaryScrollHandler.handleScrollEvent(this, it, rotaryHaptics) - } - } - } - - override fun onRotaryScrollEvent(event: RotaryScrollEvent): Boolean = false - - override fun onPreRotaryScrollEvent(event: RotaryScrollEvent): Boolean { - debugLog { "onPreRotaryScrollEvent" } - channel.trySend( - TimestampedDelta( - event.uptimeMillis, - event.verticalScrollPixels * if (reverseDirection) -1f else 1f, - ), - ) - return true - } -} - -private fun inverseLerp(start: Float, stop: Float, value: Float): Float { - return ((value - start) / (stop - start)).coerceIn(0f, 1f) -} - -/** Debug logging that can be enabled. */ -private const val DEBUG = false - -private inline fun debugLog(generateMsg: () -> String) { - if (DEBUG) { - println("RotaryScroll: ${generateMsg()}") - } -} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/RotaryVelocityTracker.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/RotaryVelocityTracker.kt deleted file mode 100644 index 1719ecef3..000000000 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/rotaryinput/RotaryVelocityTracker.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.permissioncontroller.permission.ui.wear.elements.rotaryinput - -import androidx.compose.ui.input.pointer.util.VelocityTracker1D - -// This file is a copy of RotaryVelocityTracker.kt from Horologist (go/horologist), -// remove it once Wear Compose 1.4 is landed (b/325560444). - -/** A wrapper around VelocityTracker1D to provide support for rotary input. */ -class RotaryVelocityTracker { - private var velocityTracker: VelocityTracker1D = VelocityTracker1D(true) - - /** Retrieve the last computed velocity. */ - val velocity: Float - get() = velocityTracker.calculateVelocity() - - /** Start tracking motion. */ - fun start(currentTime: Long) { - velocityTracker.resetTracking() - velocityTracker.addDataPoint(currentTime, 0f) - } - - /** Continue tracking motion as the input rotates. */ - fun move(currentTime: Long, delta: Float) { - velocityTracker.addDataPoint(currentTime, delta) - } - - /** Stop tracking motion. */ - fun end() { - velocityTracker.resetTracking() - } -} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearLocationProviderInterceptDialogViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearLocationProviderInterceptDialogViewModel.kt index 009ff952c..ec7647f3c 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearLocationProviderInterceptDialogViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/model/WearLocationProviderInterceptDialogViewModel.kt @@ -32,10 +32,14 @@ import com.android.permissioncontroller.permission.utils.Utils class WearLocationProviderInterceptDialogViewModel : ViewModel() { private val showDialogLiveData = MutableLiveData<Boolean>() val dialogVisibilityLiveData: LiveData<Boolean> = showDialogLiveData - var locationProviderInterceptDialogArgs: LocationProviderInterceptDialogArgs? = null + private val _locationProviderInterceptDialogArgs = + MutableLiveData<LocationProviderInterceptDialogArgs?>() + var locationProviderInterceptDialogArgs: LiveData<LocationProviderInterceptDialogArgs?> = + _locationProviderInterceptDialogArgs init { showDialogLiveData.value = false + _locationProviderInterceptDialogArgs.value = null } private fun applicationInfo(context: Context, packageName: String): ApplicationInfo? { @@ -51,7 +55,7 @@ class WearLocationProviderInterceptDialogViewModel : ViewModel() { fun showDialog(context: Context, packageName: String) { val applicationInfo = applicationInfo(context, packageName) ?: return val appLabel = Utils.getAppLabel(applicationInfo, context) - locationProviderInterceptDialogArgs = + _locationProviderInterceptDialogArgs.value = LocationProviderInterceptDialogArgs( iconId = R.drawable.ic_dialog_alert_material, titleId = android.R.string.dialog_alert_title, @@ -61,13 +65,13 @@ class WearLocationProviderInterceptDialogViewModel : ViewModel() { onOkButtonClick = { dismissDialog() }, onLocationSettingsClick = { context.startActivity(Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)) - } + }, ) showDialogLiveData.value = true } fun dismissDialog() { - locationProviderInterceptDialogArgs = null + _locationProviderInterceptDialogArgs.value = null showDialogLiveData.value = false } } @@ -75,7 +79,8 @@ class WearLocationProviderInterceptDialogViewModel : ViewModel() { /** Factory for an AppPermissionGroupsRevokeDialogViewModel */ class WearLocationProviderInterceptDialogViewModelFactory : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { - @Suppress("UNCHECKED_CAST") return WearLocationProviderInterceptDialogViewModel() as T + @Suppress("UNCHECKED_CAST") + return WearLocationProviderInterceptDialogViewModel() as T } } @@ -86,5 +91,5 @@ data class LocationProviderInterceptDialogArgs( val okButtonTitleId: Int, val locationSettingsId: Int, val onOkButtonClick: () -> Unit, - val onLocationSettingsClick: () -> Unit + val onLocationSettingsClick: () -> Unit, ) diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt index c7ed0958c..2a40a625f 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/ResourceHelper.kt @@ -27,11 +27,35 @@ internal object ResourceHelper { private const val MATERIAL3_ENABLED_SYSPROP = "persist.cw_build.bluechip.enabled" - val material3Enabled: Boolean + /* This controls in app permission controller experience. */ + private val material3Enabled: Boolean get() { return SystemProperties.getBoolean(MATERIAL3_ENABLED_SYSPROP, false) } + val materialUIVersionInApp: WearPermissionMaterialUIVersion = + if (material3Enabled) { + WearPermissionMaterialUIVersion.MATERIAL3 + } else { + WearPermissionMaterialUIVersion.MATERIAL2_5 + } + + /* + This is to control the permission controller screens in settings. + Currently it is set as false. We will either use the flag or a common property from settings + based on settings implementation when we are ready" */ + private val material3EnabledInSettings: Boolean + get() { + return false + } + + val materialUIVersionInSettings: WearPermissionMaterialUIVersion = + if (material3EnabledInSettings) { + WearPermissionMaterialUIVersion.MATERIAL3 + } else { + WearPermissionMaterialUIVersion.MATERIAL2_5 + } + @DoNotInline fun getColor(context: Context, @ColorRes id: Int): Color? { return try { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt index 8823bee07..736d543a3 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/theme/WearPermissionTheme.kt @@ -53,9 +53,6 @@ fun WearPermissionTheme( if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) { WearPermissionLegacyTheme(content) } else { - // Whether we are ready to use material3 for any screen. - val useBridgedTheme = ResourceHelper.material3Enabled - // Material3 UI controls are still being used in the screen that the theme is applied if (version == MATERIAL3) { val material3Theme = WearOverlayableMaterial3Theme(LocalContext.current) @@ -70,7 +67,7 @@ fun WearPermissionTheme( // But some in-app screens(like permission grant screen) are migrated to material3. // To avoid having two set of overlay resources, we will use material3 overlay resources to // support material2_5 UI controls as well. - else if (version == MATERIAL2_5 && useBridgedTheme) { + else if (version == MATERIAL2_5 && ResourceHelper.materialUIVersionInApp == MATERIAL3) { val material3Theme = WearOverlayableMaterial3Theme(LocalContext.current) val bridgedLegacyTheme = WearMaterialBridgedLegacyTheme.createFrom(material3Theme) MaterialTheme( diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt index a5f78aa53..081a467bd 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt @@ -24,8 +24,8 @@ import android.content.ContextWrapper import android.content.pm.ComponentInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo -import android.os.Looper import android.os.UserHandle +import androidx.arch.core.executor.ArchTaskExecutor import java.util.concurrent.Executors import kotlinx.coroutines.asCoroutineDispatcher @@ -51,8 +51,8 @@ val IPC = Executors.newFixedThreadPool(IPC_THREAD_POOL_COUNT).asCoroutineDispatc /** Assert that an operation is running on main thread */ fun ensureMainThread() = - check(Looper.myLooper() == Looper.getMainLooper()) { - "Only meant to be used on the main thread" + check(ArchTaskExecutor.getInstance().isMainThread) { + ("Only meant to be used on the main thread, current thread is " + Thread.currentThread()) } /** A more readable version of [PackageManager.updatePermissionFlags] */ @@ -72,5 +72,7 @@ fun PackageManager.updatePermissionFlags( val ResolveInfo.componentInfo: ComponentInfo get() { return (activityInfo as ComponentInfo?) - ?: serviceInfo ?: providerInfo ?: throw IllegalStateException("Missing ComponentInfo!") + ?: serviceInfo + ?: providerInfo + ?: throw IllegalStateException("Missing ComponentInfo!") } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt index a3446f802..7f714e083 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt @@ -181,13 +181,18 @@ object PermissionMapping { Manifest.permission_group.CAMERA } - PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS] = Manifest.permission_group.SENSORS - if (SdkLevel.isAtLeastT()) { PLATFORM_PERMISSIONS[Manifest.permission.POST_NOTIFICATIONS] = Manifest.permission_group.NOTIFICATIONS - PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS_BACKGROUND] = + } + + if (!Flags.replaceBodySensorPermissionEnabled()) { + PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS] = Manifest.permission_group.SENSORS + if (SdkLevel.isAtLeastT()) { + PLATFORM_PERMISSIONS[Manifest.permission.BODY_SENSORS_BACKGROUND] = + Manifest.permission_group.SENSORS + } } for ((permission, permissionGroup) in PLATFORM_PERMISSIONS) { @@ -343,7 +348,7 @@ object PermissionMapping { val appSupportsPickerPrompt = group.permissions[Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED]?.isImplicit == - false + false return if (appSupportsPickerPrompt) { PARTIAL_MEDIA_PERMISSIONS @@ -352,12 +357,6 @@ object PermissionMapping { } } - /** Returns true if the given permission is a health platform permission. */ - @JvmStatic - fun isHealthPermission(permissionName: String): Boolean { - return HEALTH_PERMISSIONS_SET.contains(permissionName) - } - /** * Returns the platform permission group for the permission that the provided op backs, if any. */ diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java index 3d3b47272..aae5cb82c 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java @@ -547,8 +547,14 @@ public final class Utils { if (group.equals(Manifest.permission_group.UNDEFINED)) { List<PermissionInfo> undefinedPerms = new ArrayList<>(); for (PermissionInfo permissionInfo : installedRuntime) { + if (Flags.replaceBodySensorPermissionEnabled() + && (permissionInfo.name.equals(Manifest.permission.BODY_SENSORS) || + permissionInfo.name.equals(Manifest.permission.BODY_SENSORS_BACKGROUND))) { + continue; + } + String permGroup = - PermissionMapping.getGroupOfPlatformPermission(permissionInfo.name); + PermissionMapping.getGroupOfPlatformPermission(permissionInfo.name); if (permGroup == null || permGroup.equals(Manifest.permission_group.UNDEFINED)) { undefinedPerms.add(permissionInfo); } diff --git a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING index 93ad3d31b..83b513c04 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING +++ b/PermissionController/src/com/android/permissioncontroller/role/TEST_MAPPING @@ -7,6 +7,9 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsRoleMultiUserTestCases" } ], "mainline-presubmit": [ @@ -24,6 +27,9 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]" } ], "permission-mainline-presubmit": [ @@ -41,6 +47,9 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsRoleMultiUserTestCases" } ], "postsubmit": [ diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java index 234554193..60139f0c8 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/RequestRoleFragment.java @@ -710,7 +710,7 @@ public class RequestRoleFragment extends DialogFragment { checked = mCheckedUserPackage == null; icon = AppCompatResources.getDrawable(context, R.drawable.ic_remove_circle); title = context.getString(R.string.default_app_none); - subtitle = mHolderUserPackage != null ? context.getString( + subtitle = mHolderUserPackage == null ? context.getString( R.string.request_role_current_default) : null; } else { applicationInfo = qualifyingApplication.first; diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt index afee50389..c322b2bef 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListScreen.kt @@ -29,10 +29,10 @@ import androidx.compose.ui.res.stringResource import androidx.lifecycle.LiveData import androidx.wear.compose.material.Text import com.android.permissioncontroller.R -import com.android.permissioncontroller.permission.ui.wear.elements.Chip import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen -import com.android.permissioncontroller.permission.ui.wear.elements.chipDefaultColors -import com.android.permissioncontroller.permission.ui.wear.elements.chipDisabledColors +import com.android.permissioncontroller.permission.ui.wear.elements.material2.Chip +import com.android.permissioncontroller.permission.ui.wear.elements.material2.chipDefaultColors +import com.android.permissioncontroller.permission.ui.wear.elements.material2.chipDisabledColors import com.android.permissioncontroller.role.ui.RoleItem @Composable @@ -65,7 +65,7 @@ fun WearDefaultAppListScreen( onClick = pref.getOnClicked(), modifier = Modifier.fillMaxWidth(), labelMaxLines = Int.MAX_VALUE, - secondaryLabelMaxLines = Integer.MAX_VALUE + secondaryLabelMaxLines = Integer.MAX_VALUE, ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt index 5d4233c6e..0c39ca8c4 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppScreen.kt @@ -25,14 +25,16 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.wear.compose.foundation.lazy.rememberScalingLazyListState import androidx.wear.compose.material.ToggleChipDefaults -import com.android.permissioncontroller.permission.ui.wear.elements.AlertDialog -import com.android.permissioncontroller.permission.ui.wear.elements.ListFooter import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen -import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChip -import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl -import com.android.permissioncontroller.permission.ui.wear.elements.toggleChipDisabledColors +import com.android.permissioncontroller.permission.ui.wear.elements.material2.ListFooter +import com.android.permissioncontroller.permission.ui.wear.elements.material2.ToggleChip +import com.android.permissioncontroller.permission.ui.wear.elements.material2.toggleChipDisabledColors +import com.android.permissioncontroller.permission.ui.wear.elements.material3.DialogButtonContent +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionConfirmationDialog +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType +import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper +import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion import com.android.permissioncontroller.role.ui.wear.model.ConfirmDialogArgs @Composable @@ -41,11 +43,13 @@ fun WearDefaultAppScreen(helper: WearDefaultAppHelper) { val showConfirmDialog = helper.confirmDialogViewModel.showConfirmDialogLiveData.observeAsState(false) var isLoading by remember { mutableStateOf(true) } + val materialUIVersion = ResourceHelper.materialUIVersionInSettings Box { WearDefaultAppContent(isLoading, roleLiveData.value, helper) ConfirmDialog( + materialUIVersion = materialUIVersion, showDialog = showConfirmDialog.value, - args = helper.confirmDialogViewModel.confirmDialogArgs + args = helper.confirmDialogViewModel.confirmDialogArgs, ) } if (isLoading && roleLiveData.value.isNotEmpty()) { @@ -57,7 +61,7 @@ fun WearDefaultAppScreen(helper: WearDefaultAppHelper) { private fun WearDefaultAppContent( isLoading: Boolean, qualifyingApplications: List<Pair<ApplicationInfo, Boolean>>, - helper: WearDefaultAppHelper + helper: WearDefaultAppHelper, ) { ScrollableScreen(title = helper.getTitle(), isLoading = isLoading) { helper.getNonePreference(qualifyingApplications)?.let { @@ -67,8 +71,8 @@ private fun WearDefaultAppContent( icon = it.icon, checked = it.checked, onCheckedChanged = it.onDefaultCheckChanged, - toggleControl = ToggleChipToggleControl.Radio, - labelMaxLine = Integer.MAX_VALUE + toggleControl = WearPermissionToggleControlType.Radio, + labelMaxLine = Integer.MAX_VALUE, ) } } @@ -86,9 +90,9 @@ private fun WearDefaultAppContent( secondaryLabel = pref.summary?.toString(), checked = pref.checked, onCheckedChanged = pref.getOnCheckChanged(), - toggleControl = ToggleChipToggleControl.Radio, + toggleControl = WearPermissionToggleControlType.Radio, labelMaxLine = Integer.MAX_VALUE, - secondaryLabelMaxLine = Integer.MAX_VALUE + secondaryLabelMaxLine = Integer.MAX_VALUE, ) } } @@ -98,14 +102,18 @@ private fun WearDefaultAppContent( } @Composable -private fun ConfirmDialog(showDialog: Boolean, args: ConfirmDialogArgs?) { - args?.let { - AlertDialog( - showDialog = showDialog, - message = it.message, - onOKButtonClick = it.onOkButtonClick, - onCancelButtonClick = it.onCancelButtonClick, - scalingLazyListState = rememberScalingLazyListState() +private fun ConfirmDialog( + materialUIVersion: WearPermissionMaterialUIVersion, + showDialog: Boolean, + args: ConfirmDialogArgs?, +) { + args?.run { + WearPermissionConfirmationDialog( + materialUIVersion = materialUIVersion, + show = showDialog, + message = message, + positiveButtonContent = DialogButtonContent(onClick = onOkButtonClick), + negativeButtonContent = DialogButtonContent(onClick = onCancelButtonClick), ) } } diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt index fcc0d56f9..b4758c88b 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearRequestRoleScreen.kt @@ -32,17 +32,15 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.ui.wear.elements.ScrollableScreen -import com.android.permissioncontroller.permission.ui.wear.elements.ToggleChipToggleControl import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButton import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionButtonStyle import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionIconBuilder import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionListFooter import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControl import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlStyle +import com.android.permissioncontroller.permission.ui.wear.elements.material3.WearPermissionToggleControlType import com.android.permissioncontroller.permission.ui.wear.theme.ResourceHelper import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion -import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL2_5 -import com.android.permissioncontroller.permission.ui.wear.theme.WearPermissionMaterialUIVersion.MATERIAL3 import com.android.permissioncontroller.role.UserPackage import com.android.permissioncontroller.role.ui.ManageRoleHolderStateLiveData @@ -80,14 +78,8 @@ fun WearRequestRoleScreen( helper.initializeSelectedPackage() } } - val materialUIVersion = - if (ResourceHelper.material3Enabled) { - MATERIAL3 - } else { - MATERIAL2_5 - } WearRequestRoleContent( - materialUIVersion, + ResourceHelper.materialUIVersionInApp, isLoading, helper, roleLiveData.value, @@ -137,7 +129,7 @@ internal fun WearRequestRoleContent( onCheckedChanged = { checked -> onCheckedChanged(checked, pref.userPackage, pref.isHolder) }, - toggleControl = ToggleChipToggleControl.Radio, + toggleControl = WearPermissionToggleControlType.Radio, labelMaxLines = Integer.MAX_VALUE, ) } @@ -162,7 +154,7 @@ internal fun WearRequestRoleContent( onCheckedChanged = { checked -> onCheckedChanged(checked, pref.userPackage, pref.isHolder) }, - toggleControl = ToggleChipToggleControl.Radio, + toggleControl = WearPermissionToggleControlType.Radio, ) } pref.subTitle?.let { subTitle -> @@ -183,7 +175,7 @@ internal fun WearRequestRoleContent( enabled = enabled, onCheckedChanged = { checked -> run { onDontAskAgainCheckedChanged(checked) } }, label = stringResource(R.string.request_role_dont_ask_again), - toggleControl = ToggleChipToggleControl.Checkbox, + toggleControl = WearPermissionToggleControlType.Checkbox, style = WearPermissionToggleControlStyle.Transparent, modifier = Modifier.testTag("com.android.permissioncontroller:id/dont_ask_again"), diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java index abf159955..0bef71b3e 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyStatusPreference.java @@ -29,7 +29,6 @@ import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.widget.ImageView; -import android.widget.TextView; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; @@ -42,9 +41,6 @@ import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData; import com.android.permissioncontroller.safetycenter.ui.view.StatusCardView; import com.android.settingslib.widget.GroupSectionDividerMixin; -import kotlin.Pair; - -import java.util.List; import java.util.Objects; /** Preference which displays a visual representation of {@link SafetyCenterStatus}. */ @@ -54,25 +50,16 @@ public class SafetyStatusPreference extends Preference private static final String TAG = "SafetyStatusPreference"; + private final SafetyStatusAnimationSequencer mSequencer = new SafetyStatusAnimationSequencer(); + @Nullable private StatusUiData mStatus; @Nullable private SafetyCenterViewModel mViewModel; - private final TextFadeAnimator mTitleTextAnimator = new TextFadeAnimator(R.id.status_title); - - private final TextFadeAnimator mSummaryTextAnimator = new TextFadeAnimator(R.id.status_summary); - - private final TextFadeAnimator mAllTextAnimator = - new TextFadeAnimator(List.of(R.id.status_title, R.id.status_summary)); - - private boolean mFirstBind = true; - public SafetyStatusPreference(Context context, AttributeSet attrs) { super(context, attrs); setLayoutResource(R.layout.preference_safety_status); } - private boolean mIsTextChangeAnimationRunning; - private final SafetyStatusAnimationSequencer mSequencer = new SafetyStatusAnimationSequencer(); @Override public void onBindViewHolder(PreferenceViewHolder holder) { @@ -93,9 +80,7 @@ public class SafetyStatusPreference extends Preference updateStatusIcon(statusCardView); - updateStatusText(statusCardView.getTitleView(), statusCardView.getSummaryView()); - - mFirstBind = false; + statusCardView.showText(mStatus); } private void configureButtons(Context context, StatusCardView statusCardView) { @@ -125,14 +110,6 @@ public class SafetyStatusPreference extends Preference statusCardView.showButtons(mStatus); } - private void updateStatusText(TextView title, TextView summary) { - if (mFirstBind) { - title.setText(mStatus.getTitle()); - summary.setText(mStatus.getSummary(getContext())); - } - runTextAnimationIfNeeded(title, summary); - } - private void updateStatusIcon(StatusCardView statusCardView) { int severityLevel = mStatus.getSeverityLevel(); boolean isRefreshing = mStatus.isRefreshInProgress(); @@ -143,33 +120,6 @@ public class SafetyStatusPreference extends Preference /* scanningAnimation= */ null); } - private void runTextAnimationIfNeeded(TextView titleView, TextView summaryView) { - if (mIsTextChangeAnimationRunning) { - return; - } - Log.v(TAG, "Starting status text animation"); - String titleText = mStatus.getTitle().toString(); - String summaryText = mStatus.getSummary(getContext()).toString(); - boolean titleEquals = titleView.getText().toString().equals(titleText); - boolean summaryEquals = summaryView.getText().toString().equals(summaryText); - Runnable onFinish = - () -> { - Log.v(TAG, "Finishing status text animation"); - mIsTextChangeAnimationRunning = false; - runTextAnimationIfNeeded(titleView, summaryView); - }; - mIsTextChangeAnimationRunning = !titleEquals || !summaryEquals; - if (!titleEquals && !summaryEquals) { - Pair<TextView, String> titleChange = new Pair<>(titleView, titleText); - Pair<TextView, String> summaryChange = new Pair<>(summaryView, summaryText); - mAllTextAnimator.animateChangeText(List.of(titleChange, summaryChange), onFinish); - } else if (!titleEquals) { - mTitleTextAnimator.animateChangeText(titleView, titleText, onFinish); - } else if (!summaryEquals) { - mSummaryTextAnimator.animateChangeText(summaryView, summaryText, onFinish); - } - } - private void startScanningAnimation(StatusCardView statusCardView) { mSequencer.onStartScanningAnimationStart(); ImageView statusImage = statusCardView.getStatusImageView(); diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt index 6a415c563..bb417104d 100644 --- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt +++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/view/StatusCardView.kt @@ -21,6 +21,7 @@ import android.os.Build import android.util.AttributeSet import android.widget.ImageView import android.widget.LinearLayout +import android.widget.TextSwitcher import android.widget.TextView import androidx.annotation.RequiresApi import androidx.constraintlayout.widget.ConstraintLayout @@ -35,7 +36,7 @@ constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, - defStyleRes: Int = 0 + defStyleRes: Int = 0, ) : ConstraintLayout(context, attrs, defStyleAttr, defStyleRes) { init { @@ -44,11 +45,29 @@ constructor( val statusImageView: ImageView by lazyView(R.id.status_image) val titleAndSummaryContainerView: LinearLayout by lazyView(R.id.status_title_and_summary) - val titleView: TextView by lazyView(R.id.status_title) - val summaryView: TextView by lazyView(R.id.status_summary) + private val titleView: TextSwitcher by lazyView(R.id.status_title) + private val summaryView: TextSwitcher by lazyView(R.id.status_summary) val reviewSettingsButton: MaterialButton by lazyView(R.id.review_settings_button) val rescanButton: MaterialButton by lazyView(R.id.rescan_button) + fun showText(statusUiData: StatusUiData) { + titleView.updateText(statusUiData.title) + summaryView.updateText(statusUiData.getSummary(context)) + } + + private fun TextSwitcher.updateText(newText: CharSequence) { + val currentText: CharSequence? = (currentView as TextView).text + if (currentText == newText) { + return + } + + if (currentText.isNullOrBlank()) { + setCurrentText(newText) + } else { + setText(newText) + } + } + fun showButtons(statusUiData: StatusUiData) { rescanButton.isEnabled = !statusUiData.isRefreshInProgress diff --git a/PermissionController/tests/inprocess/Android.bp b/PermissionController/tests/inprocess/Android.bp index 4cd9e0e6f..49e4e9474 100644 --- a/PermissionController/tests/inprocess/Android.bp +++ b/PermissionController/tests/inprocess/Android.bp @@ -53,6 +53,7 @@ android_test { // This may result in two flag libs being included. This should only be used for Flag //string referencing for test annotations. "com.android.permission.flags-aconfig-java-export", + "android.permission.flags-aconfig-java-export", ], data: [ diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/GetPermissionGroupInfoTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/GetPermissionGroupInfoTest.kt index b20e99c38..d06de169b 100644 --- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/GetPermissionGroupInfoTest.kt +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/GetPermissionGroupInfoTest.kt @@ -18,12 +18,14 @@ package com.android.permissioncontroller.permission import android.content.Context import android.os.Build +import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import com.android.permissioncontroller.permission.utils.PermissionMapping import com.google.common.truth.Truth.assertThat import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit +import org.junit.Rule import org.junit.Test @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S") @@ -32,6 +34,8 @@ class GetPermissionGroupInfoTest { private val packageManager = context.packageManager private val timeoutMs: Long = 10000 + @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule() + @Test fun assertAllPlatformPermGroupPermListsMatch() { val groups = PermissionMapping.getPlatformPermissionGroups() diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/data/AttributionLabelLiveDataTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/data/AttributionLabelLiveDataTest.kt index bc9e5d6ff..7c735a451 100644 --- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/data/AttributionLabelLiveDataTest.kt +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/data/AttributionLabelLiveDataTest.kt @@ -22,10 +22,12 @@ import android.os.Process.myUserHandle import android.os.UserHandle import android.permission.cts.PermissionUtils.install import android.permission.cts.PermissionUtils.uninstallApp +import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.test.platform.app.InstrumentationRegistry import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test private const val APK = "/data/local/tmp/pc-inprocess/AppThatUsesCameraPermission.apk" @@ -34,6 +36,8 @@ private const val PKG = "com.android.permissioncontroller.tests.appthatrequestpe class AttributionLabelLiveDataTest { private val context = InstrumentationRegistry.getInstrumentation().context as Context + @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule() + @Before fun installAttributingApp() { install(APK) diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt index 708d4222f..c7b9ad823 100644 --- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt @@ -16,11 +16,15 @@ package com.android.permissioncontroller.permission.util +import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.android.permissioncontroller.permission.utils.ArrayUtils import com.google.common.truth.Truth.assertThat +import org.junit.Rule import org.junit.Test class ArrayUtilsTest { + @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule() + @Test fun appendString_appendToNull_returnsArrayWithString() { assertThat(ArrayUtils.appendString(null, TEST_STRING)).isEqualTo(arrayOf(TEST_STRING)) diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt index 3d4bd28ff..627d19474 100644 --- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt @@ -16,11 +16,15 @@ package com.android.permissioncontroller.permission.util +import androidx.arch.core.executor.testing.InstantTaskExecutorRule import com.android.permissioncontroller.permission.utils.CollectionUtils import com.google.common.truth.Truth.assertThat +import org.junit.Rule import org.junit.Test class CollectionUtilsTest { + @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule() + @Test fun testContains_true() { val byteArrays = setOf(TEST_BYTE_ARRAY_1, TEST_BYTE_ARRAY_2, TEST_BYTE_ARRAY_3) diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt index 37aa8d988..34c351683 100644 --- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt @@ -30,11 +30,13 @@ import android.graphics.Canvas import android.graphics.ColorFilter import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import com.android.permissioncontroller.permission.utils.KotlinUtils import com.google.common.truth.Truth.assertThat import kotlin.test.assertFailsWith +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any @@ -48,6 +50,8 @@ import org.mockito.Mockito.`when` as whenever class KotlinUtilsTest { private val targetContext = InstrumentationRegistry.getInstrumentation().targetContext + @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule() + @Test fun convertToBitmap_argb888BitmapDrawable_returnsSameBitmap() { val bitmap = Bitmap.createBitmap(/* width= */ 5, /* height= */ 10, Bitmap.Config.ARGB_8888) @@ -64,11 +68,15 @@ class KotlinUtilsTest { class FakeDrawable(private val intrinsicSize: Int) : Drawable() { override fun getIntrinsicWidth() = intrinsicSize + override fun getIntrinsicHeight() = intrinsicSize override fun draw(canvas: Canvas) = Unit // no-op + override fun getOpacity() = throw UnsupportedOperationException() + override fun setAlpha(alpha: Int) = throw UnsupportedOperationException() + override fun setColorFilter(colorFilter: ColorFilter?) = throw UnsupportedOperationException() } diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt index aa7d7da60..29b4e1c4e 100644 --- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt @@ -19,14 +19,31 @@ package com.android.permissioncontroller.permission.util import android.Manifest import android.app.AppOpsManager import android.health.connect.HealthPermissions +import android.os.Build +import android.permission.flags.Flags +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider +import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SdkSuppress import com.android.permissioncontroller.permission.utils.PermissionMapping +import com.android.permissioncontroller.permission.utils.Utils; import com.google.common.truth.Truth.assertThat +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assume.assumeTrue +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PermissionMappingTest { + + @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule() + @JvmField @Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + @Test fun testGetPlatformPermissionGroupForOp_healthPermissionGroup() { assertThat( @@ -76,4 +93,81 @@ class PermissionMappingTest { PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.READ_CONTACTS) ) } + + @Test + fun testHealthPermissionIsRuntime_healthPermissionUiEnabled_isRuntime() { + assumeTrue(Utils.isHealthPermissionUiEnabled()) + + assertThat(PermissionMapping.isRuntimePlatformPermission( + HealthPermissions.READ_HEART_RATE)).isTrue() + } + + @Test + fun testHealthPermissionGroupIsPlatform_healthPermissionUiEnabled_isPlatform() { + assumeTrue(Utils.isHealthPermissionUiEnabled()) + + assertThat(PermissionMapping.isPlatformPermissionGroup( + HealthPermissions.HEALTH_PERMISSION_GROUP)).isTrue() + } + + @Test + fun testGetGroupForHealthPermission_healthPermissionUiEnabled_isHealthPermissionGroup() { + assumeTrue(Utils.isHealthPermissionUiEnabled()) + + assertThat(PermissionMapping.getGroupOfPlatformPermission( + HealthPermissions.READ_HEART_RATE)).isEqualTo( + HealthPermissions.HEALTH_PERMISSION_GROUP) + } + + @Test + fun testGetPermNameForHealthPermissionGroup_healthPermissionUiEnabled_isHealthPermission() { + assumeTrue(Utils.isHealthPermissionUiEnabled()) + + assertThat(PermissionMapping.getPlatformPermissionNamesOfGroup( + HealthPermissions.HEALTH_PERMISSION_GROUP)).contains( + HealthPermissions.READ_HEART_RATE) + } + + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA) + @RequiresFlagsEnabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) + @Test + fun getGroupOfPlatformPermission_replaceBodySensorFlagEnabled_notHaveSensorsGroup() { + assertNull(PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.BODY_SENSORS)) + assertNull( + PermissionMapping.getGroupOfPlatformPermission( + Manifest.permission.BODY_SENSORS_BACKGROUND + ) + ) + } + + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA) + @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) + @Test + fun getGroupOfPlatformPermission_replaceBodySensorFlagDisabled_haveSensorsGroup() { + assertNotNull( + PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.BODY_SENSORS) + ) + assertNotNull( + PermissionMapping.getGroupOfPlatformPermission( + Manifest.permission.BODY_SENSORS_BACKGROUND + ) + ) + } + + + @SdkSuppress( + minSdkVersion = Build.VERSION_CODES.TIRAMISU, + maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + ) + @Test + fun getGroupOfPlatformPermission_preV_haveSensorsGroup() { + assertNotNull( + PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.BODY_SENSORS) + ) + assertNotNull( + PermissionMapping.getGroupOfPlatformPermission( + Manifest.permission.BODY_SENSORS_BACKGROUND + ) + ) + } } diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt index 11bcca356..1cfe6a5d3 100644 --- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt @@ -16,6 +16,8 @@ package com.android.permissioncontroller.permission.util +import android.Manifest.permission.BODY_SENSORS +import android.Manifest.permission.BODY_SENSORS_BACKGROUND import android.Manifest.permission.READ_CONTACTS import android.Manifest.permission_group.ACTIVITY_RECOGNITION import android.Manifest.permission_group.CALENDAR @@ -38,6 +40,14 @@ import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager.NameNotFoundException import android.content.res.Resources +import android.os.Build +import android.permission.flags.Flags +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID import com.android.permissioncontroller.Constants.INVALID_SESSION_ID @@ -46,12 +56,20 @@ import com.android.permissioncontroller.permission.utils.Utils import com.android.permissioncontroller.privacysources.WorkPolicyInfo import com.google.common.truth.Truth.assertThat import kotlin.test.assertFailsWith +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue import org.junit.Ignore +import org.junit.Rule import org.junit.Test class UtilsTest { + + @JvmField @Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + private val context = InstrumentationRegistry.getInstrumentation().targetContext as Context + @JvmField @Rule val instantTaskExecutorRule = InstantTaskExecutorRule() + @Test fun getAbsoluteTimeString_zero_returnsNull() { assertThat(Utils.getAbsoluteTimeString(context, 0)).isNull() @@ -96,6 +114,7 @@ class UtilsTest { fun getBlockedTitle_invalidGroupName_returnsMinusOne() { assertThat(Utils.getBlockedTitle(INVALID_GROUP_NAME)).isEqualTo(-1) } + @Test fun getBlockedTitle_validGroupName() { assertThat(Utils.getBlockedTitle(CAMERA)).isEqualTo(R.string.blocked_camera_title) @@ -295,6 +314,59 @@ class UtilsTest { assertThat(permissionInfos[0].name).isEqualTo(READ_CONTACTS) } + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA) + @RequiresFlagsEnabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) + @Test + fun getInstalledRuntimePermissionInfosForGroup_bodySensorFlagEnabled_bodySensorPermissionsNotIncluded() { + val permissionNamesInUndefinedGroup = + Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, UNDEFINED) + .map { it.name } + val permissionNamesInSensorsGroup = + Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, SENSORS) + .map { it.name } + + assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS)) + assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS_BACKGROUND)) + assertFalse(permissionNamesInSensorsGroup.contains(BODY_SENSORS)) + assertFalse(permissionNamesInSensorsGroup.contains(BODY_SENSORS_BACKGROUND)) + } + + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA) + @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) + @Test + fun getInstalledRuntimePermissionInfosForGroup_bodySensorFlagDisabled_bodySensorPermissionsIncluded() { + val permissionNamesInUndefinedGroup = + Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, UNDEFINED) + .map { it.name } + val permissionNamesInSensorsGroup = + Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, SENSORS) + .map { it.name } + + assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS)) + assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS_BACKGROUND)) + assertTrue(permissionNamesInSensorsGroup.contains(BODY_SENSORS)) + assertTrue(permissionNamesInSensorsGroup.contains(BODY_SENSORS_BACKGROUND)) + } + + @SdkSuppress( + minSdkVersion = Build.VERSION_CODES.TIRAMISU, + maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + ) + @Test + fun getInstalledRuntimePermissionInfosForGroup_preV_bodySensorPermissionsIncluded() { + val permissionNamesInUndefinedGroup = + Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, UNDEFINED) + .map { it.name } + val permissionNamesInSensorsGroup = + Utils.getInstalledRuntimePermissionInfosForGroup(context.packageManager, SENSORS) + .map { it.name } + + assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS)) + assertFalse(permissionNamesInUndefinedGroup.contains(BODY_SENSORS_BACKGROUND)) + assertTrue(permissionNamesInSensorsGroup.contains(BODY_SENSORS)) + assertTrue(permissionNamesInSensorsGroup.contains(BODY_SENSORS_BACKGROUND)) + } + @Test fun getColorResId_validId_returnsNonZero() { assertThat(Utils.getColorResId(context, android.R.attr.colorPrimary)) diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/NotificationListenerCheckInternalTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/NotificationListenerCheckInternalTest.kt index bc00d3bc8..4bb021b3d 100644 --- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/NotificationListenerCheckInternalTest.kt +++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/NotificationListenerCheckInternalTest.kt @@ -30,7 +30,6 @@ import android.safetycenter.SafetyCenterManager import android.safetycenter.SafetyEvent import android.safetycenter.SafetySourceData import android.safetycenter.SafetySourceIssue -import androidx.core.util.Preconditions import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SdkSuppress @@ -464,9 +463,7 @@ class NotificationListenerCheckInternalTest { } val safetySourceIssue = - Preconditions.checkNotNull( - notificationListenerCheck.createSafetySourceIssue(testComponent, 0) - ) + checkNotNull(notificationListenerCheck.createSafetySourceIssue(testComponent, 0)) val expectedId = "notification_listener_${testComponent.flattenToString()}" val expectedTitle = diff --git a/PermissionController/tests/permissionui/Android.bp b/PermissionController/tests/permissionui/Android.bp index 5f177f40c..e0e8fed10 100644 --- a/PermissionController/tests/permissionui/Android.bp +++ b/PermissionController/tests/permissionui/Android.bp @@ -47,6 +47,7 @@ android_test { "androidx.test.ext.truth", "androidx.test.rules", "androidx.test.uiautomator_uiautomator", + "android.permission.flags-aconfig-java-export", "com.android.permission.flags-aconfig-java-export", "compatibility-device-util-axt", "flag-junit", diff --git a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageCustomPermissionsFragmentTest.kt b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageCustomPermissionsFragmentTest.kt index b38f5f40a..08143f77f 100644 --- a/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageCustomPermissionsFragmentTest.kt +++ b/PermissionController/tests/permissionui/src/com/android/permissioncontroller/permissionui/ui/handheld/ManageCustomPermissionsFragmentTest.kt @@ -17,11 +17,18 @@ package com.android.permissioncontroller.permissionui.ui.handheld import android.content.Intent +import android.os.Build +import android.permission.flags.Flags import android.permission.cts.PermissionUtils.grantPermission import android.permission.cts.PermissionUtils.install import android.permission.cts.PermissionUtils.revokePermission import android.permission.cts.PermissionUtils.uninstallApp +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SdkSuppress import androidx.test.uiautomator.By import com.android.compatibility.common.util.SystemUtil.eventually import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity @@ -32,13 +39,18 @@ import com.android.permissioncontroller.permissionui.wakeUpScreen import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith /** Simple tests for {@link ManageCustomPermissionsFragment} */ @RunWith(AndroidJUnit4::class) class ManageCustomPermissionsFragmentTest : BaseHandheldPermissionUiTest() { + + @JvmField @Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + private val ONE_PERMISSION_DEFINER_APK = "/data/local/tmp/pc-permissionui/" + "PermissionUiDefineAdditionalPermissionApp.apk" private val PERMISSION_USER_APK = @@ -95,14 +107,38 @@ class ManageCustomPermissionsFragmentTest : BaseHandheldPermissionUiTest() { eventually { assertThat(getUsageCountsFromUi(PERM_LABEL)).isEqualTo(original) } } + + @SdkSuppress( + minSdkVersion = Build.VERSION_CODES.TIRAMISU, + maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, + ) + @Test + fun testFindBodySensor_preV_labelDisplayed() { + if (waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL)) == null) { + waitFindObject(By.textContains(ADDITIONAL_PERMISSIONS_LABEL)).click() + assertNotNull(waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL))) + } + } + + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA) + @RequiresFlagsDisabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) @Test - fun bodySensorsEitherDisplayedInMainPageOrInAdditional() { + fun testFindBodySensor_replaceBodySensorFlagDisabled_labelDisplayed() { if (waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL)) == null) { waitFindObject(By.textContains(ADDITIONAL_PERMISSIONS_LABEL)).click() assertNotNull(waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL))) } } + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA) + @RequiresFlagsEnabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) + @Test + fun testFindBodySensor_replaceBodySensorFlagEnabled_labelNotDisplayed() { + assertNull(waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL))) + waitFindObject(By.textContains(ADDITIONAL_PERMISSIONS_LABEL)).click() + assertNull(waitFindObjectOrNull(By.textContains(BODY_SENSORS_LABEL))) + } + @After fun tearDown() { uninstallApp(DEFINER_PKG) diff --git a/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml b/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml index de033ac44..cb6323fff 100644 --- a/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml +++ b/SafetyCenter/Resources/res/raw-v36/safety_center_config.xml @@ -30,6 +30,36 @@ initialDisplayState="disabled" notificationsAllowed="true"/> <dynamic-safety-source + id="AndroidFaceUnlock" + packageName="com.android.settings" + profile="all_profiles" + title="@com.android.safetycenter.resources:string/face_unlock_title" + titleForWork="@com.android.safetycenter.resources:string/face_unlock_title_for_work" + titleForPrivateProfile="@com.android.safetycenter.resources:string/face_unlock_title_for_private_profile" + searchTerms="@com.android.safetycenter.resources:string/face_unlock_search_terms" + refreshOnPageOpenAllowed="true" + initialDisplayState="hidden"/> + <dynamic-safety-source + id="AndroidFingerprintUnlock" + packageName="com.android.settings" + profile="all_profiles" + title="@com.android.safetycenter.resources:string/fingerprint_unlock_title" + titleForWork="@com.android.safetycenter.resources:string/fingerprint_unlock_title_for_work" + titleForPrivateProfile="@com.android.safetycenter.resources:string/fingerprint_unlock_title_for_private_profile" + searchTerms="@com.android.safetycenter.resources:string/fingerprint_unlock_search_terms" + refreshOnPageOpenAllowed="true" + initialDisplayState="hidden"/> + <dynamic-safety-source + id="AndroidWearUnlock" + packageName="com.android.settings" + profile="all_profiles" + title="@com.android.safetycenter.resources:string/wear_unlock_title" + titleForWork="@com.android.safetycenter.resources:string/wear_unlock_title_for_work" + titleForPrivateProfile="@com.android.safetycenter.resources:string/wear_unlock_title_for_private_profile" + searchTerms="@com.android.safetycenter.resources:string/wear_unlock_search_terms" + refreshOnPageOpenAllowed="true" + initialDisplayState="hidden"/> + <dynamic-safety-source id="AndroidBiometrics" packageName="com.android.settings" profile="all_profiles" diff --git a/SafetyCenter/Resources/res/values-af-v36/strings.xml b/SafetyCenter/Resources/res/values-af-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-af-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-am-v36/strings.xml b/SafetyCenter/Resources/res/values-am-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-am-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ar-v36/strings.xml b/SafetyCenter/Resources/res/values-ar-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ar-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-as-v36/strings.xml b/SafetyCenter/Resources/res/values-as-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-as-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-az-v36/strings.xml b/SafetyCenter/Resources/res/values-az-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-az-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-b+sr+Latn-v36/strings.xml b/SafetyCenter/Resources/res/values-b+sr+Latn-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-b+sr+Latn-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-be-v36/strings.xml b/SafetyCenter/Resources/res/values-be-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-be-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-bg-v36/strings.xml b/SafetyCenter/Resources/res/values-bg-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-bg-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-bn-v36/strings.xml b/SafetyCenter/Resources/res/values-bn-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-bn-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-bs-v36/strings.xml b/SafetyCenter/Resources/res/values-bs-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-bs-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ca-v36/strings.xml b/SafetyCenter/Resources/res/values-ca-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ca-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-cs-v36/strings.xml b/SafetyCenter/Resources/res/values-cs-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-cs-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-da-v36/strings.xml b/SafetyCenter/Resources/res/values-da-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-da-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-de-v36/strings.xml b/SafetyCenter/Resources/res/values-de-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-de-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-el-v36/strings.xml b/SafetyCenter/Resources/res/values-el-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-el-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-en-rAU-v36/strings.xml b/SafetyCenter/Resources/res/values-en-rAU-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-en-rAU-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-en-rCA-v36/strings.xml b/SafetyCenter/Resources/res/values-en-rCA-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-en-rCA-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-en-rGB-v36/strings.xml b/SafetyCenter/Resources/res/values-en-rGB-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-en-rGB-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-en-rIN-v36/strings.xml b/SafetyCenter/Resources/res/values-en-rIN-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-en-rIN-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-es-rUS-v36/strings.xml b/SafetyCenter/Resources/res/values-es-rUS-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-es-rUS-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-es-v36/strings.xml b/SafetyCenter/Resources/res/values-es-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-es-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-et-v36/strings.xml b/SafetyCenter/Resources/res/values-et-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-et-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-eu-v36/strings.xml b/SafetyCenter/Resources/res/values-eu-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-eu-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-fa-v36/strings.xml b/SafetyCenter/Resources/res/values-fa-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-fa-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-fi-v36/strings.xml b/SafetyCenter/Resources/res/values-fi-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-fi-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-fr-rCA-v36/strings.xml b/SafetyCenter/Resources/res/values-fr-rCA-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-fr-rCA-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-fr-v36/strings.xml b/SafetyCenter/Resources/res/values-fr-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-fr-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-gl-v36/strings.xml b/SafetyCenter/Resources/res/values-gl-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-gl-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-gu-v36/strings.xml b/SafetyCenter/Resources/res/values-gu-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-gu-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-hi-v36/strings.xml b/SafetyCenter/Resources/res/values-hi-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-hi-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-hr-v36/strings.xml b/SafetyCenter/Resources/res/values-hr-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-hr-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-hu-v36/strings.xml b/SafetyCenter/Resources/res/values-hu-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-hu-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-hy-v36/strings.xml b/SafetyCenter/Resources/res/values-hy-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-hy-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-in-v36/strings.xml b/SafetyCenter/Resources/res/values-in-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-in-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-is-v36/strings.xml b/SafetyCenter/Resources/res/values-is-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-is-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-it-v36/strings.xml b/SafetyCenter/Resources/res/values-it-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-it-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-iw-v36/strings.xml b/SafetyCenter/Resources/res/values-iw-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-iw-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ja-v36/strings.xml b/SafetyCenter/Resources/res/values-ja-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ja-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ka-v36/strings.xml b/SafetyCenter/Resources/res/values-ka-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ka-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-kk-v36/strings.xml b/SafetyCenter/Resources/res/values-kk-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-kk-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-km-v36/strings.xml b/SafetyCenter/Resources/res/values-km-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-km-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-kn-v36/strings.xml b/SafetyCenter/Resources/res/values-kn-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-kn-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ko-v36/strings.xml b/SafetyCenter/Resources/res/values-ko-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ko-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ky-v36/strings.xml b/SafetyCenter/Resources/res/values-ky-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ky-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-lo-v36/strings.xml b/SafetyCenter/Resources/res/values-lo-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-lo-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-lt-v36/strings.xml b/SafetyCenter/Resources/res/values-lt-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-lt-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-lv-v36/strings.xml b/SafetyCenter/Resources/res/values-lv-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-lv-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-mk-v36/strings.xml b/SafetyCenter/Resources/res/values-mk-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-mk-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ml-v36/strings.xml b/SafetyCenter/Resources/res/values-ml-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ml-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-mn-v36/strings.xml b/SafetyCenter/Resources/res/values-mn-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-mn-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-mr-v36/strings.xml b/SafetyCenter/Resources/res/values-mr-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-mr-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ms-v36/strings.xml b/SafetyCenter/Resources/res/values-ms-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ms-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-my-v36/strings.xml b/SafetyCenter/Resources/res/values-my-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-my-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-nb-v36/strings.xml b/SafetyCenter/Resources/res/values-nb-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-nb-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ne-v36/strings.xml b/SafetyCenter/Resources/res/values-ne-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ne-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-nl-v36/strings.xml b/SafetyCenter/Resources/res/values-nl-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-nl-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-or-v36/strings.xml b/SafetyCenter/Resources/res/values-or-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-or-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-pa-v36/strings.xml b/SafetyCenter/Resources/res/values-pa-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-pa-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-pl-v36/strings.xml b/SafetyCenter/Resources/res/values-pl-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-pl-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-pt-rBR-v36/strings.xml b/SafetyCenter/Resources/res/values-pt-rBR-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-pt-rBR-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-pt-rPT-v36/strings.xml b/SafetyCenter/Resources/res/values-pt-rPT-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-pt-rPT-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-pt-v36/strings.xml b/SafetyCenter/Resources/res/values-pt-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-pt-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ro-v36/strings.xml b/SafetyCenter/Resources/res/values-ro-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ro-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ru-v36/strings.xml b/SafetyCenter/Resources/res/values-ru-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ru-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-si-v36/strings.xml b/SafetyCenter/Resources/res/values-si-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-si-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-sk-v36/strings.xml b/SafetyCenter/Resources/res/values-sk-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-sk-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-sl-v36/strings.xml b/SafetyCenter/Resources/res/values-sl-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-sl-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-sq-v36/strings.xml b/SafetyCenter/Resources/res/values-sq-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-sq-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-sr-v36/strings.xml b/SafetyCenter/Resources/res/values-sr-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-sr-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-sv-v36/strings.xml b/SafetyCenter/Resources/res/values-sv-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-sv-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-sw-v36/strings.xml b/SafetyCenter/Resources/res/values-sw-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-sw-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ta-v36/strings.xml b/SafetyCenter/Resources/res/values-ta-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ta-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-te-v36/strings.xml b/SafetyCenter/Resources/res/values-te-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-te-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-th-v36/strings.xml b/SafetyCenter/Resources/res/values-th-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-th-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-tl-v36/strings.xml b/SafetyCenter/Resources/res/values-tl-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-tl-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-tr-v36/strings.xml b/SafetyCenter/Resources/res/values-tr-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-tr-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-uk-v36/strings.xml b/SafetyCenter/Resources/res/values-uk-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-uk-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-ur-v36/strings.xml b/SafetyCenter/Resources/res/values-ur-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-ur-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-uz-v36/strings.xml b/SafetyCenter/Resources/res/values-uz-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-uz-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-v36/config.xml b/SafetyCenter/Resources/res/values-v36/config.xml new file mode 100644 index 000000000..6fa28a340 --- /dev/null +++ b/SafetyCenter/Resources/res/values-v36/config.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Comma separated list of safety source IDs to show in the same task as the safety center --> + <string name="config_same_task_safety_source_ids" translatable="false">AndroidAccessibility,AndroidBackgroundLocation,AndroidBiometrics,AndroidFaceUnlock,AndroidFingerprintUnlock,AndroidHealthConnect,AndroidLockScreen,AndroidPrivateSpace,AndroidMoreSettings,AndroidNotificationListener,AndroidPermissionAutoRevoke,AndroidPermissionManager,AndroidPermissionUsage,AndroidPrivacyAppDataSharingUpdates,AndroidPrivacyControls,AndroidWearUnlock,AndroidWorkPolicyInfo</string> +</resources> diff --git a/SafetyCenter/Resources/res/values-v36/strings.xml b/SafetyCenter/Resources/res/values-v36/strings.xml new file mode 100644 index 000000000..f452e045a --- /dev/null +++ b/SafetyCenter/Resources/res/values-v36/strings.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="face_unlock_title" description="The default title of the setting for managing face unlock options on the device">Face</string> + <string name="face_unlock_title_for_work" description="The default title of the setting for managing face unlock options for work on the device">Face for work</string> + <string name="face_unlock_title_for_private_profile" description="The default title of the setting for managing face unlock options for private profile on the device"><!-- Empty placeholder--></string> + <string name="face_unlock_search_terms" description="Search keywords of the setting for managing face unlock options on the device">Face unlock, Face</string> + + <string name="fingerprint_unlock_title" description="The default title of the setting for managing fingerprint unlock options on the device">Fingerprint</string> + <string name="fingerprint_unlock_title_for_work" description="The default title of the setting for managing fingerprint unlock options for work on the device">Fingerprint for work</string> + <string name="fingerprint_unlock_title_for_private_profile" description="The default title of the setting for managing fingerprint unlock options for private profile on the device"><!-- Empty placeholder--></string> + <string name="fingerprint_unlock_search_terms" description="Search keywords of the setting for managing fingerprint unlock options on the device">Fingerprint, Finger, Add fingerprint</string> + + <string name="wear_unlock_title" description="The default title of the setting for managing wear unlock options on the device">Watch</string> + <string name="wear_unlock_title_for_work" description="The default title of the setting for managing wear unlock options for work on the device">Watch for work</string> + <string name="wear_unlock_title_for_private_profile" description="The default title of the setting for managing wear unlock options for private profile on the device"><!-- Empty placeholder--></string> + <string name="wear_unlock_search_terms" description="Search keywords of the setting for managing wear unlock options on the device">Watch, Watch unlock</string> +</resources> diff --git a/SafetyCenter/Resources/res/values-vi-v36/strings.xml b/SafetyCenter/Resources/res/values-vi-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-vi-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-zh-rCN-v36/strings.xml b/SafetyCenter/Resources/res/values-zh-rCN-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-zh-rCN-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-zh-rHK-v36/strings.xml b/SafetyCenter/Resources/res/values-zh-rHK-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-zh-rHK-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-zh-rTW-v36/strings.xml b/SafetyCenter/Resources/res/values-zh-rTW-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-zh-rTW-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/SafetyCenter/Resources/res/values-zu-v36/strings.xml b/SafetyCenter/Resources/res/values-zu-v36/strings.xml new file mode 100644 index 000000000..07f3c054c --- /dev/null +++ b/SafetyCenter/Resources/res/values-zu-v36/strings.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright (C) 2025 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<resources xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- no translation found for face_unlock_title (3991635517593572926) --> + <skip /> + <!-- no translation found for face_unlock_title_for_work (1451170625947022012) --> + <skip /> + <string name="face_unlock_title_for_private_profile" msgid="2758692637409168420"></string> + <!-- no translation found for face_unlock_search_terms (2708195853333028283) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title (5579868242026550596) --> + <skip /> + <!-- no translation found for fingerprint_unlock_title_for_work (6343690273672384918) --> + <skip /> + <string name="fingerprint_unlock_title_for_private_profile" msgid="9004513575240235691"></string> + <!-- no translation found for fingerprint_unlock_search_terms (688405183240088603) --> + <skip /> + <!-- no translation found for wear_unlock_title (1613730442896319515) --> + <skip /> + <!-- no translation found for wear_unlock_title_for_work (3103157953371670280) --> + <skip /> + <string name="wear_unlock_title_for_private_profile" msgid="927318621331822758"></string> + <!-- no translation found for wear_unlock_search_terms (3769797118448924263) --> + <skip /> +</resources> diff --git a/TEST_MAPPING b/TEST_MAPPING index 823738181..5c6f04426 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -45,6 +45,9 @@ "name" : "CtsRoleTestCases" }, { + "name" : "CtsRoleMultiUserTestCases" + }, + { "name" : "CtsPermissionMultiUserTestCases" }, { diff --git a/flags/flags.aconfig b/flags/flags.aconfig index 4cb084988..45afa5ff4 100644 --- a/flags/flags.aconfig +++ b/flags/flags.aconfig @@ -152,4 +152,13 @@ flag { description: "This flag is used to enable Expressive Design for Settings pages inside PermissionController" bug: "375480184" is_fixed_read_only: true -}
\ No newline at end of file +} + +flag { + name: "default_apps_recommendation_enabled" + is_exported: true + namespace: "permissions" + description: "This flag enables the recommended section in default apps" + bug: "388234667" + is_fixed_read_only: true +} diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt index 212996b24..c5d971435 100644 --- a/framework-s/api/system-current.txt +++ b/framework-s/api/system-current.txt @@ -64,7 +64,7 @@ package android.app.role { field @FlaggedApi("com.android.permission.flags.cross_user_role_enabled") public static final String ROLE_RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY = "android.app.role.RESERVED_FOR_TESTING_PROFILE_GROUP_EXCLUSIVITY"; field public static final String ROLE_SYSTEM_ACTIVITY_RECOGNIZER = "android.app.role.SYSTEM_ACTIVITY_RECOGNIZER"; field public static final String ROLE_SYSTEM_CALL_STREAMING = "android.app.role.SYSTEM_CALL_STREAMING"; - field public static final String ROLE_SYSTEM_DEPENDENCY_INSTALLER = "android.app.role.SYSTEM_DEPENDENCY_INSTALLER"; + field @FlaggedApi("android.content.pm.sdk_dependency_installer") public static final String ROLE_SYSTEM_DEPENDENCY_INSTALLER = "android.app.role.SYSTEM_DEPENDENCY_INSTALLER"; field public static final String ROLE_SYSTEM_SUPERVISION = "android.app.role.SYSTEM_SUPERVISION"; field public static final String ROLE_SYSTEM_WELLBEING = "android.app.role.SYSTEM_WELLBEING"; } diff --git a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java index db05a0af6..4248a429c 100644 --- a/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java +++ b/framework-s/java/android/app/ecm/EnhancedConfirmationManager.java @@ -33,6 +33,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Build; import android.os.RemoteException; +import android.os.UserHandle; import android.permission.flags.Flags; import android.util.ArraySet; @@ -202,6 +203,19 @@ public final class EnhancedConfirmationManager { public static final String ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG = "android.app.ecm.action.SHOW_ECM_RESTRICTED_SETTING_DIALOG"; + /** + * The setting is restricted because of the phone state of the device + * @hide + */ + public static final String REASON_PHONE_STATE = "phone_state"; + + /** + * The setting is restricted because the restricted app op is set for the given package + * @hide + */ + public static final String REASON_PACKAGE_RESTRICTED = "package_restricted"; + + /** A map of ECM states to their corresponding app op states */ @Retention(java.lang.annotation.RetentionPolicy.SOURCE) @IntDef(prefix = {"ECM_STATE_"}, value = {EcmState.ECM_STATE_NOT_GUARDED, @@ -349,8 +363,17 @@ public final class EnhancedConfirmationManager { @NonNull String settingIdentifier) throws NameNotFoundException { Intent intent = new Intent(ACTION_SHOW_ECM_RESTRICTED_SETTING_DIALOG); intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); - intent.putExtra(Intent.EXTRA_UID, getPackageUid(packageName)); + int uid = getPackageUid(packageName); + intent.putExtra(Intent.EXTRA_UID, uid); intent.putExtra(Intent.EXTRA_SUBJECT, settingIdentifier); + try { + String restrictionReason = mService.getRestrictionReason(packageName, + settingIdentifier, UserHandle.getUserHandleForUid(uid).getIdentifier()); + intent.putExtra(Intent.EXTRA_REASON, restrictionReason); + } catch (SecurityException | RemoteException e) { + // The caller of this method does not have permission to read the ECM state, so we + // won't include it in the return + } return intent; } diff --git a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl index 5149daa49..79d2322bd 100644 --- a/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl +++ b/framework-s/java/android/app/ecm/IEnhancedConfirmationManager.aidl @@ -25,6 +25,8 @@ interface IEnhancedConfirmationManager { boolean isRestricted(in String packageName, in String settingIdentifier, int userId); + String getRestrictionReason(in String packageName, in String settingIdentifier, int userId); + void clearRestriction(in String packageName, int userId); boolean isClearRestrictionAllowed(in String packageName, int userId); diff --git a/framework-s/java/android/app/role/RoleManager.java b/framework-s/java/android/app/role/RoleManager.java index 70bcfbf36..9f28b7f19 100644 --- a/framework-s/java/android/app/role/RoleManager.java +++ b/framework-s/java/android/app/role/RoleManager.java @@ -272,7 +272,7 @@ public final class RoleManager { * * @hide */ - @SuppressLint("UnflaggedApi") + @FlaggedApi("android.content.pm.sdk_dependency_installer") @SystemApi public static final String ROLE_SYSTEM_DEPENDENCY_INSTALLER = "android.app.role.SYSTEM_DEPENDENCY_INSTALLER"; diff --git a/framework-s/java/android/app/role/TEST_MAPPING b/framework-s/java/android/app/role/TEST_MAPPING index 46b148e68..62c07e5d9 100644 --- a/framework-s/java/android/app/role/TEST_MAPPING +++ b/framework-s/java/android/app/role/TEST_MAPPING @@ -7,6 +7,9 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsRoleMultiUserTestCases" } ], "mainline-presubmit": [ @@ -24,6 +27,9 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]" } ], "permission-mainline-presubmit": [ @@ -41,6 +47,9 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsRoleMultiUserTestCases" } ], "postsubmit": [ diff --git a/service/java/com/android/ecm/EnhancedConfirmationService.java b/service/java/com/android/ecm/EnhancedConfirmationService.java index 65fde6daf..46b5eedbc 100644 --- a/service/java/com/android/ecm/EnhancedConfirmationService.java +++ b/service/java/com/android/ecm/EnhancedConfirmationService.java @@ -16,6 +16,9 @@ package com.android.ecm; +import static android.app.ecm.EnhancedConfirmationManager.REASON_PACKAGE_RESTRICTED; +import static android.app.ecm.EnhancedConfirmationManager.REASON_PHONE_STATE; + import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -89,7 +92,7 @@ public class EnhancedConfirmationService extends SystemService { private static final int CALL_TYPE_UNTRUSTED = 0; private static final int CALL_TYPE_TRUSTED = 1; - private static final int CALL_TYPE_EMERGENCY = 2; + private static final int CALL_TYPE_EMERGENCY = 1 << 1; @IntDef(flag = true, value = { CALL_TYPE_UNTRUSTED, CALL_TYPE_TRUSTED, @@ -237,7 +240,7 @@ public class EnhancedConfirmationService extends SystemService { int ECM_STATE_IMPLICIT = AppOpsManager.MODE_DEFAULT; } - private static final ArraySet<String> PROTECTED_SETTINGS = new ArraySet<>(); + private static final ArraySet<String> PER_PACKAGE_PROTECTED_SETTINGS = new ArraySet<>(); // Settings restricted when an untrusted call is ongoing. These must also be added to // PROTECTED_SETTINGS @@ -245,30 +248,31 @@ public class EnhancedConfirmationService extends SystemService { static { // Runtime permissions - PROTECTED_SETTINGS.add(Manifest.permission.SEND_SMS); - PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_SMS); - PROTECTED_SETTINGS.add(Manifest.permission.READ_SMS); - PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_MMS); - PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_WAP_PUSH); - PROTECTED_SETTINGS.add(Manifest.permission.READ_CELL_BROADCASTS); - PROTECTED_SETTINGS.add(Manifest.permission_group.SMS); - - PROTECTED_SETTINGS.add(Manifest.permission.BIND_DEVICE_ADMIN); + PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.SEND_SMS); + PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_SMS); + PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.READ_SMS); + PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_MMS); + PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.RECEIVE_WAP_PUSH); + PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.READ_CELL_BROADCASTS); + PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission_group.SMS); + + PER_PACKAGE_PROTECTED_SETTINGS.add(Manifest.permission.BIND_DEVICE_ADMIN); // App ops - PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); - PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS); - PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW); - PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_GET_USAGE_STATS); - PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS); + PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); + PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS); + PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW); + PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_GET_USAGE_STATS); + PER_PACKAGE_PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS); // Default application roles. - PROTECTED_SETTINGS.add(RoleManager.ROLE_DIALER); - PROTECTED_SETTINGS.add(RoleManager.ROLE_SMS); + PER_PACKAGE_PROTECTED_SETTINGS.add(RoleManager.ROLE_DIALER); + PER_PACKAGE_PROTECTED_SETTINGS.add(RoleManager.ROLE_SMS); if (Flags.unknownCallPackageInstallBlockingEnabled()) { // Requesting package installs, limited during phone calls - PROTECTED_SETTINGS.add(AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES); UNTRUSTED_CALL_RESTRICTED_SETTINGS.add( AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES); + UNTRUSTED_CALL_RESTRICTED_SETTINGS.add( + AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); } } @@ -287,10 +291,16 @@ public class EnhancedConfirmationService extends SystemService { public boolean isRestricted(@NonNull String packageName, @NonNull String settingIdentifier, @UserIdInt int userId) { + return getRestrictionReason(packageName, settingIdentifier, userId) != null; + } + + public String getRestrictionReason(@NonNull String packageName, + @NonNull String settingIdentifier, + @UserIdInt int userId) { enforcePermissions("isRestricted", userId); if (!UserUtils.isUserExistent(userId, getContext())) { Log.e(LOG_TAG, "user " + userId + " does not exist"); - return false; + return null; } Preconditions.checkStringNotEmpty(packageName, "packageName cannot be null or empty"); @@ -299,12 +309,16 @@ public class EnhancedConfirmationService extends SystemService { try { if (!isSettingEcmProtected(settingIdentifier)) { - return false; + return null; + } + if (isSettingEcmGuardedForPackage(settingIdentifier, packageName, userId)) { + return REASON_PACKAGE_RESTRICTED; } - if (isSettingProtectedGlobally(settingIdentifier)) { - return true; + String globalProtectionReason = getGlobalProtectionReason(settingIdentifier); + if (globalProtectionReason != null) { + return globalProtectionReason; } - return isPackageEcmGuarded(packageName, userId); + return null; } catch (NameNotFoundException e) { throw new IllegalArgumentException(e); } @@ -436,6 +450,14 @@ public class EnhancedConfirmationService extends SystemService { || isAllowlistedInstaller(installingPackageName)); } + private boolean isSettingEcmGuardedForPackage(@NonNull String settingIdentifier, + @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException { + if (!PER_PACKAGE_PROTECTED_SETTINGS.contains(settingIdentifier)) { + return false; + } + return isPackageEcmGuarded(packageName, userId); + } + private boolean isAllowlistedPackage(String packageName) { return isPackageSignedWithAnyOf(packageName, mTrustedPackageCertDigests.get(packageName)); @@ -506,19 +528,23 @@ public class EnhancedConfirmationService extends SystemService { return false; } - if (PROTECTED_SETTINGS.contains(settingIdentifier)) { + if (PER_PACKAGE_PROTECTED_SETTINGS.contains(settingIdentifier)) { + return true; + } + if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) { return true; } // TODO(b/310218979): Add role selections as protected settings return false; } - private boolean isSettingProtectedGlobally(@NonNull String settingIdentifier) { - if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier)) { - return isUntrustedCallOngoing(); + private String getGlobalProtectionReason(@NonNull String settingIdentifier) { + if (UNTRUSTED_CALL_RESTRICTED_SETTINGS.contains(settingIdentifier) + && isUntrustedCallOngoing()) { + return REASON_PHONE_STATE; } - return false; + return null; } @Nullable diff --git a/service/java/com/android/role/TEST_MAPPING b/service/java/com/android/role/TEST_MAPPING index e0e1160d8..720330f82 100644 --- a/service/java/com/android/role/TEST_MAPPING +++ b/service/java/com/android/role/TEST_MAPPING @@ -15,6 +15,9 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsRoleMultiUserTestCases" } ], "mainline-presubmit": [ @@ -32,6 +35,9 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]" } ], "permission-mainline-presubmit": [ @@ -49,6 +55,9 @@ "exclude-annotation": "androidx.test.filters.FlakyTest" } ] + }, + { + "name": "CtsRoleMultiUserTestCases" } ], "postsubmit": [ diff --git a/tests/apex/AndroidTest.xml b/tests/apex/AndroidTest.xml index b1af0f53e..c3807eb9d 100644 --- a/tests/apex/AndroidTest.xml +++ b/tests/apex/AndroidTest.xml @@ -20,6 +20,7 @@ <option name="test-suite-tag" value="apct" /> <option name="test-suite-tag" value="apct-instrumentation" /> <option name="test-tag" value="PermissionApexTests" /> + <option name="hidden-api-checks" value="false" /> <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" /> <!-- Install test --> diff --git a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt index 687234582..44eef2144 100644 --- a/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt +++ b/tests/cts/permissionmultidevice/src/android/permissionmultidevice/cts/DeviceAwarePermissionGrantTest.kt @@ -93,7 +93,7 @@ class DeviceAwarePermissionGrantTest { val displayConfigBuilder = VirtualDeviceRule.createDefaultVirtualDisplayConfigBuilder( DISPLAY_WIDTH, - DISPLAY_HEIGHT + DISPLAY_HEIGHT, ) .setFlags( DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC or @@ -114,7 +114,7 @@ class DeviceAwarePermissionGrantTest { @RequiresFlagsEnabled( Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED, - Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED + Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED, ) @Test fun onHostDevice_requestPermissionForHostDevice_shouldGrantPermission() { @@ -124,13 +124,13 @@ class DeviceAwarePermissionGrantTest { false, "", expectPermissionGrantedOnDefaultDevice = true, - expectPermissionGrantedOnRemoteDevice = false + expectPermissionGrantedOnRemoteDevice = false, ) } @RequiresFlagsEnabled( Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED, - Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED + Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED, ) @Test fun onHostDevice_requestPermissionForRemoteDevice_shouldGrantPermission() { @@ -140,13 +140,13 @@ class DeviceAwarePermissionGrantTest { true, deviceDisplayName, expectPermissionGrantedOnDefaultDevice = false, - expectPermissionGrantedOnRemoteDevice = true + expectPermissionGrantedOnRemoteDevice = true, ) } @RequiresFlagsEnabled( Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED, - Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED + Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED, ) @RequiresFlagsDisabled(Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES) @Test @@ -160,8 +160,9 @@ class DeviceAwarePermissionGrantTest { @RequiresFlagsEnabled( Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED, Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED, - Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES + Flags.FLAG_ALLOW_HOST_PERMISSION_DIALOGS_ON_VIRTUAL_DEVICES, ) + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava") @Test fun onRemoteDevice_requestPermissionForHostDevice_shouldGrantPermission() { // Create a virtual device with default policy, so that camera permission request will @@ -176,16 +177,18 @@ class DeviceAwarePermissionGrantTest { virtualDisplay.display.displayId, virtualDevice.deviceId, true, - Settings.Global.getString(defaultDeviceContext.contentResolver, - Settings.Global.DEVICE_NAME), + Settings.Global.getString( + defaultDeviceContext.contentResolver, + Settings.Global.DEVICE_NAME, + ), expectPermissionGrantedOnDefaultDevice = true, - expectPermissionGrantedOnRemoteDevice = false + expectPermissionGrantedOnRemoteDevice = false, ) } @RequiresFlagsEnabled( Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED, - Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED + Flags.FLAG_DEVICE_AWARE_PERMISSIONS_ENABLED, ) @Test fun onRemoteDevice_requestPermissionForRemoteDevice_shouldGrantPermission() { @@ -195,7 +198,7 @@ class DeviceAwarePermissionGrantTest { true, deviceDisplayName, expectPermissionGrantedOnDefaultDevice = false, - expectPermissionGrantedOnRemoteDevice = true + expectPermissionGrantedOnRemoteDevice = true, ) } @@ -205,7 +208,7 @@ class DeviceAwarePermissionGrantTest { showDeviceName: Boolean, expectedDeviceNameInDialog: String, expectPermissionGrantedOnDefaultDevice: Boolean, - expectPermissionGrantedOnRemoteDevice: Boolean + expectPermissionGrantedOnRemoteDevice: Boolean, ) { // Assert no permission granted to either default device or virtual device at the beginning assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, false) @@ -240,13 +243,13 @@ class DeviceAwarePermissionGrantTest { assertAppHasPermissionForDevice(DEVICE_ID_DEFAULT, expectPermissionGrantedOnDefaultDevice) assertAppHasPermissionForDevice( virtualDevice.deviceId, - expectPermissionGrantedOnRemoteDevice + expectPermissionGrantedOnRemoteDevice, ) } private fun requestPermissionOnDevice( displayId: Int, - targetDeviceId: Int + targetDeviceId: Int, ): CompletableFuture<Bundle> { val future = CompletableFuture<Bundle>() val callback = RemoteCallback { result: Bundle? -> future.complete(result) } diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml index bceec18ae..a2f85c6a1 100644 --- a/tests/cts/permissionpolicy/res/raw/android_manifest.xml +++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml @@ -8632,22 +8632,6 @@ android:protectionLevel="signature|role" android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/> - <!-- - This permission allows the system to receive PACKAGE_CHANGED broadcasts when the component - state of a non-exported component has been changed. - <p>Not for use by third-party applications. </p> - <p>Protection level: internal - @hide - --> - <permission - android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED" - android:protectionLevel="internal" - android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/> - - <uses-permission - android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED" - android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/> - <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml b/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml index 5949d08f2..6b9b9f41f 100644 --- a/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml +++ b/tests/cts/permissionui/UsePermissionApp30WithBackground/AndroidManifest.xml @@ -24,6 +24,8 @@ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> + <uses-permission android:name="android.permission.BODY_SENSORS" /> + <uses-permission android:name="android.permission.BODY_SENSORS_BACKGROUND" /> <application> <activity android:name=".RequestPermissionsActivity" android:exported="true" /> diff --git a/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt index f0c12171c..05c824072 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/AppPermissionTest.kt @@ -18,7 +18,9 @@ package android.permissionui.cts import android.Manifest.permission.ACCESS_COARSE_LOCATION import android.Manifest.permission_group.SMS +import android.app.AppOpsManager import android.os.Build +import android.os.Process import android.permission.flags.Flags import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.CheckFlagsRule @@ -31,8 +33,11 @@ import androidx.test.filters.SdkSuppress import androidx.test.uiautomator.By import androidx.test.uiautomator.Until import com.android.compatibility.common.util.DeviceConfigStateChangerRule +import com.android.compatibility.common.util.SystemUtil.eventually +import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity import com.android.modules.utils.build.SdkLevel import com.google.common.truth.Truth +import org.junit.Assert.assertEquals import org.junit.Assume import org.junit.Before import org.junit.Rule @@ -296,6 +301,9 @@ class AppPermissionTest : BaseUsePermissionTest() { APP_APK_NAME_LATEST ) + // TODO: b/388960315 - Remove wait after addressing race condition + waitForModeDefault(APP_PACKAGE_NAME) + navigateToIndividualPermissionSetting(SMS) assertAllowButtonIsDisabledAndRestrictedSettingDialogPoppedUp() @@ -311,6 +319,8 @@ class AppPermissionTest : BaseUsePermissionTest() { @Test fun installFromLocalFile_disabledAllowRadioButtonAndIfClickedAndRestrictedSettingDialog_SMSPermGroup() { installPackageWithInstallSourceAndMetadataFromLocalFile(APP_APK_NAME_LATEST) + // TODO: b/388960315 - Remove wait after addressing race condition + waitForModeDefault(APP_PACKAGE_NAME) navigateToIndividualPermissionSetting(SMS) @@ -341,6 +351,28 @@ class AppPermissionTest : BaseUsePermissionTest() { ) } + private fun waitForModeDefault(packageName: String) { + val appOpsManager = context.getSystemService(AppOpsManager::class.java)!! + eventually { + val uid = context.packageManager.getApplicationInfoAsUser( + packageName, + /* flags */ 0, + Process.myUserHandle() + ).uid + runWithShellPermissionIdentity { + assertEquals( + "Timed out waiting for package mode to change to MODE_DEFAULT", + appOpsManager.checkOpNoThrow( + AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, + uid, + packageName, + ), + AppOpsManager.MODE_DEFAULT, + ) + } + } + } + companion object { private const val PERMISSION_RATIONALE_ENABLED = "permission_rationale_enabled" private val ENHANCED_CONFIRMATION_DIALOG_SELECTOR = By.res( diff --git a/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt index d8eb153bf..b2da92d22 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/BasePermissionTest.kt @@ -121,7 +121,7 @@ abstract class BasePermissionTest { /* PackageManager.FEATURE_CAR_SPLITSCREEN_MULTITASKING */ "android.software.car.splitscreen_multitasking") @JvmStatic - private val isAutomotiveVisibleBackgroundUser = isAutomotive && + protected val isAutomotiveVisibleBackgroundUser = isAutomotive && UserHelper(context).isVisibleBackgroundUser() // TODO(b/382327037):find a way to avoid specifying the display ID for each UiSelector. diff --git a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt index 92599b617..bd22cb5bc 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/BaseUsePermissionTest.kt @@ -148,6 +148,9 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { const val APP_PERMISSION_RATIONALE_TITLE_TEXT = "app_location_permission_rationale_title" const val APP_PERMISSION_RATIONALE_SUBTITLE_TEXT = "app_location_permission_rationale_subtitle" + const val HEALTH_PERMISSION_SELECT_HEART_RATE_PLAIN_TEXT = "Heart rate" + const val HEALTH_PERMISSION_ALLOW_ALL_PLAIN_TEXT = "Allow all" + const val HEALTH_PERMISSION_ALLOW_ALWAYS_PLAIN_TEXT = "Allow all the time" const val GRANT_DIALOG_PERMISSION_RATIONALE_CONTAINER_VIEW = "com.android.permissioncontroller:id/permission_rationale_container" const val PERMISSION_RATIONALE_ACTIVITY_TITLE_VIEW = @@ -315,7 +318,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { reinstall: Boolean, grantRuntimePermissions: Boolean, expectSuccess: Boolean, - installSource: String? + installSource: String?, ) { installPackage( apkPath, @@ -333,7 +336,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { grantRuntimePermissions: Boolean = false, expectSuccess: Boolean = true, installSource: String? = null, - skipClearLowSdkDialog: Boolean = false + skipClearLowSdkDialog: Boolean = false, ) { super.installPackage( apkPath, @@ -463,7 +466,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { } protected fun installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms( - apkName: String + apkName: String, ) { installPackageViaSession( apkName, @@ -506,7 +509,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { } protected fun installPackageWithInstallSourceAndMetadataWithoutTopLevelVersion( - apkName: String + apkName: String, ) { installPackageViaSession( apkName, @@ -515,7 +518,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { } protected fun installPackageWithInstallSourceAndMetadataWithInvalidTopLevelVersion( - apkName: String + apkName: String, ) { installPackageViaSession( apkName, @@ -524,7 +527,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { } protected fun installPackageWithInstallSourceAndMetadataWithoutSafetyLabelVersion( - apkName: String + apkName: String, ) { installPackageViaSession( apkName, @@ -533,7 +536,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { } protected fun installPackageWithInstallSourceAndMetadataWithInvalidSafetyLabelVersion( - apkName: String + apkName: String, ) { installPackageViaSession( apkName, @@ -552,7 +555,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { } protected fun assertPermissionRationaleActivityDataSharingSourceSectionVisible( - expected: Boolean + expected: Boolean, ) { findView(By.res(DATA_SHARING_SOURCE_TITLE_ID).displayId(displayId), expected = expected) findView(By.res(DATA_SHARING_SOURCE_MESSAGE_ID).displayId(displayId), expected = expected) @@ -577,7 +580,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { protected fun assertPermissionRationaleDialogIsVisible( expected: Boolean, - showSettingsSection: Boolean = true + showSettingsSection: Boolean = true, ) { assertPermissionRationaleActivityTitleIsVisible(expected) assertPermissionRationaleActivityDataSharingSourceSectionVisible(expected) @@ -623,7 +626,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { protected inline fun startAppActivityAndAssertResultCode( expectedResultCode: Int, - block: () -> Unit + block: () -> Unit, ) { val future = startActivityForFuture( @@ -641,7 +644,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { protected inline fun requestAppPermissionsForNoResult( vararg permissions: String?, - crossinline block: () -> Unit + crossinline block: () -> Unit, ) { // Request the permissions doAndWaitForWindowTransition { @@ -665,7 +668,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { vararg permissions: String?, askTwice: Boolean = false, waitForWindowTransition: Boolean = !isWatch, - crossinline block: () -> Unit + crossinline block: () -> Unit, ): Instrumentation.ActivityResult { // Request the permissions lateinit var future: CompletableFuture<Instrumentation.ActivityResult> @@ -700,7 +703,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { permissionAndExpectedGrantResults: Array<out Pair<String?, Boolean>>, askTwice: Boolean = false, waitForWindowTransition: Boolean = !isWatch, - crossinline block: () -> Unit + crossinline block: () -> Unit, ) { var shouldWaitForWindowTransition = waitForWindowTransition // Do not wait for windowTransition after action is performed on auto, when permissions @@ -766,7 +769,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { vararg permissionAndExpectedGrantResults: Pair<String?, Boolean>, askTwice: Boolean = false, waitForWindowTransition: Boolean = !isWatch, - crossinline block: () -> Unit + crossinline block: () -> Unit, ) { requestAppPermissionsAndAssertResult( permissionAndExpectedGrantResults.map { it.first }.toTypedArray(), @@ -801,8 +804,11 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { } } - protected fun clickPermissionRequestAllowButton(timeoutMillis: Long = 20000) { - if (isAutomotive || isWatch) { + protected fun clickPermissionRequestAllowButton( + timeoutMillis: Long = 20000, + isHealthPermission: Boolean = false, + ) { + if (isAutomotive || isWatch || isHealthPermission) { click(By.text(getPermissionControllerString(ALLOW_BUTTON_TEXT)).displayId(displayId), timeoutMillis) } else { @@ -973,6 +979,37 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { assertTrue("Could not click on the settings link correctly", clickedOnLink) } + protected fun clickAllowReadHeartRate() { + eventually { + scrollToBottom() + + // Phone UI has allow all, toggle, and allow button. Watch UI only has allow button. + if (!isWatch) { + // Check "Allow all" button is visible. + val allowAllNode = + uiAutomation.rootInActiveWindow.findAccessibilityNodeInfosByText( + HEALTH_PERMISSION_ALLOW_ALL_PLAIN_TEXT + )[0] + assertTrue(allowAllNode.isVisibleToUser) + + // Select "Heart rate" toggle and click "Allow" button. + click(By.text(HEALTH_PERMISSION_SELECT_HEART_RATE_PLAIN_TEXT).displayId(displayId)) + } + + clickPermissionRequestAllowButton(isHealthPermission = true) + } + } + + protected fun clickAlwaysAllowReadHealthDataInBackground() { + eventually { + if (isWatch) { + click(By.text(HEALTH_PERMISSION_ALLOW_ALWAYS_PLAIN_TEXT).displayId(displayId)) + } else { + clickPermissionRequestAllowButton(isHealthPermission = true) + } + } + } + protected fun clickPermissionRequestDenyAndDontAskAgainButton() { if (isAutomotive) { scrollToBottom() @@ -1032,7 +1069,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { protected fun revokeAppPermissionsByUi( vararg permissions: String, - isLegacyApp: Boolean = false + isLegacyApp: Boolean = false, ) { setAppPermissionState( *permissions, @@ -1095,7 +1132,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { protected fun navigateToIndividualPermissionSetting( permission: String, - manuallyNavigate: Boolean = false + manuallyNavigate: Boolean = false, ) { val useLegacyNavigation = isWatch || isAutomotive || manuallyNavigate if (useLegacyNavigation) { @@ -1368,7 +1405,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_COARSE_LOCATION, - android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true + android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, + -> true else -> false } @@ -1379,7 +1417,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { return when (permission) { Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO -> true + Manifest.permission.READ_MEDIA_VIDEO, + -> true else -> false } } @@ -1387,7 +1426,8 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { private fun showsForegroundOnlyButton(permission: String): Boolean = when (permission) { android.Manifest.permission.CAMERA, - android.Manifest.permission.RECORD_AUDIO -> true + android.Manifest.permission.RECORD_AUDIO, + -> true else -> false } @@ -1420,7 +1460,7 @@ abstract class BaseUsePermissionTest : BasePermissionTest() { protected fun findAccessibilityNodeInfosByTextForSurfaceView( node: AccessibilityNodeInfo, - text: String + text: String, ): AccessibilityNodeInfo? { if (node.text != null && node.text.contains(text)) return node for (i in 0 until node.childCount) { diff --git a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt index 16a27c9a8..9a4908c79 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationInCallTest.kt @@ -24,6 +24,7 @@ import android.content.Context import android.content.pm.PackageManager import android.net.Uri import android.os.Build +import android.os.Process import android.permission.flags.Flags import android.platform.test.annotations.AppModeFull import android.platform.test.annotations.RequiresFlagsEnabled @@ -56,8 +57,11 @@ import org.junit.Test // @CddTest(requirement = "TBD") class EnhancedConfirmationInCallTest { private val ecm = context.getSystemService(EnhancedConfirmationManager::class.java)!! + private val aom = context.getSystemService(AppOpsManager::class.java)!! private val packageManager = context.packageManager private val addedContacts = mutableMapOf<String, List<Uri>>() + private val phoneOnlyRestrictedSetting = AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES + private val phoneAndEcmRestrictedSetting = AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES @JvmField @Rule @@ -149,20 +153,32 @@ class EnhancedConfirmationInCallTest { fun tearDown() { voipService.endCallAndWaitForInactive() addedContacts.keys.forEach { removeContact(it) } + runWithShellPermissionIdentity { + aom.setUidMode( + AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, + Process.myUid(), + AppOpsManager.MODE_ALLOWED, + ) + } } - private fun isSettingRestricted(): Boolean { + private fun isSettingRestricted(settingsIdentifier: String): Boolean { return callWithShellPermissionIdentity { - ecm.isRestricted(context.packageName, AppOpsManager.OPSTR_REQUEST_INSTALL_PACKAGES) + ecm.isRestricted(context.packageName, settingsIdentifier) } } + private fun areSettingsRestricted(): Boolean { + return isSettingRestricted(phoneOnlyRestrictedSetting) && + isSettingRestricted(phoneAndEcmRestrictedSetting) + } + @Test fun testIncomingCall_NonContact() { voipService.createCallAndWaitForActive(NON_CONTACT_DISPLAY_NAME, NON_CONTACT_PHONE_NUMBER) - Assert.assertTrue(isSettingRestricted()) + Assert.assertTrue(areSettingsRestricted()) voipService.endCallAndWaitForInactive() - Assert.assertFalse(isSettingRestricted()) + Assert.assertFalse(areSettingsRestricted()) } @Test @@ -170,9 +186,9 @@ class EnhancedConfirmationInCallTest { addContact(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) // If no phone number is given, the display name will be checked voipService.createCallAndWaitForActive(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) - Assert.assertFalse(isSettingRestricted()) + Assert.assertFalse(areSettingsRestricted()) voipService.endCallAndWaitForInactive() - Assert.assertFalse(isSettingRestricted()) + Assert.assertFalse(areSettingsRestricted()) } @Test @@ -180,9 +196,9 @@ class EnhancedConfirmationInCallTest { addContact(CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) // If the phone number matches, the display name is not checked voipService.createCallAndWaitForActive(NON_CONTACT_DISPLAY_NAME, CONTACT_PHONE_NUMBER) - Assert.assertFalse(isSettingRestricted()) + Assert.assertFalse(areSettingsRestricted()) voipService.endCallAndWaitForInactive() - Assert.assertFalse(isSettingRestricted()) + Assert.assertFalse(areSettingsRestricted()) } @Test @@ -192,10 +208,26 @@ class EnhancedConfirmationInCallTest { voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone) addContact(tempContactDisplay, tempContactPhone) // State should not be recomputed just because the contact is newly added - Assert.assertTrue(isSettingRestricted()) + Assert.assertTrue(areSettingsRestricted()) voipService.endCallAndWaitForInactive() voipService.createCallAndWaitForActive(tempContactDisplay, tempContactPhone) // A new call should recognize our contact, and mark the call as trusted - Assert.assertFalse(isSettingRestricted()) + Assert.assertFalse(areSettingsRestricted()) + } + + @Test + fun testCallOnlyRestrictedSetting_notRestrictedIfEcmSet() { + // Set the current app to be restricted by ECM + runWithShellPermissionIdentity { + aom.setUidMode( + AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, + Process.myUid(), + AppOpsManager.MODE_ERRORED, + ) + } + // The ecm and phone restricted setting is restricted + Assert.assertFalse(isSettingRestricted(phoneOnlyRestrictedSetting)) + // But the phone only restriction is not + Assert.assertFalse(isSettingRestricted(phoneAndEcmRestrictedSetting)) } } diff --git a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt index 8e91a00ce..9ec09dab7 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/EnhancedConfirmationManagerTest.kt @@ -78,20 +78,14 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { @Test fun installedAppStartsWithModeDefault() { installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST) - eventually { - runWithShellPermissionIdentity { - assertEquals( - getAppEcmState(context, appOpsManager, APP_PACKAGE_NAME), - AppOpsManager.MODE_DEFAULT - ) - } - } + waitForModeDefault() } @RequiresFlagsEnabled(Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED) @Test fun givenStoreAppThenIsNotRestrictedFromProtectedSetting() { installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST) + waitForModeDefault() runWithShellPermissionIdentity { eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } } @@ -101,6 +95,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { @Test fun givenLocalAppThenIsRestrictedFromProtectedSetting() { installPackageWithInstallSourceAndMetadataFromLocalFile(APP_APK_NAME_LATEST) + waitForModeDefault() runWithShellPermissionIdentity { eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } } @@ -110,6 +105,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { @Test fun givenDownloadedThenAppIsRestrictedFromProtectedSetting() { installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST) + waitForModeDefault() runWithShellPermissionIdentity { eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } } @@ -119,6 +115,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { @Test fun givenExplicitlyRestrictedAppThenIsRestrictedFromProtectedSetting() { installPackageWithInstallSourceAndMetadataFromStore(APP_APK_NAME_LATEST) + waitForModeDefault() eventually { runWithShellPermissionIdentity { assertEquals( @@ -138,6 +135,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { @Test fun givenRestrictedAppThenIsNotRestrictedFromNonProtectedSetting() { installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST) + waitForModeDefault() runWithShellPermissionIdentity { eventually { assertFalse(ecm.isRestricted(APP_PACKAGE_NAME, NON_PROTECTED_SETTING)) } } @@ -147,6 +145,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { @Test fun givenRestrictedAppThenClearRestrictionNotAllowedByDefault() { installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST) + waitForModeDefault() runWithShellPermissionIdentity { eventually { assertFalse(ecm.isClearRestrictionAllowed(APP_PACKAGE_NAME)) } } @@ -156,6 +155,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { @Test fun givenRestrictedAppWhenClearRestrictionThenNotRestrictedFromProtectedSetting() { installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST) + waitForModeDefault() runWithShellPermissionIdentity { eventually { assertTrue(ecm.isRestricted(APP_PACKAGE_NAME, PROTECTED_SETTING)) } ecm.setClearRestrictionAllowed(APP_PACKAGE_NAME) @@ -169,6 +169,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { @Test fun createRestrictedSettingDialogIntentReturnsIntent() { installPackageWithInstallSourceAndMetadataFromDownloadedFile(APP_APK_NAME_LATEST) + waitForModeDefault() val intent = ecm.createRestrictedSettingDialogIntent(APP_PACKAGE_NAME, PROTECTED_SETTING) @@ -181,6 +182,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms( APP_APK_NAME_LATEST ) + waitForModeDefault() val permissionAndExpectedGrantResults = arrayOf( GROUP_2_PERMISSION_1_RESTRICTED to false, @@ -207,6 +209,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms( APP_APK_NAME_LATEST ) + waitForModeDefault() requestAppPermissionsAndAssertResult( GROUP_3_PERMISSION_1_UNRESTRICTED to false, @@ -236,6 +239,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms( APP_APK_NAME_LATEST ) + waitForModeDefault() requestAppPermissionsAndAssertResult( GROUP_3_PERMISSION_1_UNRESTRICTED to true, @@ -254,6 +258,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { installPackageWithInstallSourceFromDownloadedFileAndAllowHardRestrictedPerms( APP_APK_NAME_LATEST ) + waitForModeDefault() requestAppPermissionsAndAssertResult( GROUP_4_PERMISSION_1_UNRESTRICTED to true, @@ -287,6 +292,18 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { ) } + private fun waitForModeDefault() { + eventually { + runWithShellPermissionIdentity { + assertEquals( + "Timed out waiting for package mode to change to MODE_DEFAULT", + getAppEcmState(context, appOpsManager, APP_PACKAGE_NAME), + AppOpsManager.MODE_DEFAULT + ) + } + } + } + companion object { private const val GROUP_2_PERMISSION_1_RESTRICTED = Manifest.permission.SEND_SMS private const val GROUP_2_PERMISSION_2_RESTRICTED = Manifest.permission.READ_SMS @@ -294,7 +311,7 @@ class EnhancedConfirmationManagerTest : BaseUsePermissionTest() { Manifest.permission.ACCESS_FINE_LOCATION private const val GROUP_3_PERMISSION_2_UNRESTRICTED = Manifest.permission.ACCESS_COARSE_LOCATION - private const val GROUP_4_PERMISSION_1_UNRESTRICTED = Manifest.permission.BODY_SENSORS + private const val GROUP_4_PERMISSION_1_UNRESTRICTED = Manifest.permission.CAMERA private const val NON_PROTECTED_SETTING = "example_setting_which_is_not_protected" private const val PROTECTED_SETTING = "android:bind_accessibility_service" diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt index e71ac32a5..c2b5447dd 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionSplitTest.kt @@ -16,7 +16,11 @@ package android.permissionui.cts +import android.health.connect.HealthPermissions import android.os.Build +import android.permission.flags.Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED +import android.platform.test.annotations.RequiresFlagsDisabled +import android.platform.test.annotations.RequiresFlagsEnabled import android.platform.test.flag.junit.DeviceFlagsValueProvider import androidx.test.filters.FlakyTest import androidx.test.filters.SdkSuppress @@ -60,34 +64,112 @@ class PermissionSplitTest : BaseUsePermissionTest() { testLocationPermissionSplit(false) } + // TODO: b/388596433 - Update maxSdkVersion to VANILLA_ICE_CREAM after SDK bumps. + // TODO: b/383440585 - Remove this test when flag annotation issue is fixed. @SdkSuppress( minSdkVersion = Build.VERSION_CODES.TIRAMISU, - maxSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, + maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, ) @Test - fun testBodySensorSplitOnTToV() { + fun testBodySensorSplitOnTToU() { installPackage(APP_APK_PATH_31) - testBodySensorPermissionSplit(true) + testBodySensorPermissionSplitToBodySensorsBackground(true) } + // Before SDK_INT bumps to 36, the in-development B images are using SDK_INT=35(V). This will + // cause test failures on main builds where replaceBodySensor flag is enabled to remove Sensor + // group UI. As a workaround, we move SDK_INT=35 tests out and requires replaceBodySensor flag + // disabled when running on these images. + // TODO: b/388596433 - Update minSdkVersion to BAKLAVA after SDK bumps. + // TODO: b/383440585 - Update minSdkVersion to TIRAMISU when flag annotation issue is fixed. + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + @RequiresFlagsDisabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) + @Test + fun testBodySensorSplitPostV_replaceBodySensorFlagDisabled() { + installPackage(APP_APK_PATH_31) + testBodySensorPermissionSplitToBodySensorsBackground(true) + } + + // TODO: b/388596433 - Update maxSdkVersion to VANILLA_ICE_CREAM after SDK bumps. + // TODO: b/383440585 - Remove this test when flag annotation issue is fixed. @SdkSuppress( minSdkVersion = Build.VERSION_CODES.TIRAMISU, - maxSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, + maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, ) @Test - fun testBodySensorSplit32OnTToV() { + fun testBodySensorSplit32OnTToU() { + installPackage(APP_APK_PATH_32) + testBodySensorPermissionSplitToBodySensorsBackground(true) + } + + // Before SDK_INT bumps to 36, the in-development B images are using SDK_INT=35(V). This will + // cause test failures on main builds where replaceBodySensor flag is enabled to remove Sensor + // group UI. As a workaround, we move SDK_INT=35 tests out and requires replaceBodySensor flag + // disabled when running on these images. + // TODO: b/388596433 - Update minSdkVersion to BAKLAVA after SDK bumps. + // TODO: b/383440585 - Update minSdkVersion to TIRAMISU when flag annotation issue is fixed. + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + @RequiresFlagsDisabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) + @Test + fun testBodySensorSplit32PostV_replaceBodySensorFlagDisabled() { installPackage(APP_APK_PATH_32) - testBodySensorPermissionSplit(true) + testBodySensorPermissionSplitToBodySensorsBackground(true) } + // TODO: b/388596433 - Update maxSdkVersion to VANILLA_ICE_CREAM after SDK bumps. + // TODO: b/383440585 - Remove this test when flag annotation issue is fixed. @SdkSuppress( minSdkVersion = Build.VERSION_CODES.TIRAMISU, - maxSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM, + maxSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, ) @Test - fun testBodySensorNonSplitonTToV() { + fun testBodySensorNonSplitOnTToU() { installPackage(APP_APK_PATH_LATEST) - testBodySensorPermissionSplit(false) + testBodySensorPermissionSplitToBodySensorsBackground(false) + } + + // Before SDK_INT bumps to 36, the in-development B images are using SDK_INT=35(V). This will + // cause test failures on main builds where replaceBodySensor flag is enabled to remove Sensor + // group UI. As a workaround, we move SDK_INT=35 tests out and requires replaceBodySensor flag + // disabled when running on these images. + // TODO: b/388596433 - Update minSdkVersion to BAKLAVA after SDK bumps. + // TODO: b/383440585 - Update minSdkVersion to TIRAMISU when flag annotation issue is fixed. + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + @RequiresFlagsDisabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) + @Test + fun testBodySensorNonSplitPostV_replaceBodySensorFlagDisabled() { + installPackage(APP_APK_PATH_LATEST) + testBodySensorPermissionSplitToBodySensorsBackground(false) + } + + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava") + @RequiresFlagsEnabled(FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED) + @Test + fun testBodySensorSplitOnBaklava_splitToReadHeartRate() { + installPackage(APP_APK_PATH_30_WITH_BACKGROUND) + assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, false) + assertAppHasPermission(HealthPermissions.READ_HEART_RATE, false) + assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, false) + assertAppHasPermission(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND, false) + + requestAppPermissionsAndAssertResult( + android.Manifest.permission.BODY_SENSORS to true, + waitForWindowTransition = false, + ) { + clickAllowReadHeartRate() + } + + requestAppPermissionsAndAssertResult( + android.Manifest.permission.BODY_SENSORS_BACKGROUND to true, + waitForWindowTransition = false, + ) { + clickAlwaysAllowReadHealthDataInBackground() + } + + assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, true) + assertAppHasPermission(HealthPermissions.READ_HEART_RATE, true) + assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, true) + assertAppHasPermission(HealthPermissions.READ_HEALTH_DATA_IN_BACKGROUND, true) } private fun testLocationPermissionSplit(expectSplit: Boolean) { @@ -112,7 +194,7 @@ class PermissionSplitTest : BaseUsePermissionTest() { assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, expectSplit) } - private fun testBodySensorPermissionSplit(expectSplit: Boolean) { + private fun testBodySensorPermissionSplitToBodySensorsBackground(expectSplit: Boolean) { assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, false) assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, false) diff --git a/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt index baebfe06f..68af60dde 100644 --- a/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt +++ b/tests/cts/permissionui/src/android/permissionui/cts/PermissionTapjackingTest.kt @@ -52,14 +52,17 @@ class PermissionTapjackingTest : BaseUsePermissionTest() { requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {} val buttonCenter = - waitFindObject(By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT)) - .displayId(displayId)) + waitFindObject( + By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT)) + .displayId(displayId) + ) .visibleCenter // Wait for overlay to hide the dialog context.sendBroadcast(Intent(ACTION_SHOW_OVERLAY).putExtra(EXTRA_FULL_OVERLAY, true)) waitFindObject( - By.res("android.permissionui.cts.usepermission:id/overlay").displayId(displayId)) + By.res("android.permissionui.cts.usepermission:id/overlay").displayId(displayId) + ) tryClicking(buttonCenter) } @@ -76,18 +79,19 @@ class PermissionTapjackingTest : BaseUsePermissionTest() { assertAppHasPermission(ACCESS_FINE_LOCATION, false) requestAppPermissionsForNoResult(ACCESS_FINE_LOCATION) {} - val foregroundButtonCenter = - waitFindObject(By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT)) - .displayId(displayId)) - .visibleCenter val oneTimeButton = - waitFindObjectOrNull(By.text(getPermissionControllerString(ALLOW_ONE_TIME_BUTTON_TEXT)) - .displayId(displayId)) + waitFindObjectOrNull( + By.text(getPermissionControllerString(ALLOW_ONE_TIME_BUTTON_TEXT)) + .displayId(displayId) + ) + // If one-time button is not available, fallback to deny button val overlayButtonBounds = oneTimeButton?.visibleBounds - ?: waitFindObject(By.text(getPermissionControllerString(DENY_BUTTON_TEXT)) - .displayId(displayId)) + ?: waitFindObject( + By.text(getPermissionControllerString(DENY_BUTTON_TEXT)) + .displayId(displayId) + ) .visibleBounds // Wait for overlay to hide the dialog @@ -100,7 +104,15 @@ class PermissionTapjackingTest : BaseUsePermissionTest() { .putExtra(OVERLAY_BOTTOM, overlayButtonBounds.bottom) ) waitFindObject( - By.res("android.permissionui.cts.usepermission:id/overlay").displayId(displayId)) + By.res("android.permissionui.cts.usepermission:id/overlay").displayId(displayId) + ) + + val foregroundButtonCenter = + waitFindObject( + By.text(getPermissionControllerString(ALLOW_FOREGROUND_BUTTON_TEXT)) + .displayId(displayId) + ) + .visibleCenter tryClicking(foregroundButtonCenter) } @@ -119,7 +131,7 @@ class PermissionTapjackingTest : BaseUsePermissionTest() { } assertAppHasPermission(ACCESS_FINE_LOCATION, true) }, - 10000 + 10000, ) } catch (e: RuntimeException) { // expected @@ -140,22 +152,26 @@ class PermissionTapjackingTest : BaseUsePermissionTest() { } assertAppHasPermission(ACCESS_FINE_LOCATION, true) }, - 10000 + 10000, ) } private fun click(buttonCenter: Point) { - val downTime = SystemClock.uptimeMillis() - val x= buttonCenter.x.toFloat() - val y = buttonCenter.y.toFloat() - var event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,x , y, 0) - event.displayId = displayId - uiAutomation.injectInputEvent(event, true) - - val upTime = SystemClock.uptimeMillis() - event = MotionEvent.obtain(upTime, upTime, MotionEvent.ACTION_UP, x, y, 0) - event.displayId = displayId - uiAutomation.injectInputEvent(event, true) + if (isAutomotiveVisibleBackgroundUser) { + val downTime = SystemClock.uptimeMillis() + val x = buttonCenter.x.toFloat() + val y = buttonCenter.y.toFloat() + var event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, 0) + event.displayId = displayId + uiAutomation.injectInputEvent(event, true) + + val upTime = SystemClock.uptimeMillis() + event = MotionEvent.obtain(upTime, upTime, MotionEvent.ACTION_UP, x, y, 0) + event.displayId = displayId + uiAutomation.injectInputEvent(event, true) + } else { + uiDevice.click(buttonCenter.x, buttonCenter.y) + } } companion object { diff --git a/tests/cts/role/Android.bp b/tests/cts/role/Android.bp index 9f1e6cff6..ea9af5d8e 100644 --- a/tests/cts/role/Android.bp +++ b/tests/cts/role/Android.bp @@ -37,7 +37,9 @@ android_test { "bedstead-multiuser", "flag-junit", "platform-test-annotations", + "platform-test-rules", "truth", + "uiautomator-helpers", ], test_suites: [ @@ -48,9 +50,17 @@ android_test { ], data: [ + ":CtsDefaultNotesApp", ":CtsRoleTestApp", ":CtsRoleTestApp28", ":CtsRoleTestApp33WithoutInCallService", ":CtsRoleTestAppClone", ], } + +filegroup { + name: "CtsRoleTestUtils", + srcs: [ + "src/android/app/role/cts/RoleManagerUtil.kt", + ], +} diff --git a/tests/cts/role/AndroidManifest.xml b/tests/cts/role/AndroidManifest.xml index a8c8c8e3d..7ea4287dc 100644 --- a/tests/cts/role/AndroidManifest.xml +++ b/tests/cts/role/AndroidManifest.xml @@ -22,6 +22,7 @@ <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> + <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> <application> diff --git a/tests/cts/role/AndroidTest.xml b/tests/cts/role/AndroidTest.xml index 73f23dd1b..9a60b09e3 100644 --- a/tests/cts/role/AndroidTest.xml +++ b/tests/cts/role/AndroidTest.xml @@ -32,6 +32,8 @@ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true" /> <option name="test-file-name" value="CtsRoleTestCases.apk" /> + <option name="install-arg" value="-t" /> + <option name="test-file-name" value="CtsDefaultNotesApp.apk" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> @@ -40,6 +42,7 @@ </target_preparer> <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher"> <option name="cleanup" value="true" /> + <option name="push" value="CtsDefaultNotesApp.apk->/data/local/tmp/cts-role/CtsDefaultNotesApp.apk" /> <option name="push" value="CtsRoleTestApp.apk->/data/local/tmp/cts-role/CtsRoleTestApp.apk" /> <option name="push" value="CtsRoleTestApp28.apk->/data/local/tmp/cts-role/CtsRoleTestApp28.apk" /> <option name="push" value="CtsRoleTestApp33WithoutInCallService.apk->/data/local/tmp/cts-role/CtsRoleTestApp33WithoutInCallService.apk" /> diff --git a/tests/cts/role/src/android/app/role/cts/ChooseNoteRoleAppTest.kt b/tests/cts/role/src/android/app/role/cts/ChooseNoteRoleAppTest.kt new file mode 100644 index 000000000..18003d1d9 --- /dev/null +++ b/tests/cts/role/src/android/app/role/cts/ChooseNoteRoleAppTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.role.cts + +import android.content.Intent +import android.platform.test.rule.NotesRoleManagerRule +import android.platform.uiautomatorhelpers.WaitUtils.ensureThat +import android.provider.Settings +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.By.text +import com.android.compatibility.common.util.UiAutomatorUtils2.getUiDevice +import com.android.compatibility.common.util.UiAutomatorUtils2.waitFindObject +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test + +class ChooseNoteRoleAppTest { + + @[Rule JvmField] + val rule = NotesRoleManagerRule(requiredNotesRoleHolderPackage = NOTES_APP_PACKAGE_NAME) + + @Before + fun setUp() { + rule.utils.clearRoleHolder() + InstrumentationRegistry.getInstrumentation() + .context + .startActivity( + Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS) + .addCategory(Intent.CATEGORY_DEFAULT) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + ) + } + + @After + fun after() { + getUiDevice().pressHome() + } + + @Test + fun chooseNoteRoleHolderApp() { + ensureThat { rule.utils.getRoleHolderPackageName().isEmpty() } + + // Scroll to "Notes app" item in Default apps screen and click on it + waitFindObject(text("Notes app")).click() + // Scroll to "CtsDefaultNotesApp" item and click on it + waitFindObject(text("CtsDefaultNotesApp")).click() + + assertEquals(rule.utils.getRoleHolderPackageName(), NOTES_APP_PACKAGE_NAME) + } + + private companion object { + const val NOTES_APP_PACKAGE_NAME = "com.android.cts.notesapp" + } +} diff --git a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java index f26bc0eb5..bf1e32676 100644 --- a/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java +++ b/tests/cts/role/src/android/app/role/cts/RoleManagerTest.java @@ -18,6 +18,7 @@ package android.app.role.cts; import static com.android.bedstead.multiuser.MultiUserDeviceStateExtensionsKt.privateProfile; import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity; +import static com.android.compatibility.common.util.SystemUtil.eventually; import static com.android.compatibility.common.util.SystemUtil.runShellCommand; import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow; import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; @@ -26,6 +27,7 @@ import static com.android.compatibility.common.util.UiAutomatorUtils2.waitFindOb import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; @@ -286,6 +288,10 @@ public class RoleManagerTest { public void requestRoleThenBlockRequestRoleDialogByRestrictedSettingDialog() throws Exception { assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)); assumeFalse(sIsWatch || sIsAutomotive || sIsTelevision); + // TODO: b/388960315 - Remove wait after addressing race condition + runWithShellPermissionIdentity( + () -> waitForEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME, + AppOpsManager.MODE_DEFAULT)); runWithShellPermissionIdentity( () -> setEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME, AppOpsManager.MODE_ERRORED)); @@ -713,6 +719,10 @@ public class RoleManagerTest { throws Exception { assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER)); assumeFalse(sIsWatch || sIsAutomotive || sIsTelevision); + // TODO: b/388960315 - Remove wait after addressing race condition + runWithShellPermissionIdentity( + () -> waitForEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME, + AppOpsManager.MODE_DEFAULT)); runWithShellPermissionIdentity( () -> setEnhancedConfirmationRestrictedAppOpMode(sContext, APP_PACKAGE_NAME, AppOpsManager.MODE_ERRORED)); @@ -923,6 +933,19 @@ public class RoleManagerTest { pressBack(); } + private void waitForEnhancedConfirmationRestrictedAppOpMode(@NonNull Context context, + @NonNull String packageName, int expectedMode) + throws PackageManager.NameNotFoundException { + final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class); + eventually(() -> { + int uid = context.getPackageManager().getApplicationInfo(packageName, 0).uid; + int actualMode = appOpsManager.checkOpNoThrow( + AppOpsManager.OPSTR_ACCESS_RESTRICTED_SETTINGS, uid, packageName); + assertEquals("Even after waiting, a test app's post-install" + + " ACCESS_RESTRICTED_SETTINGS op mode is incorrect", expectedMode, actualMode); + }); + } + private void setEnhancedConfirmationRestrictedAppOpMode(@NonNull Context context, @NonNull String packageName, int mode) throws PackageManager.NameNotFoundException { @@ -1402,16 +1425,6 @@ public class RoleManagerTest { }); } - @RequiresFlagsDisabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava") - @Test - public void cannotGetDefaultHoldersForTestFlagDisabled() throws Exception { - runWithShellPermissionIdentity(() -> { - assertThrows(IllegalStateException.class, () -> - sRoleManager.getDefaultHoldersForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME)); - }); - } - @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava") @Test @@ -1441,18 +1454,6 @@ public class RoleManagerTest { }); } - @RequiresFlagsDisabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava") - @Test - public void cannotSetDefaultHoldersForTestFlagDisabled() throws Exception { - List<String> testRoleHolders = List.of("a", "b", "c"); - runWithShellPermissionIdentity(() -> { - assertThrows(IllegalStateException.class, () -> - sRoleManager.setDefaultHoldersForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME, - testRoleHolders)); - }); - } - @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava") @Test @@ -1528,16 +1529,6 @@ public class RoleManagerTest { }); } - @RequiresFlagsDisabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava") - @Test - public void cannotGetIsRoleVisibleForTestFlagDisabled() throws Exception { - runWithShellPermissionIdentity(() -> { - assertThrows(IllegalStateException.class, () -> - sRoleManager.isRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME)); - }); - } - @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava") @Test @@ -1566,16 +1557,6 @@ public class RoleManagerTest { }); } - @RequiresFlagsDisabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava") - @Test - public void cannotSetRoleVisibleForTestFlagDisabled() throws Exception { - runWithShellPermissionIdentity(() -> { - assertThrows(IllegalStateException.class, () -> - sRoleManager.setRoleVisibleForTest(PROFILE_GROUP_EXCLUSIVE_ROLE_NAME, false)); - }); - } - @RequiresFlagsEnabled(com.android.permission.flags.Flags.FLAG_CROSS_USER_ROLE_ENABLED) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA, codeName = "Baklava") @Test diff --git a/tests/cts/rolemultiuser/Android.bp b/tests/cts/rolemultiuser/Android.bp index 7a49bc4e5..51eff83b9 100644 --- a/tests/cts/rolemultiuser/Android.bp +++ b/tests/cts/rolemultiuser/Android.bp @@ -24,6 +24,7 @@ android_test { srcs: [ "src/**/*.kt", + ":CtsRoleTestUtils", ], static_libs: [ diff --git a/tests/cts/rolemultiuser/TEST_MAPPING b/tests/cts/rolemultiuser/TEST_MAPPING index 323e3094c..b45469e14 100644 --- a/tests/cts/rolemultiuser/TEST_MAPPING +++ b/tests/cts/rolemultiuser/TEST_MAPPING @@ -1,12 +1,17 @@ { - "postsubmit": [ + "presubmit": [ { "name": "CtsRoleMultiUserTestCases" } ], - "mainline-postsubmit": [ + "mainline-presubmit": [ { "name": "CtsRoleMultiUserTestCases[com.google.android.permission.apex]" } + ], + "permission-mainline-presubmit": [ + { + "name": "CtsRoleMultiUserTestCases" + } ] } diff --git a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt index ee00c2c39..e8aaddf4c 100644 --- a/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt +++ b/tests/cts/rolemultiuser/src/android/app/rolemultiuser/cts/RoleManagerMultiUserTest.kt @@ -16,6 +16,7 @@ package android.app.rolemultiuser.cts import android.app.Activity +import android.app.role.cts.RoleManagerUtil import android.app.role.RoleManager import android.content.ComponentName import android.content.Context @@ -70,6 +71,7 @@ import java.util.function.Consumer import org.junit.After import org.junit.Assert.assertThrows import org.junit.Assume.assumeFalse +import org.junit.Assume.assumeTrue import org.junit.Before import org.junit.ClassRule import org.junit.Rule @@ -88,6 +90,7 @@ class RoleManagerMultiUserTest { @Before @Throws(java.lang.Exception::class) fun setUp() { + assumeTrue(RoleManagerUtil.isCddCompliantScreenSize()); installAppForAllUsers() } diff --git a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt index ee0326bd3..09a32f058 100644 --- a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt +++ b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt @@ -60,6 +60,7 @@ import com.android.safetycenter.testing.UiTestHelper.RESCAN_BUTTON_LABEL import com.android.safetycenter.testing.UiTestHelper.clickConfirmDismissal import com.android.safetycenter.testing.UiTestHelper.clickDismissIssueCard import com.android.safetycenter.testing.UiTestHelper.clickMoreIssuesCard +import com.android.safetycenter.testing.UiTestHelper.clickOpenSubpage import com.android.safetycenter.testing.UiTestHelper.resetRotation import com.android.safetycenter.testing.UiTestHelper.rotate import com.android.safetycenter.testing.UiTestHelper.setAnimationsEnabled @@ -481,7 +482,10 @@ class SafetyCenterActivityTest { context.launchSafetyCenterActivity { if (SafetyCenterFlags.showSubpages) { - waitDisplayed(By.text("OK")) { it.click() } // Open subpage + clickOpenSubpage( + context, + safetyCenterTestConfigs.singleSourceConfig.safetySourcesGroups.first(), + ) } waitDisplayed(By.text("OK")) { it.click() } waitButtonDisplayed("Exit test activity") { it.click() } @@ -495,7 +499,11 @@ class SafetyCenterActivityTest { context.launchSafetyCenterActivity { if (SafetyCenterFlags.showSubpages) { - waitDisplayed(By.text("OK")) { it.click() } // Open subpage + clickOpenSubpage( + context, + safetyCenterTestConfigs.implicitIntentSingleSourceConfig.safetySourcesGroups + .first(), + ) } waitDisplayed(By.text("OK")) { it.click() } waitButtonDisplayed("Exit test activity") { it.click() } @@ -509,7 +517,10 @@ class SafetyCenterActivityTest { context.launchSafetyCenterActivity { if (SafetyCenterFlags.showSubpages) { - waitDisplayed(By.text("OK")) { it.click() } // Open subpage + clickOpenSubpage( + context, + safetyCenterTestConfigs.singleSourceConfig.safetySourcesGroups.first(), + ) } waitDisplayed(By.text("Ok title")) { it.click() } waitButtonDisplayed("Exit test activity") { it.click() } @@ -527,7 +538,10 @@ class SafetyCenterActivityTest { context.launchSafetyCenterActivity { if (SafetyCenterFlags.showSubpages) { - waitDisplayed(By.text("OK")) { it.click() } // Open subpage + clickOpenSubpage( + context, + safetyCenterTestConfigs.singleSourceConfig.safetySourcesGroups.first(), + ) } waitDisplayed(By.desc("Information")) { it.click() } waitButtonDisplayed("Exit test activity") { it.click() } @@ -1482,7 +1496,11 @@ class SafetyCenterActivityTest { context.launchSafetyCenterActivity { if (SafetyCenterFlags.showSubpages) { - waitDisplayed(By.text("OK")) { it.click() } // Open subpage + clickOpenSubpage( + context, + safetyCenterTestConfigs.implicitIntentSingleSourceConfig.safetySourcesGroups + .first(), + ) } waitDisplayed(By.text("OK")) { it.click() } waitDisplayed(By.text("is_from_settings_homepage false")) diff --git a/tests/utils/safetycenter/AndroidManifest.xml b/tests/utils/safetycenter/AndroidManifest.xml index f0a4fcbb6..ce3724318 100644 --- a/tests/utils/safetycenter/AndroidManifest.xml +++ b/tests/utils/safetycenter/AndroidManifest.xml @@ -39,7 +39,6 @@ android:exported="false"/> <activity android:name=".TestActivity" - android:theme="@style/OptOutEdgeToEdgeEnforcement" android:exported="false"> <intent-filter android:priority="-1"> <action android:name="com.android.safetycenter.testing.action.TEST_ACTIVITY"/> diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt index c7d195528..3dfefeecf 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/UiTestHelper.kt @@ -197,6 +197,7 @@ object UiTestHelper { /** Opens the subpage by clicking on the group title. */ fun clickOpenSubpage(context: Context, group: SafetySourcesGroup) { waitDisplayed(By.text(context.getString(group.titleResId))) { it.click() } + getUiDevice().waitForIdle() } /** Clicks the more issues card button to show or hide additional issues. */ diff --git a/tests/utils/safetycenter/res/layout/test_activity.xml b/tests/utils/safetycenter/res/layout/test_activity.xml index edbe3641a..b0b7523c8 100644 --- a/tests/utils/safetycenter/res/layout/test_activity.xml +++ b/tests/utils/safetycenter/res/layout/test_activity.xml @@ -19,6 +19,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:fitsSystemWindows="true" android:orientation="vertical" > <Button android:id="@+id/button" android:layout_width="wrap_content" diff --git a/tests/utils/safetycenter/res/values/styles.xml b/tests/utils/safetycenter/res/values/styles.xml deleted file mode 100644 index ce54568ed..000000000 --- a/tests/utils/safetycenter/res/values/styles.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2024 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<resources xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- - TODO(b/309578419): Make activities handle insets properly and then remove this. - --> - <style name="OptOutEdgeToEdgeEnforcement"> - <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> - </style> -</resources> |